Chapter 55: Testing Frameworks and Infrastructure¶
Testing in the Android Open Source Project is not an afterthought bolted onto the platform; it is a first-class citizen woven into every layer of the build system, the continuous integration pipeline, and the compliance certification process. Android ships on billions of devices made by hundreds of OEMs, which makes correctness verification at scale a survival requirement rather than a luxury.
This chapter provides a comprehensive tour of every major testing subsystem in AOSP: from the philosophy that guides where tests live and how they run, through the build system module types that compile them, to the harness that executes them on hosts and devices. Every section references real source files in the tree so you can follow along in your own checkout.
55.1 Testing Philosophy¶
55.1.1 The Android Test Pyramid¶
Android's testing strategy borrows the classic test pyramid concept and extends it to account for the unique challenge of a platform that spans kernel, native libraries, a managed-runtime framework, and user-space applications.
/\
/ \
/ E2E\ Fewest, slowest, most realistic
/------\
/ Integr.\ Medium count, device or emulator
/----------\
/ Unit \ Most numerous, fastest, host-preferred
/--------------\
The pyramid's width at each level represents test volume, and its height represents execution cost. Android tooling actively pushes tests downward:
| Level | Typical runner | Target | Speed | Example |
|---|---|---|---|---|
| Unit (host) | JUnit / GoogleTest on host JVM/binary | No device | Milliseconds | Ravenwood, Robolectric, cc_test_host |
| Unit (device) | AndroidJUnitRunner | Device/emulator | Seconds | android_test with small scope |
| Integration | TradeFederation suites | Device/emulator | Minutes | CTS module, VTS HAL test |
| End-to-end | UIAutomator / TAPL / Flicker | Device/emulator | Minutes | Launcher transition test |
55.1.2 The Major Test Suites¶
Android defines several compliance and validation suites. Each suite is a
TradeFederation plan that selects a set of test modules, and each module
declares its suite membership via the test_suites property in its build rule.
| Suite | Full Name | Purpose | Runner |
|---|---|---|---|
| CTS | Compatibility Test Suite | Verify API contracts for app compatibility | cts-tradefed |
| VTS | Vendor Test Suite | Verify HAL/kernel contracts across Treble boundary | vts-tradefed |
| GTS | Google Test Suite | Verify Google Mobile Services requirements | gts-tradefed |
| STS | Security Test Suite | Verify patches for monthly security bulletins | sts-tradefed |
| MTS | Mainline Test Suite | Verify updatable Mainline modules | mts-tradefed |
| CTS-root | CTS with root | CTS modules requiring root privileges | cts-root-tradefed |
| Catbox | Automotive compliance | CTS subset for automotive builds | catbox-tradefed |
55.1.3 Host Tests vs Device Tests¶
A fundamental axis in Android testing is where the test executes:
-
Host tests run on the build machine's JVM or as native binaries. They never touch a device. Examples:
java_test_host,cc_test_host,android_robolectric_test,android_ravenwood_test. -
Device tests run on a physical device or emulator. They are pushed via
adb, and TradeFederation manages their lifecycle. Examples:android_test,cc_testtargeting device. -
Host-driven device tests run control logic on the host but exercise code on the device. CTS host-side tests (
cts/hostsidetests/) are the canonical example.
The build system encodes this distinction in module types and in the
test_suites property. The atest tool uses it to decide whether to require a
connected device.
flowchart LR
subgraph Host["Host Machine"]
JVM["JVM / Native Binary"]
TF["TradeFederation"]
end
subgraph Device["Device / Emulator"]
ART["ART / Dalvik"]
Native["Native Binary"]
end
TF -- "adb push + am instrument" --> ART
TF -- "adb push + shell" --> Native
JVM -- "direct execution" --> JVM
TF -- "host test runner" --> JVM
55.1.4 The Role of Presubmit and Postsubmit¶
Android's CI pipeline distinguishes two phases:
-
Presubmit: Tests run before a change merges. These must be fast and reliable.
TEST_MAPPINGfiles declare which tests run in presubmit. -
Postsubmit: Tests run after a change merges, typically on the full build. Slower, flakier, or more resource-intensive tests live here.
The TEST_MAPPING system (Section 31.4) is the primary mechanism for declaring
presubmit and postsubmit coverage for a given directory.
55.1.5 Test Execution Environments¶
Understanding where tests can execute is crucial for choosing the right module type.
flowchart TB
subgraph Environments["Test Execution Environments"]
subgraph HostOnly["Host-Only (no device)"]
direction TB
H1["JUnit on host JVM"]
H2["GoogleTest host binary"]
H3["Python host test"]
H4["Rust host test"]
end
subgraph DeviceRequired["Device Required"]
direction TB
D1["Instrumentation test (APK)"]
D2["Native test on device"]
D3["Shell command test"]
end
subgraph HostDriven["Host-Driven + Device"]
direction TB
HD1["Host-side CTS"]
HD2["Host-side VTS"]
HD3["Mobly multi-device"]
end
end
style HostOnly fill:#c8e6c9
style DeviceRequired fill:#fff9c4
style HostDriven fill:#bbdefb
Host-only tests are the fastest and most reliable. They have no external dependencies beyond the build machine and can run in CI without device allocation. Ravenwood and Robolectric enable Java/Kotlin framework tests to run host-only.
Device-required tests exercise real system behavior on actual hardware or an emulator. They are essential for hardware-specific features (camera, sensors, telephony) and for verifying system integration.
Host-driven tests combine host-side logic with device interaction. A Java
test running on the host uses adb commands or TradeFed device APIs to
manipulate the device and verify behavior. This pattern is common in CTS
host-side tests where the test needs to install/uninstall apps, change device
state, or verify cross-process behavior.
55.1.6 Test Size Annotations¶
Android uses size annotations to categorize test execution time:
import androidx.test.filters.SmallTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.LargeTest;
@SmallTest // < 200ms, no I/O or network
@MediumTest // < 1000ms, may use filesystem
@LargeTest // No time limit, may use network/database
These annotations serve multiple purposes:
- TradeFed can filter by test size for fast presubmit runs
- CI pipelines can allocate appropriate timeouts
- Developers can quickly identify test expectations
Additional annotations used in AOSP:
| Annotation | Purpose |
|---|---|
@Presubmit |
Must pass in presubmit |
@FlakyTest |
Known flaky, excluded from presubmit |
@RequiresDevice |
Needs physical device (not emulator) |
@SecurityTest |
Security-related test |
@AppModeFull |
Run in full (non-instant) app mode |
@AppModeInstant |
Run in instant app mode |
@CddTest |
Maps to a CDD requirement |
55.1.7 Test Isolation Principles¶
Android tests strive for isolation to prevent interference:
- Process isolation: Each instrumentation test runs in its own process
- User isolation: Tests can create and destroy test users
- State cleanup: Target preparers restore device state after tests
-
Classloader isolation: Ravenwood uses
IsolatedHostTestwith separate classloaders per module -
Shard isolation: Each TradeFed shard gets cloned configuration objects
55.2 Trade Federation (TradeFed)¶
55.2.1 Overview¶
Trade Federation -- universally called TradeFed or just TF -- is Android's primary test execution harness. It manages the entire lifecycle: device allocation, build artifact preparation, test execution, result collection, and retry logic.
Source location:
tools/tradefederation/
core/ -- Main harness
contrib/ -- Community-contributed modules
prebuilts/ -- Pre-built JARs for bootstrapping
The core Java source tree lives under:
tools/tradefederation/core/src/com/android/tradefed/
command/ -- CommandScheduler, CommandRunner
config/ -- XML configuration parsing
invoker/ -- TestInvocation, InvocationExecution
invoker/shard/ -- ShardHelper, StrictShardHelper, DynamicShardHelper
testtype/ -- Test runners (IRemoteTest implementations)
targetprep/ -- Device preparers (flash, install APK, root, etc.)
result/ -- Result reporters and listeners
retry/ -- BaseRetryDecision, RetryStatistics
device/ -- Device abstraction (ITestDevice)
build/ -- Build info providers
suite/ -- Suite-level execution
55.2.2 Architecture¶
TradeFed's architecture is built around a pipeline of well-defined phases, each represented by pluggable Java objects configured in XML.
flowchart TB
subgraph TF["TradeFederation Process"]
CS["CommandScheduler"]
CS --> |"schedule"| TI["TestInvocation"]
TI --> IE["InvocationExecution"]
IE --> BP["Build Provider"]
IE --> TP["Target Preparers"]
IE --> TR["Test Runners"]
IE --> RR["Result Reporters"]
end
subgraph Device
D1["Device 1"]
D2["Device 2"]
end
TP --> |"prepare"| D1
TR --> |"execute"| D1
RR --> |"report"| Results["Results / Logs"]
The key classes and interfaces:
CommandScheduler (tools/tradefederation/core/src/com/android/tradefed/command/CommandScheduler.java):
The central scheduler that accepts command-line invocations, matches them to
available devices, and dispatches TestInvocation instances. It handles device
allocation from the DeviceManager and supports both interactive console mode
and headless batch mode.
TestInvocation (tools/tradefederation/core/src/com/android/tradefed/invoker/TestInvocation.java):
Represents a single test run. It orchestrates the full pipeline:
- Fetch build artifacts (
IBuildProvider) - Prepare target devices (
ITargetPreparer) - Run tests (
IRemoteTest) - Collect results (
ITestInvocationListener) - Clean up (
ITargetCleaner)
InvocationExecution (tools/tradefederation/core/src/com/android/tradefed/invoker/InvocationExecution.java):
The concrete execution logic that drives the phases above. For sandboxed
invocations, SandboxedInvocationExecution and ParentSandboxInvocationExecution
provide isolation.
55.2.3 Configuration System¶
TradeFed uses XML configuration files to describe test plans. A configuration specifies:
<configuration description="Example test config">
<build_provider class="com.android.tradefed.build.DeviceBuildProvider" />
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup" />
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="MyTest.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.example.mytest" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
</test>
<result_reporter class="com.android.tradefed.result.ConsoleResultReporter" />
</configuration>
The configuration is parsed by ConfigurationFactory
(tools/tradefederation/core/src/com/android/tradefed/config/ConfigurationFactory.java)
and ConfigurationXmlParser. Each <option> tag is injected into the target
object via OptionSetter, which uses Java reflection and the @Option
annotation:
public class AndroidJUnitTest implements IRemoteTest, IDeviceTest {
@Option(name = "package", description = "The test package to run.")
private String mPackageName = null;
@Option(name = "runner", description = "The instrumentation runner.")
private String mRunnerName = "androidx.test.runner.AndroidJUnitRunner";
// ...
}
55.2.4 Sharding¶
Sharding splits a test suite across multiple devices or invocations for parallel execution. TradeFed provides several sharding strategies:
ShardHelper (tools/tradefederation/core/src/com/android/tradefed/invoker/shard/ShardHelper.java):
The primary helper that creates shard invocations. It clones configuration
objects to each shard to avoid shared state. From the source:
/** Helper class that handles creating the shards and scheduling them
* for an invocation. */
public class ShardHelper implements IShardHelper {
public static final String LAST_SHARD_DETECTOR = "last_shard_detector";
public static final String SHARED_TEST_INFORMATION = "shared_test_information";
// ...
}
StrictShardHelper: Ensures each shard gets a deterministic, non-overlapping partition of test cases.
DynamicShardHelper: Uses a gRPC-based dynamic sharding service
(ConfigurableGrpcDynamicShardingClient) that distributes tests to workers on
demand, improving load balancing when test durations vary widely.
sequenceDiagram
participant CS as CommandScheduler
participant SH as ShardHelper
participant S1 as Shard 1
participant S2 as Shard 2
participant Pool as TestsPool
CS->>SH: shardConfig(tests, numShards=2)
SH->>Pool: create pool of test modules
SH->>S1: schedule(shard config clone)
SH->>S2: schedule(shard config clone)
S1->>Pool: poll next test
S2->>Pool: poll next test
Pool-->>S1: TestModule A
Pool-->>S2: TestModule B
S1->>S1: execute A
S2->>S2: execute B
Key sharding-related classes:
TestsPoolPoller: Polls from a sharedITestsPoolLocalPool: In-process pool implementationRemoteDynamicPool: gRPC-backed distributed poolParentShardReplicate: Replicates the parent invocation to each shard
55.2.5 Retry Logic¶
TradeFed has built-in retry support for handling flaky tests and transient failures. The retry subsystem is in:
tools/tradefederation/core/src/com/android/tradefed/retry/
BaseRetryDecision.java -- Core retry logic
IRetryDecision.java -- Interface
ResultAggregator.java -- Aggregates results across retries
RetryStatistics.java -- Tracks retry counts and outcomes
BaseRetryDecision implements the retry strategy:
- Retry on failure: Re-run only failed test cases
- Retry count: Configurable maximum number of retries
- Result aggregation:
ResultAggregatormerges results from multiple attempts, using the best outcome for each test case
The retry decision is wired into TestInvocation via IRetryDecision, which
examines the outcome of each test run module and decides whether to retry.
55.2.6 Test Types (Runners)¶
TradeFed provides a rich set of test runner implementations under
tools/tradefederation/core/src/com/android/tradefed/testtype/:
| Runner Class | Purpose |
|---|---|
AndroidJUnitTest |
Instrumentation tests (JUnit4/5 on device) |
GTest |
Native GoogleTest binaries on device |
HostTest |
JUnit tests on host JVM |
IsolatedHostTest |
Host tests in isolated classloader (Ravenwood) |
PythonBinaryHostTest |
Python tests on host |
RustBinaryHostTest |
Rust test binaries on host |
FakeTest |
Generates fake results for testing TF itself |
TfTestLauncher |
Launches another TF process |
The IRemoteTest interface is the contract all runners implement:
public interface IRemoteTest {
void run(TestInformation testInfo, ITestInvocationListener listener)
throws DeviceNotAvailableException;
}
55.2.7 Target Preparers¶
Target preparers set up the device before tests run. Key preparers in
tools/tradefederation/core/src/com/android/tradefed/targetprep/:
| Preparer | Purpose |
|---|---|
DeviceSetup |
Configure device settings (screen, locale, etc.) |
DeviceFlashPreparer |
Flash device with a build image |
RootTargetPreparer |
Ensure device has root access |
TestAppInstallSetup |
Install test APKs |
StopServicesSetup |
Stop framework services during test |
PushFilePreparer |
Push files to device |
The ITargetPreparer interface and its counterpart ITargetCleaner provide
setup/teardown semantics:
public interface ITargetPreparer {
void setUp(TestInformation testInfo) throws TargetSetupError,
BuildError, DeviceNotAvailableException;
}
public interface ITargetCleaner extends ITargetPreparer {
void tearDown(TestInformation testInfo, Throwable e)
throws DeviceNotAvailableException;
}
55.2.8 Suite Mode¶
TradeFed's suite mode (ITestSuite, BaseTestSuite) is the foundation for
CTS, VTS, MTS, and other compliance suites. Key classes:
tools/tradefederation/core/src/com/android/tradefed/testtype/suite/
ITestSuite.java -- Base suite runner
BaseTestSuite.java -- Configurable suite loading
ModuleDefinition.java -- Represents a single test module
ModuleListener.java -- Per-module result listener
ModuleSplitter.java -- Splits modules for sharding
SuiteModuleLoader.java -- Loads module configs from disk
TestMappingSuiteRunner.java -- Runs tests from TEST_MAPPING
GranularRetriableTestWrapper.java -- Per-test-case retry
ModuleDefinition encapsulates everything needed to run a single module:
preparers, tests, and cleanup. SuiteModuleLoader discovers *.config files
in the test case directories and instantiates ModuleDefinition objects.
55.2.9 Invocation Lifecycle in Detail¶
A complete TradeFed invocation follows this detailed lifecycle:
sequenceDiagram
participant User
participant CS as CommandScheduler
participant TI as TestInvocation
participant IE as InvocationExecution
participant BP as BuildProvider
participant TP as TargetPreparer[]
participant TR as IRemoteTest[]
participant RR as ResultReporter[]
participant Dev as Device
User->>CS: addCommand("test-config.xml")
CS->>CS: allocateDevice()
CS->>TI: invoke(context, config, rescheduler)
TI->>IE: doSetup()
IE->>BP: getBuild(buildInfo)
BP-->>IE: IBuildInfo
IE->>TP: setUp(testInfo)
TP->>Dev: adb push, install, configure
TI->>IE: runTests()
loop For each IRemoteTest
IE->>TR: run(testInfo, listener)
TR->>Dev: execute test
Dev-->>TR: results
TR->>RR: testRunStarted/testStarted/testEnded
end
TI->>IE: doTeardown()
IE->>TP: tearDown(testInfo)
TP->>Dev: cleanup
TI->>RR: invocationEnded()
TI->>CS: releaseDevice()
Key details of each phase:
Build Provision (IBuildProvider):
DeviceBuildProvider: Fetches build artifacts from a build serverLocalDeviceBuildProvider: Uses locally built artifactsCommandLineBuildInfoBuilder: Constructs build info from command-line args
Target Preparation (ITargetPreparer):
Preparers execute in order, and their teardowns execute in reverse order
(stack discipline). Common preparation sequences:
- Flash the device (
DeviceFlashPreparer) - Wait for boot completion
- Configure device settings (
DeviceSetup) - Install test APKs (
TestAppInstallSetup) - Push data files (
PushFilePreparer) - Root the device if needed (
RootTargetPreparer)
Test Execution (IRemoteTest):
Multiple tests can be configured in a single invocation. Each test
reports results via the ITestInvocationListener callback interface:
public interface ITestInvocationListener {
void invocationStarted(IInvocationContext context);
void testRunStarted(String runName, int testCount);
void testStarted(TestDescription test);
void testEnded(TestDescription test, HashMap<String, Metric> metrics);
void testFailed(TestDescription test, FailureDescription failure);
void testRunEnded(long elapsedTime, HashMap<String, Metric> metrics);
void invocationEnded(long elapsedTime);
}
55.2.10 Multi-Device Testing¶
TradeFed supports multi-device test configurations where a single test
module requires multiple devices. The configuration uses <device> tags:
<configuration description="Multi-device test">
<device name="device1">
<target_preparer class="...TestAppInstallSetup">
<option name="test-file-name" value="App1.apk" />
</target_preparer>
</device>
<device name="device2">
<target_preparer class="...TestAppInstallSetup">
<option name="test-file-name" value="App2.apk" />
</target_preparer>
</device>
<test class="com.example.MultiDeviceTest" />
</configuration>
The test accesses devices via TestInformation:
ITestDevice device1 = testInfo.getContext().getDevice("device1");
ITestDevice device2 = testInfo.getContext().getDevice("device2");
55.2.11 Sandbox Mode¶
TradeFed can run invocations in a sandbox for isolation. The sandbox uses a separate classloader or process to prevent test code from affecting the harness. This is critical for running untrusted test code in CI:
SandboxedInvocationExecution: Runs inside the sandboxParentSandboxInvocationExecution: Coordinates from outside
55.2.12 Result Reporting¶
TradeFed supports multiple result reporters simultaneously:
| Reporter | Output |
|---|---|
ConsoleResultReporter |
Terminal output |
TextResultReporter |
Plain text file |
XmlResultReporter |
JUnit XML format |
InvocationProtoResultReporter |
Protocol buffer format |
FileInputStreamSource |
Log file attachments |
LogSaverResultForwarder |
Saves logs to storage |
Results include:
- Pass/fail status for each test case
- Stack traces for failures
- Test metrics (timing, custom metrics)
- Device logs (logcat, bugreport)
- Screenshots on failure
55.3 atest¶
55.3.1 Overview¶
atest is the developer-facing CLI tool that automates the build-install-test
cycle. It translates human-friendly test references into TradeFederation
invocations.
Source: tools/asuite/atest/atest_main.py (1683 lines)
From the module docstring:
"""Command line utility for running Android tests through TradeFederation.
atest helps automate the flow of building test modules across the Android
code base and executing the tests via the TradeFederation test harness.
atest is designed to support any test types that can be ran by TradeFederation.
"""
55.3.2 Architecture¶
atest's execution flow follows three steps, controlled by the Steps dataclass:
@dataclasses.dataclass
class Steps:
"""A dataclass that stores enabled steps."""
build: bool
install: bool
test: bool
The main entry point creates an AtestMain object and calls _run_all_steps(),
which orchestrates:
- Test discovery -- Find test modules matching the user's references
- Build -- Invoke the build system to compile the test and its dependencies
- Test -- Execute via TradeFederation
flowchart TB
User["atest FrameworksCoreTests"] --> Parse["Parse Arguments"]
Parse --> Discover["Test Discovery<br>(cli_translator, module_finder)"]
Discover --> Build["Build Step<br>(make/soong)"]
Build --> Install["Install Step<br>(adb install)"]
Install --> Execute["Test Execution<br>(TradeFederation)"]
Execute --> Report["Result Reporter"]
55.3.3 Test Discovery¶
atest supports multiple test reference formats:
# By module name
atest FrameworksCoreTests
# By module:class
atest FrameworksCoreTests:android.os.BundleTest
# By file path
atest frameworks/base/core/tests/coretests/src/android/os/BundleTest.java
# By package
atest com.android.server.pm
# By TEST_MAPPING (current directory)
atest --test-mapping
# By class name
atest android.os.BundleTest
Test discovery is handled by finders in tools/asuite/atest/test_finders/:
module_finder.py-- Searches the module-info.json databasecache_finder.py-- Uses cached results from previous runstf_integration_finder.py-- Finds TradeFed integration configssuite_plan_finder.py-- Finds suite plans (CTS, VTS, etc.)smart_test_finder/-- AI/ML-based smart test selection
The cli_translator.py module coordinates the finders and translates user
input into TestInfo objects that the runner can execute.
55.3.4 Test Execution and Filtering¶
atest passes many options through to TradeFederation via extra args:
arg_maps = {
'all_abi': constants.ALL_ABI,
'annotation_filter': constants.ANNOTATION_FILTER,
'collect_tests_only': constants.COLLECT_TESTS_ONLY,
'custom_args': constants.CUSTOM_ARGS,
'device_only': constants.DEVICE_ONLY,
'disable_teardown': constants.DISABLE_TEARDOWN,
'dry_run': constants.DRY_RUN,
'host': constants.HOST,
'instant': constants.INSTANT,
'iterations': constants.ITERATIONS,
'serial': constants.SERIAL,
'sharding': constants.SHARDING,
'test_filter': constants.TEST_FILTER,
'test_timeout': constants.TEST_TIMEOUT,
# ...
}
Key filtering options:
--test-filter/-tf: Filter by class or method name--annotation-filter: Include/exclude by Java annotation--include-filter/--exclude-filter: TradeFed-level module filtering--host: Force host-side execution--device-only: Force device-side execution
55.3.5 Execution Mode Validation¶
atest validates that host-only and device-only tests are not mixed in
conflicting ways. From _validate_exec_mode():
def _validate_exec_mode(args, test_infos: list[TestInfo], host_tests=None):
all_device_modes = {x.get_supported_exec_mode() for x in test_infos}
# In the case of '$atest <device-only> --host', exit.
if (host_tests or args.host) and device_only_test_detected:
# ... error and exit
# In the case of '$atest <host-only>', we add --host to run on host-side.
if not args.host and host_tests is None and not device_only_test_detected:
args.host = host_only_test_detected
55.3.6 Common atest Commands¶
# Run a single module
atest CtsNetTestCases
# Run a specific test class
atest CtsNetTestCases:android.net.cts.ConnectivityManagerTest
# Run a specific method
atest CtsNetTestCases:android.net.cts.ConnectivityManagerTest#testGetActiveNetwork
# Run tests from TEST_MAPPING in current directory
atest
# Run only host tests
atest --host FrameworksMockingServicesTests
# Run with sharding across 4 devices
atest LargeTestSuite --sharding 4
# Dry run (show TF command without executing)
atest --dry-run CtsNetTestCases
# Run with iterations for flakiness detection
atest --iterations 10 MyFlakyTest
# Run with coverage
atest --experimental-coverage MyTest
55.3.7 Test Runner Registry¶
atest maintains a registry of test runners in test_runner_handler.py. Each
runner handles a different test execution backend:
_TEST_RUNNERS = {
atest_tf_test_runner.AtestTradefedTestRunner.NAME: (
atest_tf_test_runner.AtestTradefedTestRunner
),
mobly_test_runner.MoblyTestRunner.NAME: (
mobly_test_runner.MoblyTestRunner
),
robolectric_test_runner.RobolectricTestRunner.NAME: (
robolectric_test_runner.RobolectricTestRunner
),
suite_plan_test_runner.SuitePlanTestRunner.NAME: (
suite_plan_test_runner.SuitePlanTestRunner
),
vts_tf_test_runner.VtsTradefedTestRunner.NAME: (
vts_tf_test_runner.VtsTradefedTestRunner
),
}
The runners:
- AtestTradefedTestRunner: Default runner, invokes TradeFed for most tests
-
MoblyTestRunner: For Python-based multi-device tests using the Mobly framework
-
RobolectricTestRunner: Specialized runner for Robolectric tests
- SuitePlanTestRunner: Runs full suite plans (cts, vts, etc.)
- VtsTradefedTestRunner: VTS-specific TradeFed runner
55.3.8 CLITranslator¶
The CLITranslator class (tools/asuite/atest/cli_translator.py) is the
brain of atest's test discovery. From the source:
class CLITranslator:
"""CLITranslator class contains public method translate() and some
private helper methods. The atest tool can call the translate() method
with a list of strings, each string referencing a test to run.
Translate() will "translate" this list of test strings into a list of
build targets and a list of TradeFederation run commands.
Translation steps for a test string reference:
1. Narrow down the type of reference the test string could be,
i.e. whether it could be referencing a Module, Class,
Package, etc.
2. Try to find the test files assuming the test string is one
of these types of reference.
3. If test files found, generate Build Targets and the
Run Command.
"""
The translation uses module-info.json -- a database of all modules in the
build, generated by Soong. This database maps module names to their build
paths, installed paths, and test configuration files.
55.3.9 The _AtestMain Class¶
The main entry point is the _AtestMain class in atest_main.py:
class _AtestMain:
"""Entry point of atest script."""
def __init__(self, argv: list[str]):
self._argv: list[str] = argv
self._banner_printer: banner.BannerPrinter = None
self._steps: Steps = None
self._results_dir: str = None
self._mod_info: module_info.ModuleInfo = None
self._test_infos: list[test_info.TestInfo] = None
self._test_execution_plan: _TestExecutionPlan = None
self._acloud_proc: subprocess.Popen = None
self._acloud_report_file: str = None
self._test_info_loading_duration: float = 0
self._build_duration: float = 0
The run() method orchestrates the complete flow:
- Parse arguments (supports config file overrides)
- Validate environment (ANDROID_BUILD_TOP, etc.)
- Start acloud/AVD if requested
- Discover test modules (CLITranslator)
- Validate execution mode (host vs device)
- Build required modules
- Execute tests via TradeFed
- Report results
55.3.10 TEST_MAPPING Integration in atest¶
When invoked without arguments in a directory containing TEST_MAPPING, atest automatically discovers and runs the presubmit tests:
def is_from_test_mapping(test_infos):
"""Check that the test_infos came from TEST_MAPPING files."""
return list(test_infos)[0].from_test_mapping
TEST_MAPPING tests are split into device and host groups:
def _split_test_mapping_tests(test_infos):
"""Split Test Mapping tests into 2 groups: device and host tests."""
assert is_from_test_mapping(test_infos)
host_test_infos = {info for info in test_infos if info.host}
device_test_infos = {info for info in test_infos if not info.host}
return device_test_infos, host_test_infos
55.3.11 Smart Test Selection¶
atest supports smart test selection (--sts flag), which uses ML/heuristics
to determine which tests are most relevant for a given code change:
Smart test selection:
- Analyzes the
git diffof the current change - Maps changed files to historically relevant test modules
- Runs only the most impactful tests
- Reduces presubmit test time significantly
55.3.12 Device Availability Checking¶
atest validates device availability before running device tests:
def _validate_adb_devices(args, test_infos):
"""Validate the availability of connected devices via adb command."""
if not parse_steps(args).test:
return
if args.no_checking_device:
return
all_device_modes = {x.get_supported_exec_mode() for x in test_infos}
if constants.DEVICE_TEST in all_device_modes:
if (not any((args.host, args.start_avd, args.acloud_create))
and not atest_utils.get_adb_devices()):
err_msg = (
f'Stop running test(s): {", ".join(device_tests)} '
f'require a device.')
# ... exit with DEVICE_NOT_FOUND
55.3.13 Multi-Device Support¶
atest can detect when a test requires multiple devices:
def get_device_count_config(test_infos, mod_info):
"""Get the amount of desired devices from the test config."""
max_count = 0
for tinfo in test_infos:
test_config, _ = test_finder_utils.get_test_config_and_srcs(
tinfo, mod_info)
if test_config:
devices = atest_utils.get_config_device(test_config)
if devices:
max_count = max(len(devices), max_count)
return max_count
flowchart TB
subgraph atest["atest Architecture"]
CLI["Command Line Interface<br>arg_parser.py"]
Config["Config File<br>atest_configs.py"]
Translator["CLITranslator<br>cli_translator.py"]
subgraph Finders["Test Finders"]
MF["ModuleFinder"]
CF["CacheFinder"]
TFF["TFIntegrationFinder"]
SPF["SuitePlanFinder"]
STF["SmartTestFinder"]
end
subgraph Runners["Test Runners"]
ATF["AtestTradefedTestRunner"]
Mobly["MoblyTestRunner"]
Robo_R["RobolectricTestRunner"]
Suite_R["SuitePlanTestRunner"]
VTS_R["VtsTradefedTestRunner"]
end
Results["ResultReporter<br>result_reporter.py"]
end
CLI --> Config
Config --> Translator
Translator --> Finders
Finders --> |"TestInfo"| Runners
Runners --> Results
55.4 TEST_MAPPING¶
55.4.1 Purpose and Format¶
TEST_MAPPING files are JSON files placed alongside source code that declare
which tests should run when files in that directory (or its children) change.
They are the glue between code changes and presubmit/postsubmit test selection.
55.4.2 JSON Structure¶
A TEST_MAPPING file contains a JSON object whose keys are test groups
(typically presubmit, postsubmit, or custom names) and whose values are
arrays of test objects.
Simple example from system/libbase/TEST_MAPPING:
Complex example from frameworks/base/TEST_MAPPING:
{
"presubmit": [
{
"name": "ManagedProvisioningTests"
},
{
"file_patterns": [
"ApexManager\\.java",
"SystemServer\\.java",
"services/tests/apexsystemservices/.*"
],
"name": "ApexSystemServicesTestCases"
},
{
"name": "FrameworksUiServicesTests"
},
{
"name": "FrameworksCoreTests_Presubmit"
}
],
"ravenwood-presubmit": [
{
"name": "CtsUtilTestCasesRavenwood",
"host": true,
"file_patterns": ["[Rr]avenwood"]
}
],
"postsubmit-managedprofile-stress": [
{
"name": "ManagedProfileLifecycleStressTest"
}
],
"auto-postsubmit": [
{
"name": "FrameworksUiServicesTests"
},
{
"name": "TestablesTests"
}
],
"wear-cts-presubmit": [
{
"name": "CtsWidgetTestCases",
"options": [
{"include-filter": "android.widget.cts.RemoteViewsTest"},
{"include-filter": "android.widget.cts.TextViewTest"}
]
}
]
}
55.4.3 Test Object Fields¶
| Field | Type | Description |
|---|---|---|
name |
string | Module name (must match a test module in the build) |
host |
boolean | If true, run as host test |
file_patterns |
string[] | Regex patterns; test only runs when matching files change |
options |
object[] | TradeFed options (include-filter, exclude-filter, etc.) |
55.4.4 Test Groups¶
| Group | When it runs | Typical content |
|---|---|---|
presubmit |
Before merge, on every CL | Fast, reliable tests |
presubmit-large |
Before merge, more resources | Larger integration tests |
postsubmit |
After merge | Slower tests, stress tests |
ravenwood-presubmit |
Before merge, host-only | Ravenwood framework tests |
hwasan-presubmit |
Before merge, HWASAN builds | Memory-safety tests |
auto-postsubmit |
After merge, automotive targets | Automotive-specific |
wear-cts-presubmit |
Before merge, Wear targets | Wear-specific CTS subset |
| Custom groups | CI-defined | Any custom grouping |
55.4.5 Inheritance and Directory Walk¶
The TEST_MAPPING system walks up the directory tree from the changed file.
A test declared in frameworks/base/TEST_MAPPING applies to changes anywhere
under frameworks/base/. This allows broad test coverage with a single file,
while subdirectories can add their own more specific tests.
flowchart BT
File["frameworks/base/core/java/android/os/Bundle.java<br><i>(changed file)</i>"]
TM1["frameworks/base/core/java/android/os/TEST_MAPPING"]
TM2["frameworks/base/core/TEST_MAPPING"]
TM3["frameworks/base/TEST_MAPPING"]
File --> TM1
TM1 --> TM2
TM2 --> TM3
style TM3 fill:#e8f5e9
style TM1 fill:#e8f5e9
55.4.6 TestMappingSuiteRunner¶
TradeFed's TestMappingSuiteRunner
(tools/tradefederation/core/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java)
loads and executes tests from TEST_MAPPING files. It:
- Collects all TEST_MAPPING files for the changed paths
- Filters by the requested test group (presubmit, postsubmit, etc.)
- Applies file_patterns filtering
- Resolves module names to test configurations
- Executes via the standard suite pipeline
55.4.7 TEST_MAPPING Best Practices¶
- Keep presubmit fast: Only include tests that complete in under 5 minutes
- Use file_patterns: For large directories, scope tests to relevant changes
-
Avoid duplication: If a parent directory already tests a module, do not re-declare it in child directories
-
Group tests logically: Use custom groups for specialized targets (automotive, wear, etc.)
-
Specify host where possible: Add
"host": truefor host-only tests to avoid unnecessary device allocation -
Use options for filtering: Apply
include-filterto run only relevant test classes from large modules
55.4.8 Finding TEST_MAPPING Files¶
The CI system and atest both walk the directory tree to find TEST_MAPPING files. The search starts from the changed file's directory and walks upward to the repository root.
# From cli_translator.py
# Pattern used to identify comments in TEST_MAPPING.
_COMMENTS_RE = re.compile(r'(?m)[\s\t]*(#|//).*|(\".*?\")')
_COMMENTS = frozenset(['//', '#'])
TEST_MAPPING supports comments (lines starting with // or #), which is
non-standard JSON. The parser strips these before JSON parsing.
55.4.9 Validation¶
TEST_MAPPING files are validated at presubmit time. The validation checks:
- Valid JSON (after comment stripping)
- All referenced test modules exist in the build
- Test group names match known groups
- Options are valid TradeFed options
- No circular references
55.5 Build System Test Modules¶
55.5.1 Overview¶
The Soong build system provides dedicated module types for every supported test language and framework. Each module type encapsulates:
- Compilation rules
- Auto-generation of TradeFed XML configuration
- Test suite membership
- Installation to the correct directory
55.5.2 android_test (Java/Kotlin Instrumentation Test)¶
The most common Java test module type. It builds an APK containing test code
and installs it on a device via am instrument.
android_test {
name: "FrameworksCoreTests",
srcs: ["src/**/*.java"],
static_libs: [
"androidx.test.runner",
"androidx.test.rules",
"mockito-target-minus-junit4",
"truth",
],
test_suites: ["device-tests"],
platform_apis: true,
certificate: "platform",
instrumentation_for: "framework",
}
The build system auto-generates AndroidTest.xml (the TradeFed config) using
AutoGenInstrumentationTestConfig() from
build/soong/tradefed/autogen.go.
55.5.3 cc_test (Native GoogleTest)¶
Defined in build/soong/cc/test.go. Registered via:
func init() {
android.RegisterModuleType("cc_test", TestFactory)
android.RegisterModuleType("cc_test_library", TestLibraryFactory)
android.RegisterModuleType("cc_benchmark", BenchmarkFactory)
android.RegisterModuleType("cc_test_host", TestHostFactory)
android.RegisterModuleType("cc_benchmark_host", BenchmarkHostFactory)
}
Key properties from the source:
type TestLinkerProperties struct {
// if set, build against the gtest library. Defaults to true.
Gtest *bool
// if set, use the isolated gtest runner. Defaults to false.
Isolated *bool
}
type TestBinaryProperties struct {
// list of files or filegroup modules that provide data
Data []string `android:"path,arch_variant"`
// the name of the test configuration
Test_config *string `android:"path,arch_variant"`
// Add RootTargetPreparer to auto generated test config
Require_root *bool
// Add RunCommandTargetPreparer to stop framework
Disable_framework *bool
// Flag to indicate whether to create test config automatically
Auto_gen_config *bool
// Add parameterized mainline modules
Test_mainline_modules []string
// Install the test into a folder named for the module
Per_testcase_directory *bool
}
When gtest is true (the default), the build system automatically links
libgtest_main and libgtest:
func (test *testDecorator) linkerDeps(ctx BaseModuleContext, deps Deps) Deps {
if test.gtest() {
if ctx.useSdk() && ctx.Device() {
deps.StaticLibs = append(deps.StaticLibs,
"libgtest_main_ndk_c++", "libgtest_ndk_c++")
} else if test.isolated(ctx) {
deps.StaticLibs = append(deps.StaticLibs, "libgtest_isolated_main")
deps.SharedLibs = append(deps.SharedLibs, "liblog")
} else {
deps.StaticLibs = append(deps.StaticLibs, "libgtest_main", "libgtest")
}
}
return deps
}
The GTest flags are set based on the target platform:
func (test *testDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
if !test.gtest() {
return flags
}
flags.Local.CFlags = append(flags.Local.CFlags, "-DGTEST_HAS_STD_STRING")
if ctx.Host() {
switch ctx.Os() {
case android.Windows:
flags.Local.CFlags = append(flags.Local.CFlags, "-DGTEST_OS_WINDOWS")
case android.Linux:
flags.Local.CFlags = append(flags.Local.CFlags, "-DGTEST_OS_LINUX")
case android.Darwin:
flags.Local.CFlags = append(flags.Local.CFlags, "-DGTEST_OS_MAC")
}
} else {
flags.Local.CFlags = append(flags.Local.CFlags, "-DGTEST_OS_LINUX_ANDROID")
}
return flags
}
Example cc_test:
cc_test {
name: "libbase_test",
defaults: ["libbase_test_defaults"],
srcs: [
"chrono_utils_test.cpp",
"endian_test.cpp",
"errors_test.cpp",
"expected_test.cpp",
"file_test.cpp",
"logging_test.cpp",
"mapped_file_test.cpp",
"parsebool_test.cpp",
"parsedouble_test.cpp",
"parseint_test.cpp",
"result_test.cpp",
"scopeguard_test.cpp",
"stringprintf_test.cpp",
"strings_test.cpp",
],
test_suites: ["device-tests"],
}
55.5.4 cc_test_host¶
A convenience variant of cc_test that targets only the host:
func TestHostFactory() android.Module {
module := NewTest(android.HostSupported)
return module.Init()
}
55.5.5 rust_test¶
Defined in build/soong/rust/test.go. Properties mirror cc_test:
type TestProperties struct {
No_named_install_directory *bool
Test_config *string `android:"path,arch_variant"`
Test_config_template *string `android:"path,arch_variant"`
Test_suites []string `android:"arch_variant"`
Data []string `android:"path,arch_variant"`
Data_libs []string `android:"arch_variant"`
Data_bins []string `android:"arch_variant"`
Auto_gen_config *bool
// if set, build with the standard Rust test harness. Defaults to true.
Test_harness *bool
Test_options android.CommonTestOptions
Require_root *bool
}
When test_harness is true (default), the Rust compiler is invoked with
--test, which enables the built-in test framework that discovers functions
annotated with #[test].
Example:
rust_test {
name: "libkeystore2_test",
crate_name: "keystore2_test",
srcs: ["tests/*.rs"],
test_suites: ["general-tests"],
static_libs: ["libkeystore2"],
}
55.5.6 python_test_host¶
Defined in build/soong/python/test.go:
func init() {
registerPythonTestComponents(android.InitRegistrationContext)
}
func registerPythonTestComponents(ctx android.RegistrationContext) {
ctx.RegisterModuleType("python_test_host", PythonTestHostFactory)
ctx.RegisterModuleType("python_test", PythonTestFactory)
}
Python test options include runner selection:
type TestOptions struct {
android.CommonTestOptions
// Runner for the test. Supports "tradefed" and "mobly"
// (for multi-device tests). Default is "tradefed".
Runner *string
// Metadata to describe the test configuration.
Metadata []Metadata
}
55.5.7 java_test_host¶
A Java test that runs on the host JVM. Commonly used for host-side CTS tests
that use adb to interact with the device programmatically.
java_test_host {
name: "CtsAppSecurityHostTestCases",
srcs: ["src/**/*.java"],
libs: [
"cts-tradefed",
"tradefed",
"compatibility-host-util",
],
test_suites: ["cts", "general-tests"],
}
55.5.8 Auto-Generated Test Configuration¶
The build system's tradefed package (build/soong/tradefed/autogen.go)
auto-generates TradeFed XML configs for test modules. The key function is
AutoGenTestConfig():
type AutoGenTestConfigOptions struct {
Name string
OutputFileName string
TestConfigProp *string
TestConfigTemplateProp *string
TestSuites []string
Config []Config
OptionsForAutogenerated []Option
TestRunnerOptions []Option
AutoGenConfig *bool
UnitTest *bool
TestInstallBase string
DeviceTemplate string
HostTemplate string
HostUnitTestTemplate string
StandaloneTest *bool
}
func AutoGenTestConfig(ctx android.ModuleContext,
options AutoGenTestConfigOptions) android.Path {
// ...
if ctx.Device() {
autogenTemplate(ctx, name, autogenPath,
options.DeviceTemplate, configs, ...)
} else {
if Bool(options.UnitTest) {
autogenTemplate(ctx, name, autogenPath,
options.HostUnitTestTemplate, configs, ...)
} else {
autogenTemplate(ctx, name, autogenPath,
options.HostTemplate, configs, ...)
}
}
// ...
}
The auto-generation uses sed to substitute placeholders in template XML
files:
var autogenTestConfig = pctx.StaticRule("autogenTestConfig", blueprint.RuleParams{
Command: "sed 's&{MODULE}&${name}&g;" +
"s&{EXTRA_CONFIGS}&'${extraConfigs}'&g;" +
"s&{EXTRA_TEST_RUNNER_CONFIGS}&'${extraTestRunnerConfigs}'&g;" +
"s&{OUTPUT_FILENAME}&'${outputFileName}'&g;" +
"s&{TEST_INSTALL_BASE}&'${testInstallBase}'&g' $template > $out",
// ...
})
55.5.9 Module Type Summary¶
graph TB
subgraph Java["Java / Kotlin"]
AT["android_test"]
JTH["java_test_host"]
ART["android_ravenwood_test"]
ROBO["android_robolectric_test"]
end
subgraph Native["C / C++"]
CT["cc_test"]
CTH["cc_test_host"]
CB["cc_benchmark"]
CF["cc_fuzz"]
end
subgraph Rust
RT["rust_test"]
RF["rust_fuzz"]
end
subgraph Python
PTH["python_test_host"]
PT["python_test"]
end
AT --> TF["TradeFederation"]
JTH --> TF
ART --> TF
ROBO --> TF
CT --> TF
CTH --> TF
CB --> TF
CF --> Fuzzer["Fuzz Engine"]
RT --> TF
RF --> Fuzzer
PTH --> TF
PT --> TF
55.5.10 Standalone Tests¶
The standalone_test property for cc_test enables self-contained test
packages that bundle their shared library dependencies:
// Install the test's dependencies into a folder named standalone-libs
// relative to the test's installation path.
Standalone_test *bool
When enabled, the build system:
- Collects all transitive shared library dependencies
- Installs them alongside the test binary
- Sets
ld-library-pathin the auto-generated config - Creates packaging specs for the standalone directory
From build/soong/cc/test.go:
if Bool(test.Properties.Standalone_test) {
packagingSpecsBuilder := depset.NewBuilder[android.PackagingSpec](
depset.TOPOLOGICAL)
ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
deps := android.OtherModuleProviderOrDefault(ctx, dep,
android.InstallFilesProvider)
packagingSpecsBuilder.Transitive(deps.TransitivePackagingSpecs)
})
for _, standaloneTestDep := range packagingSpecsBuilder.Build().ToList() {
if standaloneTestDep.SrcPath() == nil { continue }
if standaloneTestDep.SkipInstall() { continue }
if standaloneTestDep.Partition() == "data" { continue }
test.binaryDecorator.baseInstaller.installStandaloneTestDep(
ctx, standaloneTestDep)
}
}
And the TradeFed config gets the library path:
if Bool(options.StandaloneTest) {
options.TestRunnerOptions = append(options.TestRunnerOptions, Option{
Name: "ld-library-path",
Value: "{TEST_INSTALL_BASE}/" + name + "/" +
ctx.Arch().ArchType.String() + "/standalone-libs",
})
}
55.5.11 Benchmark Modules¶
The cc_benchmark module type builds performance benchmark binaries using
Google Benchmark:
func BenchmarkFactory() android.Module {
module := NewBenchmark(android.HostAndDeviceSupported)
module.testModule = true
return module.Init()
}
Benchmarks automatically link against libgoogle-benchmark:
func (benchmark *benchmarkDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps {
deps = benchmark.binaryDecorator.linkerDeps(ctx, deps)
deps.StaticLibs = append(deps.StaticLibs, "libgoogle-benchmark")
return deps
}
Benchmarks are installed to a separate directory:
benchmark.binaryDecorator.baseInstaller.dir = filepath.Join(
"benchmarktest", ctx.ModuleName())
benchmark.binaryDecorator.baseInstaller.dir64 = filepath.Join(
"benchmarktest64", ctx.ModuleName())
Example benchmark:
cc_benchmark {
name: "libutils_benchmark",
srcs: ["Looper_bench.cpp", "String8_bench.cpp"],
shared_libs: ["libutils"],
test_suites: ["device-tests"],
}
55.5.12 Test Config Templates¶
The build system uses template XML files for auto-generating TradeFed configs. Key templates referenced in the code:
| Template Variable | Usage |
|---|---|
${NativeTestConfigTemplate} |
Device cc_test |
${NativeHostTestConfigTemplate} |
Host cc_test |
${NativeBenchmarkTestConfigTemplate} |
cc_benchmark |
${InstrumentationTestConfigTemplate} |
android_test |
${RobolectricTestConfigTemplate} |
android_robolectric_test |
${RavenwoodTestConfigTemplate} |
android_ravenwood_test |
Templates contain placeholders that get substituted:
{MODULE}-- Module name{EXTRA_CONFIGS}-- Additional XML config elements{EXTRA_TEST_RUNNER_CONFIGS}-- Runner-specific options{OUTPUT_FILENAME}-- Output file name{TEST_INSTALL_BASE}-- Installation base directory
55.5.13 TestSuiteInfo Provider¶
All test modules set the TestSuiteInfoProvider so that the build system and
CI can discover test attributes:
ctx.SetTestSuiteInfo(android.TestSuiteInfo{
NameSuffix: c.SubName(),
TestSuites: test.InstallerProperties.Test_suites,
MainFile: file,
MainFileStem: file.Base(),
ConfigFile: test.testConfig,
ExtraConfigs: test.extraTestConfigs,
Data: test.data,
NeedsArchFolder: true,
PerTestcaseDirectory: Bool(test.Properties.Per_testcase_directory),
IsUnitTest: Bool(test.Properties.Test_options.Unit_test),
})
The IsUnitTest flag marks host tests as unit tests, which:
- Adds them to the
host-unit-testssuite - Enables faster execution paths in CI
- Allows filtering in atest with
--host
55.6 CTS -- Compatibility Test Suite¶
55.6.1 Overview¶
The Compatibility Test Suite (CTS) is the cornerstone of Android's ecosystem compatibility guarantees. Every device that ships with Google Play must pass CTS. CTS verifies that the public SDK APIs behave according to their documented contracts.
Source location: cts/
cts/
tests/ -- Device-side test modules (87 directories)
hostsidetests/ -- Host-side test modules (95 directories)
apps/ -- Test helper apps (CtsVerifier, etc.)
common/ -- Shared utilities
libs/ -- Shared libraries
tools/ -- CTS-specific tooling
suite/ -- Suite configuration
build/ -- Build configuration
55.6.2 Test Organization¶
CTS organizes tests by Android API area. Most CTS device-side test packages
live under cts/tests/tests/, and each directory there typically maps to a
framework package or subsystem:
| Directory | API Area |
|---|---|
cts/tests/tests/app/ |
Activity, Service, ContentProvider |
cts/tests/tests/net/ |
Networking APIs |
cts/tests/tests/media/ |
Media codecs, player, recorder |
cts/tests/tests/security/ |
Security/crypto APIs |
cts/tests/netlegacy22.permission/ |
Legacy permission model |
cts/tests/tests/widget/ |
UI widgets |
cts/tests/tests/graphics/ |
Graphics, Canvas, OpenGL |
cts/tests/camera/ |
Camera2 API |
cts/tests/tests/telecom/ |
Telephony/telecom |
cts/tests/accessibility/ |
Accessibility services |
Host-side tests under cts/hostsidetests/ test behaviors that require
host-level orchestration, such as:
| Directory | Purpose |
|---|---|
cts/hostsidetests/appsecurity/ |
App signing, permissions, isolation |
cts/hostsidetests/devicepolicy/ |
Device admin, managed profiles |
cts/hostsidetests/apex/ |
APEX module testing |
cts/hostsidetests/backup/ |
Backup and restore |
cts/hostsidetests/car/ |
Automotive features |
cts/hostsidetests/blobstore/ |
Blob store API |
55.6.3 CTS Module Structure¶
A typical CTS device test module:
cts/tests/net/
Android.bp -- Build rule (android_test)
AndroidManifest.xml -- Test APK manifest
AndroidTest.xml -- TradeFed configuration
src/ -- Test source code
res/ -- Test resources (if needed)
The build rule declares CTS suite membership:
android_test {
name: "CtsNetTestCases",
defaults: ["cts_defaults"],
srcs: ["src/**/*.java"],
test_suites: [
"cts",
"general-tests",
],
static_libs: [
"ctstestrunner-axt",
"compatibility-device-util-axt",
],
}
55.6.4 CtsVerifier¶
CtsVerifier (cts/apps/CtsVerifier/) is a special interactive test app that
verifies hardware-dependent behaviors that cannot be automated:
cts/apps/CtsVerifier/
AndroidManifest.xml
src/ -- Test activities
res/ -- UI resources
jni/ -- Native test helpers
assets/ -- Test data
CtsVerifier covers:
- Sensor accuracy (accelerometer, gyroscope)
- Audio routing and latency
- Camera image quality
- Bluetooth, NFC, Wi-Fi behavior
- USB connectivity
- Biometric enrollment
Operators manually perform each test using the CtsVerifier app and confirm pass/fail results.
55.6.5 Running CTS¶
# Full CTS run
cts-tradefed run cts
# Single module
cts-tradefed run cts --module CtsNetTestCases
# Single test
cts-tradefed run cts --module CtsNetTestCases \
--test android.net.cts.ConnectivityManagerTest
# With retry
cts-tradefed run retry --retry <session_id>
# Using atest
atest CtsNetTestCases
55.6.6 CTS Architecture¶
flowchart TB
subgraph Host["Host: cts-tradefed"]
Suite["CompatibilityTestSuite"]
Loader["SuiteModuleLoader"]
Sched["CommandScheduler"]
end
subgraph Device["Target Device"]
Runner["AndroidJUnitRunner"]
TestAPK["CTS Test APK"]
Framework["Android Framework"]
end
Sched --> Suite
Suite --> Loader
Loader --> |"load .config files"| Modules["Module Definitions"]
Modules --> |"for each module"| Prep["Target Preparers<br>(install APK)"]
Prep --> Runner
Runner --> TestAPK
TestAPK --> |"test API"| Framework
Runner --> |"results"| Results["Result XML"]
55.6.7 CTS Defaults¶
CTS tests use a shared cts_defaults to ensure consistent configuration:
java_defaults {
name: "cts_defaults",
platform_apis: true,
optimize: {
enabled: false,
},
static_libs: [
"ctstestrunner-axt",
"compatibility-device-util-axt",
"junit",
"truth",
],
test_suites: [
"cts",
"general-tests",
],
}
55.6.8 CTS Sharding Across Devices¶
For large CTS runs (10,000+ test cases), sharding is essential. CTS supports:
- Static sharding: Split modules into N equal shards
- Dynamic sharding: Use a pool-based approach for load balancing
- Module-level sharding: Each module runs on one device
- Test-level sharding: Individual test cases within a module split
# Shard across 4 devices
cts-tradefed run cts --shard-count 4
# Dynamic sharding with pool
cts-tradefed run cts --enable-token-sharding
55.6.9 CTS Result Structure¶
CTS produces structured results:
android-cts/results/
YYYY.MM.DD_HH.MM.SS/
test_result.xml -- JUnit XML results
test_result_failures.html -- Human-readable failures
compatibility_result.xsl -- XSL stylesheet
result.pb -- Protocol buffer results
invocation_summary.txt -- Summary
device_logcat*.txt -- Device logs
host_log*.txt -- Host logs
The test_result.xml is the canonical result file used for compliance
certification submission.
55.6.10 CTS Module Development Workflow¶
flowchart TB
Write["Write test code"] --> Build["Build: make CtsMyApiTestCases"]
Build --> Run["Run: atest CtsMyApiTestCases"]
Run --> Debug["Debug failures"]
Debug --> |"fix"| Write
Run --> |"pass"| Verify["Verify with cts-tradefed"]
Verify --> Submit["Submit to AOSP"]
Submit --> CI["CI runs full CTS"]
55.7 VTS -- Vendor Test Suite¶
55.7.1 Overview¶
The Vendor Test Suite (VTS) verifies the contract between the Android framework and vendor implementations across the Treble architecture boundary. While CTS tests the public SDK, VTS tests HAL implementations, the VNDK, and kernel interfaces.
Source locations:
55.7.2 Test Categories¶
VTS test cases are organized under test/vts-testcase/:
HAL Tests (test/vts-testcase/hal/):
automotive/-- Automotive HAL testsneuralnetworks/-- NNAPI HAL teststhermal/-- Thermal HAL teststreble/-- Treble compliance testsusb/-- USB HAL tests
Kernel Tests (test/vts-testcase/kernel/):
abi/-- Kernel ABI stabilityapi/-- Kernel API compliancebow/-- Block on write testingcheckpoint/-- Checkpoint supportencryption/-- Disk encryptionf2fs/-- F2FS filesystem testsfuse_bpf/-- FUSE BPF testsgki/-- Generic Kernel Image testsltp/-- Linux Test Project integrationvirtual_ab/-- Virtual A/B testingzram/-- ZRAM compression tests
VNDK Tests (test/vts-testcase/vndk/):
abi/-- VNDK ABI stabilitydependency/-- VNDK dependency verificationfiles/-- VNDK file list validationgolden/-- Golden image comparison
55.7.3 HAL Testing Methodology¶
VTS HAL tests verify that vendor HAL implementations conform to their HIDL/AIDL interface definitions. The test framework:
-
Discovers HAL instances on the device via
hwservicemanagerorservicemanager -
Opens a client connection to each HAL
- Exercises the interface methods with known inputs
- Validates outputs against the interface specification
sequenceDiagram
participant VTS as VTS Test
participant SM as ServiceManager
participant HAL as Vendor HAL
VTS->>SM: getService("android.hardware.thermal@2.0::IThermal")
SM-->>VTS: IThermal binder proxy
VTS->>HAL: getTemperatures(TemperatureType.SKIN)
HAL-->>VTS: Temperature[]
VTS->>VTS: assert temperatures within valid range
55.7.4 Treble Compliance¶
The Treble tests under test/vts-testcase/hal/treble/ verify the architectural
separation:
-
VINTF manifest validation: Verifies that the vendor manifest correctly declares all HALs
-
Framework-vendor separation: Ensures no unauthorized cross-boundary dependencies
-
VNDK usage: Validates that vendor code only uses VNDK libraries
55.7.5 Kernel Tests¶
VTS kernel tests (test/vts-testcase/kernel/) verify kernel behavior:
- GKI tests: Validate Generic Kernel Image compliance
- ABI tests: Ensure kernel ABI stability for module loading
- LTP integration: Runs Linux Test Project tests on Android
- Syscall tests: Verify syscall behavior matches requirements
55.7.6 Running VTS¶
# Full VTS run
vts-tradefed run vts
# Single module
vts-tradefed run vts --module VtsHalThermalV2_0TargetTest
# Using atest
atest VtsHalThermalV2_0TargetTest
# Kernel test
atest vts_kernel_gki_test
55.7.7 VTS vs CTS: The Treble Boundary¶
flowchart TB
subgraph Framework["Framework (CTS Domain)"]
SDK["Public SDK APIs"]
SystemAPI["System APIs"]
HiddenAPI["Hidden APIs"]
end
subgraph Boundary["Treble Boundary"]
AIDL["AIDL HAL Interfaces"]
HIDL["HIDL HAL Interfaces"]
VNDK["VNDK Libraries"]
end
subgraph Vendor["Vendor (VTS Domain)"]
HALImpl["HAL Implementations"]
Kernel["Kernel Modules"]
Firmware["Firmware"]
end
CTS_Label["CTS Tests"]:::suite --> SDK
CTS_Label --> SystemAPI
VTS_Label["VTS Tests"]:::suite --> AIDL
VTS_Label --> HIDL
VTS_Label --> VNDK
VTS_Label --> HALImpl
VTS_Label --> Kernel
classDef suite fill:#e3f2fd,stroke:#1565c0
CTS tests the framework side of the boundary -- APIs that apps use. VTS tests the vendor side -- HAL implementations, VNDK compliance, and kernel behavior. Together they enforce the Treble contract that allows framework and vendor components to be updated independently.
55.7.8 Running VTS HAL Tests¶
A typical VTS HAL test invocation:
# Discover available HAL tests
vts-tradefed list modules | grep -i thermal
# Run a specific HAL test
vts-tradefed run vts --module VtsHalThermalTargetTest
# Run all HAL tests for a specific HAL
vts-tradefed run vts --include-filter 'VtsHal*Thermal*'
VTS HAL tests use the GTest runner for C++ tests and HostTest for
Python-based tests. The test binaries are compiled against the HAL interface
headers and linked against the HAL client libraries.
55.7.9 VINTF Manifest Testing¶
A critical VTS test verifies the VINTF (Vendor Interface) manifest. This manifest declares which HALs a device provides:
<manifest version="2.0" type="device">
<hal format="aidl">
<name>android.hardware.thermal</name>
<version>1</version>
<fqname>IThermal/default</fqname>
</hal>
</manifest>
VTS tests verify:
- Every declared HAL is actually available at runtime
- No undeclared HALs are present (no hidden implementations)
- Version numbers match the interface definitions
- Framework compatibility matrix is satisfied
55.8 Ravenwood -- Host-Side Framework Testing¶
55.8.1 Overview¶
Ravenwood is Android's solution for running framework tests on the host JVM without requiring a device or emulator. It provides a lightweight environment where Android framework classes execute directly on a JDK 21+ host JVM, dramatically reducing test execution time from minutes to seconds.
Source: build/soong/java/ravenwood.go (539 lines)
55.8.2 Module Type: android_ravenwood_test¶
The android_ravenwood_test module type is registered in ravenwood.go:
func RegisterRavenwoodBuildComponents(ctx android.RegistrationContext) {
ctx.RegisterModuleType("android_ravenwood_test", ravenwoodTestFactory)
ctx.RegisterModuleType("android_ravenwood_libgroup", ravenwoodLibgroupFactory)
}
The factory function sets up default suite membership:
func ravenwoodTestFactory() android.Module {
module := &ravenwoodTest{}
module.addHostAndDeviceProperties()
module.AddProperties(&module.aaptProperties,
&module.testProperties, &module.ravenwoodTestProperties)
module.Module.dexpreopter.isTest = true
module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true)
module.testProperties.Test_suites = []string{
"general-tests",
"ravenwood-tests",
}
// ...
InitJavaModule(module, android.DeviceSupported)
return module
}
55.8.3 Architecture¶
Ravenwood tests declare android.DeviceSupported but are forced to the host
OS at generation time:
func (r *ravenwoodTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
r.forceOSType = ctx.Config().BuildOS
r.forceArchType = ctx.Config().BuildArch
// ...
}
This means the test is compiled as a device JAR (using Android SDK classes) but executed on the host JVM. The Ravenwood runtime provides stub/shadow implementations of Android framework classes that are not available on the host.
flowchart LR
subgraph Build["Build Time"]
Source["Test Source<br>(uses android.* APIs)"]
Source --> Compile["Compile as device JAR"]
end
subgraph Runtime["Host JVM Runtime"]
JAR["Test JAR"]
RW["ravenwood-runtime<br>(framework stubs)"]
Utils["ravenwood-utils"]
JNI["JNI libs<br>(host-built)"]
JAR --> Runner["IsolatedHostTest"]
RW --> Runner
Utils --> Runner
JNI --> Runner
end
Compile --> JAR
55.8.4 Ravenwood Properties¶
type ravenwoodTestProperties struct {
// Specify the name of the Instrumentation subclass to use.
Instrumentation_class *string
// Specify the package name of the test target apk.
Target_package_name *string
// Specify another android_app module here to copy it to the
// test directory, so that the ravenwood test can access it.
Target_resource_apk *string
// Specify whether to build resources.
Build_resources *bool
}
55.8.5 Runtime Components¶
Ravenwood depends on two library groups:
- ravenwood-utils: Utility libraries needed at compile time
- ravenwood-runtime: Runtime environment providing framework class implementations
Both are android_ravenwood_libgroup modules that install JARs and JNI
libraries alongside the test:
func (r *ravenwoodLibgroup) GenerateAndroidBuildActions(ctx android.ModuleContext) {
r.forceOSType = ctx.Config().BuildOS
r.forceArchType = ctx.Config().BuildArch
// Install JAR libraries
for _, lib := range r.ravenwoodLibgroupProperties.Libs {
libJar := android.OutputFileForModule(ctx, libModule, "")
ctx.InstallFile(installPath, libJar.Base(), libJar)
}
// Install JNI libraries
for _, jniLib := range jniLibs {
install(soInstallPath, jniLib.path)
}
// Install data files (e.g., framework-res.apk)
// Install font files
// Install aconfig flag storage files
// ...
}
The runtime also installs aconfig flag storage files for feature flag testing:
if r.Name() == ravenwoodRuntimeName {
// Binary proto file and the text proto.
install(installPath.Join(ctx, "aconfig/metadata/aconfig/etc"),
aadi.ParsedFlagsFile, aadi.TextProtoFlagsFile)
// The "new" storage files.
install(installPath.Join(ctx, "aconfig/metadata/aconfig/maps"),
aadi.StoragePackageMap, aadi.StorageFlagMap)
install(installPath.Join(ctx, "aconfig/metadata/aconfig/boot"),
aadi.StorageFlagVal, aadi.StorageFlagInfo)
}
55.8.6 Ravenizer¶
Ravenwood tests go through a "Ravenizer" bytecode transformation step:
The Ravenizer rewrites bytecode to redirect framework calls to Ravenwood's host-compatible implementations, similar to how Robolectric's shadow system works but integrated more tightly with the platform build.
55.8.7 Manifest Properties¶
Ravenwood generates a properties file for each test module:
ctx.Build(pctx, android.BuildParams{
Rule: genManifestProperties,
Description: "genManifestProperties",
Output: propertiesOutputPath,
Args: map[string]string{
"targetSdkVersionInt": strconv.Itoa(targetSdkVersionInt),
"targetSdkVersionRaw": targetSdkVersion,
"packageName": packageName,
"targetPackageName": targetPackageName,
"instrumentationClass": instClassName,
"moduleName": ctx.ModuleName(),
"resourceApk": resApkName,
"targetResourceApk": targetResApkName,
},
})
55.8.8 Example Ravenwood Test¶
android_ravenwood_test {
name: "CtsUtilTestCasesRavenwood",
srcs: ["src/**/*.java"],
static_libs: [
"androidx.test.rules",
"ravenwood-junit",
],
sdk_version: "test_current",
target_sdk_version: "35",
build_resources: true,
package_name: "android.util.cts.ravenwood",
}
55.8.9 Ravenwood in TEST_MAPPING¶
Ravenwood tests appear in the ravenwood-presubmit group:
{
"ravenwood-presubmit": [
{
"name": "CtsUtilTestCasesRavenwood",
"host": true,
"file_patterns": ["[Rr]avenwood"]
}
]
}
55.8.10 Ravenwood Test Lifecycle¶
The Ravenwood test lifecycle through TradeFed:
sequenceDiagram
participant TF as TradeFederation
participant IHT as IsolatedHostTest
participant CL as Isolated ClassLoader
participant Test as Test Class
participant RW as Ravenwood Runtime
TF->>IHT: run(testInfo, listener)
IHT->>CL: create isolated classloader
CL->>RW: load ravenwood-runtime JARs
CL->>RW: load ravenwood-utils JARs
CL->>RW: load JNI libraries
CL->>Test: load test JAR
IHT->>RW: initialize (load ravenwood.properties)
RW->>RW: setup Android framework stubs
RW->>RW: load aconfig flags
loop For each test method
IHT->>Test: invoke @Test method
Test->>RW: Android API calls
RW-->>Test: results (from host impl)
Test-->>IHT: pass/fail
end
IHT->>RW: teardown
IHT-->>TF: results
55.8.11 Resource Support¶
Ravenwood supports Android resources via the build_resources property:
if proptools.Bool(r.ravenwoodTestProperties.Build_resources) {
r.aaptBuildActions(ctx)
resourceApk = r.aapt.exportPackage
}
The aaptBuildActions method adapts the standard Android app resource
processing pipeline for Ravenwood:
func (r *ravenwoodTest) aaptBuildActions(ctx android.ModuleContext) {
usePlatformAPI := proptools.Bool(r.Module.deviceProperties.Platform_apis)
r.aapt.usesNonSdkApis = usePlatformAPI
aconfigTextFilePaths := getAconfigFilePaths(ctx)
r.aapt.buildActions(ctx, aaptBuildActionOptions{
sdkContext: android.SdkContext(r),
enforceDefaultTargetSdkVersion: true,
forceNonFinalResourceIDs: true,
aconfigTextFiles: aconfigTextFilePaths,
usesLibrary: &r.usesLibrary,
})
}
Resource APKs are installed alongside the test:
if resourceApk != nil {
installResApk := ctx.InstallFile(resApkInstallPath,
"ravenwood-res.apk", resourceApk)
installDeps = append(installDeps, installResApk)
resApkName = "ravenwood-res.apk"
}
55.8.12 When to Use Ravenwood¶
Ravenwood is ideal for:
- Testing
android.os.*utilities (Bundle, Parcel, Handler, etc.) - Testing
android.util.*data structures (SparseArray, LruCache, etc.) - Testing
android.content.*basic classes - Testing framework services that can run without hardware
- Testing code that uses Android feature flags (aconfig)
Ravenwood is NOT suitable for:
- Tests requiring real UI rendering
- Tests needing real hardware (camera, sensors)
- Tests involving Binder IPC to system services
- Tests that need a full Activity lifecycle
55.9 Robolectric¶
55.9.1 Overview¶
Robolectric is the established open-source framework for running Android unit tests on a host JVM without an emulator. It provides "shadow" implementations of Android framework classes, intercepting calls at the bytecode level.
Source locations:
external/robolectric/ -- Upstream Robolectric source
build/soong/java/robolectric.go -- Build system integration (444 lines)
55.9.2 Module Type: android_robolectric_test¶
Registered in build/soong/java/robolectric.go:
func RegisterRobolectricBuildComponents(ctx android.RegistrationContext) {
ctx.RegisterModuleType("android_robolectric_test", RobolectricTestFactory)
ctx.RegisterModuleType("android_robolectric_runtimes", robolectricRuntimesFactory)
}
The factory function:
func RobolectricTestFactory() android.Module {
module := &robolectricTest{}
module.addHostProperties()
module.AddProperties(
&module.Module.deviceProperties,
&module.robolectricProperties,
&module.testProperties)
module.Module.dexpreopter.isTest = true
module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true)
module.Module.sourceProperties.Test_only = proptools.BoolPtr(true)
module.Module.sourceProperties.Top_level_test_target = true
module.testProperties.Test_suites = []string{"robolectric-tests"}
InitJavaModule(module, android.DeviceSupported)
return module
}
55.9.3 Properties¶
type robolectricProperties struct {
// The name of the android_app module that the tests will run against.
Instrumentation_for *string
// Additional libraries for which coverage data should be generated
Coverage_libs []string
Test_options struct {
// Timeout in seconds when running the tests.
Timeout *int64
// Number of shards to use when running the tests.
Shards *int64
}
// Use /external/robolectric rather than /external/robolectric-shadows
Upstream *bool
// Use strict mode to limit access of Robolectric API directly.
Strict_mode *bool
Jni_libs proptools.Configurable[[]string]
}
55.9.4 Default Dependencies¶
Robolectric tests automatically get these libraries:
var robolectricDefaultLibs = []string{
"mockito-robolectric-prebuilt",
"truth",
"junitxml",
}
const robolectricCurrentLib = "Robolectric_all-target"
const clearcutJunitLib = "ClearcutJunitListenerAar"
55.9.5 Strict Mode¶
Robolectric strict mode (strict_mode: true, the default) limits direct access
to Robolectric APIs, encouraging tests to use standard Android APIs:
func (r *robolectricTest) DepsMutator(ctx android.BottomUpMutatorContext) {
// ...
if proptools.BoolDefault(r.robolectricProperties.Strict_mode, true) {
ctx.AddVariationDependencies(nil, roboRuntimeOnlyDepTag, robolectricCurrentLib)
} else {
ctx.AddVariationDependencies(nil, staticLibTag, robolectricCurrentLib)
}
// ...
}
In strict mode, the Robolectric library is added as a runtime-only dependency (not compile-time), preventing test code from directly calling Robolectric shadow APIs.
55.9.6 Test Config Generation¶
Robolectric tests get a special config template:
r.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{
// ...
DeviceTemplate: "${RobolectricTestConfigTemplate}",
HostTemplate: "${RobolectricTestConfigTemplate}",
})
Additional JVM flags are injected:
var extraTestRunnerOptions []tradefed.Option
extraTestRunnerOptions = append(extraTestRunnerOptions,
tradefed.Option{Name: "java-flags", Value: "-Drobolectric=true"})
if proptools.BoolDefault(r.robolectricProperties.Strict_mode, true) {
extraTestRunnerOptions = append(extraTestRunnerOptions,
tradefed.Option{Name: "java-flags", Value: "-Drobolectric.strict.mode=true"})
}
55.9.7 Runtimes¶
The android_robolectric_runtimes module provides pre-built Android framework
JARs for each SDK level that Robolectric uses to simulate different API
versions:
func (r *robolectricRuntimes) GenerateAndroidBuildActions(ctx android.ModuleContext) {
files := android.PathsForModuleSrc(ctx, r.props.Jars)
androidAllDir := android.PathForModuleInstall(ctx, "android-all")
for _, from := range files {
installedRuntime := ctx.InstallFile(androidAllDir, from.Base(), from)
r.runtimes = append(r.runtimes, installedRuntime)
}
// Build from source for the "TREE" (current) version
if !ctx.Config().AlwaysUsePrebuiltSdks() && r.props.Lib != nil {
runtimeName := "android-all-current-robolectric-r0.jar"
installedRuntime := ctx.InstallFile(androidAllDir, runtimeName,
runtimeFromSourceJar)
r.runtimes = append(r.runtimes, installedRuntime)
}
}
55.9.8 Shadow System¶
Robolectric's shadows live under external/robolectric/shadows/:
external/robolectric/shadows/
framework/ -- Shadows for android.* framework classes
httpclient/ -- Apache HttpClient shadows
multidex/ -- Multidex shadows
playservices/ -- Google Play Services shadows
versioning/ -- SDK version handling
Shadows intercept method calls using bytecode instrumentation. For example,
a shadow of android.content.Context provides host-compatible implementations
of getSharedPreferences(), getContentResolver(), etc.
55.9.9 Example Robolectric Test¶
android_robolectric_test {
name: "SettingsRoboTests",
srcs: ["tests/robotests/src/**/*.java"],
instrumentation_for: "Settings",
static_libs: [
"Settings-testutils",
"testng",
],
java_resource_dirs: ["tests/robotests/config"],
}
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowUserManager.class})
public class SettingsActivityTest {
@Test
public void onCreate_shouldNotCrash() {
ActivityController<SettingsActivity> controller =
Robolectric.buildActivity(SettingsActivity.class);
controller.create();
assertThat(controller.get().isFinishing()).isFalse();
}
}
55.9.10 Robolectric vs Ravenwood¶
| Aspect | Robolectric | Ravenwood |
|---|---|---|
| Origin | Open source (GitHub) | Google internal, AOSP |
| Mechanism | Shadow classes (bytecode rewriting) | Actual framework code + Ravenizer |
| Fidelity | Approximate (shadows may drift) | Higher (real framework classes) |
| Framework coverage | Broad but shallow | Narrower but deeper |
| Build module | android_robolectric_test |
android_ravenwood_test |
| Suite | robolectric-tests |
ravenwood-tests |
| JDK requirement | JDK 11+ | JDK 21+ |
55.9.11 Robolectric Test Config Properties¶
The generateSameDirRoboTestConfigJar function creates a configuration JAR
that tells Robolectric where to find the app's manifest and resources:
func generateSameDirRoboTestConfigJar(ctx android.ModuleContext,
outputFile android.ModuleOutPath) {
rule := android.NewRuleBuilder(pctx, ctx)
outputDir := outputFile.InSameDir(ctx)
configFile := outputDir.Join(ctx,
"com/android/tools/test_config.properties")
rule.Command().Text("(").
Textf(`echo "android_merged_manifest=%s-AndroidManifest.xml" &&`,
ctx.ModuleName()).
Textf(`echo "android_resource_apk=%s.apk"`, ctx.ModuleName()).
Text(") >>").Output(configFile)
rule.Command().
BuiltTool("soong_zip").
FlagWithArg("-C ", outputDir.String()).
FlagWithInput("-f ", configFile).
FlagWithOutput("-o ", outputFile)
rule.Build("generate_test_config_samedir",
"generate test_config.properties")
}
This config JAR is merged with the test JAR and instrumented app JAR so that Robolectric can find resources at runtime.
55.9.12 Coverage Integration¶
Robolectric tests can collect JaCoCo coverage for additional libraries:
type robolectricProperties struct {
// Additional libraries for which coverage data should be generated
Coverage_libs []string
// ...
}
The coverage libraries are added as dependencies:
55.9.13 Robolectric Architecture Diagram¶
flowchart TB
subgraph HostJVM["Host JVM"]
TestCode["Test Code<br>(@RunWith RobolectricTestRunner)"]
Shadows["Shadow Classes<br>(intercept Android calls)"]
AppCode["App Code Under Test"]
AndroidAll["android-all-XX.jar<br>(SDK stubs)"]
Manifest["AndroidManifest.xml"]
Resources["Resources APK"]
end
TestCode --> |"calls"| AppCode
AppCode --> |"calls android.*"| Shadows
Shadows --> |"simulates"| AndroidAll
TestCode --> |"reads"| Manifest
TestCode --> |"loads"| Resources
subgraph Output["Output"]
JUnit["JUnit XML Results"]
Coverage["JaCoCo Coverage"]
end
TestCode --> JUnit
TestCode --> Coverage
55.10 Native Testing (GoogleTest)¶
55.10.1 GoogleTest in AOSP¶
AOSP includes Google Test (gtest) and Google Mock (gmock) as the standard native C/C++ testing framework.
Source: external/googletest/
external/googletest/
googletest/ -- Google Test framework
include/ -- Public headers (gtest/gtest.h)
src/ -- Implementation
googlemock/ -- Google Mock framework
include/ -- Public headers (gmock/gmock.h)
src/ -- Implementation
Android.bp -- Build rules
55.10.2 How cc_test Uses GoogleTest¶
When a cc_test module has gtest: true (the default), the build system
automatically:
- Links
libgtest_mainandlibgtestas static libraries - Adds compiler flags:
-DGTEST_HAS_STD_STRING, platform-specific OS define - Uses a TradeFed
GTestrunner in the auto-generated XML config
From build/soong/cc/test.go:
55.10.3 Writing a GoogleTest¶
// my_module_test.cpp
#include <gtest/gtest.h>
#include "my_module.h"
class MyModuleTest : public ::testing::Test {
protected:
void SetUp() override {
module_ = CreateModule();
}
void TearDown() override {
DestroyModule(module_);
}
Module* module_;
};
TEST_F(MyModuleTest, InitializeSucceeds) {
EXPECT_EQ(module_->Initialize(), 0);
}
TEST_F(MyModuleTest, ProcessValidInput) {
int result = module_->Process("valid_input");
ASSERT_GE(result, 0);
EXPECT_EQ(result, 42);
}
TEST(MyModuleStandaloneTest, NullInput) {
EXPECT_DEATH(Process(nullptr), "");
}
55.10.4 Build Rule¶
cc_test {
name: "my_module_test",
srcs: ["my_module_test.cpp"],
shared_libs: ["libmy_module"],
test_suites: ["device-tests"],
// gtest: true, // implicit default
}
55.10.5 GoogleMock¶
Google Mock (gmock) is included alongside gtest for creating mock objects:
#include <gmock/gmock.h>
#include <gtest/gtest.h>
class MockDatabase : public IDatabase {
public:
MOCK_METHOD(bool, Connect, (const std::string& host), (override));
MOCK_METHOD(Result, Query, (const std::string& sql), (override));
};
TEST(ServiceTest, HandlesConnectionFailure) {
MockDatabase db;
EXPECT_CALL(db, Connect(testing::_))
.WillOnce(testing::Return(false));
Service service(&db);
EXPECT_EQ(service.Start(), Status::CONNECTION_FAILED);
}
55.10.6 Test Installation and Execution¶
Native tests are installed to /data/local/tmp/nativetest/ (32-bit) or
/data/local/tmp/nativetest64/ (64-bit) on the device:
func NewTestInstaller() *baseInstaller {
return NewBaseInstaller("nativetest", "nativetest64", InstallInData)
}
For vendor tests:
func getTestInstallBase(useVendor bool) string {
testInstallBase := "/data/local/tmp"
if useVendor {
testInstallBase = "/data/local/tests/vendor"
}
return testInstallBase
}
55.10.7 GTest Properties in cc_test¶
The TestOptions struct provides TradeFed-level configuration:
type TestOptions struct {
android.CommonTestOptions
Run_test_as *string
Test_suite_tag []string
Extra_test_configs []string `android:"path,arch_variant"`
Min_shipping_api_level *int64
Vsr_min_shipping_api_level *int64
Min_vndk_version *int64
Test_runner_options []tradefed.Option
}
These translate to TradeFed module controllers for API-level gating:
if properties.Test_options.Min_shipping_api_level != nil {
var options []tradefed.Option
options = append(options, tradefed.Option{
Name: "min-api-level",
Value: strconv.FormatInt(
int64(*properties.Test_options.Min_shipping_api_level), 10)})
configs = append(configs, tradefed.Object{
"module_controller",
"com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController",
options})
}
55.10.8 Isolated GTest Runner¶
The isolated gtest runner runs each test in a separate process for better isolation:
func (test *testDecorator) isolated(ctx android.BaseModuleContext) bool {
return BoolDefault(test.LinkerProperties.Isolated, false) && !ctx.Windows()
}
When isolated:
if test.isolated(ctx) {
deps.StaticLibs = append(deps.StaticLibs, "libgtest_isolated_main")
deps.SharedLibs = append(deps.SharedLibs, "liblog")
}
And the TradeFed config marks it as non-shardable:
55.10.9 GoogleTest Parameterized Tests¶
GoogleTest supports parameterized tests, commonly used for testing multiple input combinations:
class ParseIntTest : public ::testing::TestWithParam<
std::pair<std::string, int>> {};
TEST_P(ParseIntTest, ParsesCorrectly) {
auto [input, expected] = GetParam();
EXPECT_EQ(ParseInt(input), expected);
}
INSTANTIATE_TEST_SUITE_P(
IntegerParsing, ParseIntTest,
::testing::Values(
std::make_pair("0", 0),
std::make_pair("42", 42),
std::make_pair("-1", -1),
std::make_pair("2147483647", INT_MAX)
));
55.10.10 Death Tests¶
GoogleTest's death test macros verify that code correctly terminates on invalid input:
TEST(SecurityTest, NullPointerCausesAbort) {
ASSERT_DEATH(ProcessUntrustedInput(nullptr, 0),
".*CHECK.*input != nullptr.*");
}
TEST(SecurityTest, BufferOverflowDetected) {
char buf[10];
EXPECT_DEATH(memcpy(buf, large_input, 1000), "");
}
These are particularly important for Android's security-critical native code.
55.10.11 Test Data and Fixtures¶
The data property in cc_test installs additional files alongside the test:
cc_test {
name: "media_codec_test",
srcs: ["media_codec_test.cpp"],
data: [
"testdata/video.mp4",
"testdata/audio.aac",
],
test_suites: ["device-tests"],
}
Data files are installed to the same directory as the test binary and can be accessed via relative paths:
TEST_F(MediaCodecTest, DecodeVideo) {
std::string path = GetTestDataPath() + "/video.mp4";
auto codec = CreateCodec(path);
ASSERT_NE(codec, nullptr);
EXPECT_EQ(codec->Decode(), OK);
}
55.10.12 Native Test Execution on Device¶
When TradeFed runs a native test on device, the flow is:
sequenceDiagram
participant TF as TradeFederation
participant ADB as adb
participant Device
participant GTest as GTest Binary
TF->>ADB: push test binary to /data/local/tmp/nativetest64/
TF->>ADB: push data files
TF->>ADB: chmod +x test binary
TF->>ADB: shell /data/local/tmp/nativetest64/test_name/test_name
Device->>GTest: execute
GTest->>GTest: discover test cases
loop For each test
GTest->>GTest: SetUp()
GTest->>GTest: TEST_F body
GTest->>GTest: TearDown()
end
GTest-->>TF: XML output
TF->>TF: parse results
TF->>ADB: cleanup
55.11 UI Testing¶
55.11.1 Overview¶
Android provides several frameworks for testing user interfaces, each targeting a different abstraction level and use case.
55.11.2 Espresso¶
Espresso is Google's recommended framework for within-app UI testing. It provides a fluent API for finding views, performing actions, and asserting states.
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class LoginActivityTest {
@Rule
public ActivityScenarioRule<LoginActivity> activityRule =
new ActivityScenarioRule<>(LoginActivity.class);
@Test
public void loginWithValidCredentials() {
onView(withId(R.id.username))
.perform(typeText("user@example.com"));
onView(withId(R.id.password))
.perform(typeText("password123"));
onView(withId(R.id.login_button))
.perform(click());
onView(withId(R.id.welcome_text))
.check(matches(withText("Welcome!")));
}
}
Key characteristics:
- Synchronizes with the UI thread automatically
- Waits for idle before performing actions
- Runs in the same process as the app under test
- Part of AndroidX Test libraries
55.11.3 UIAutomator¶
UIAutomator enables cross-app UI testing. Unlike Espresso, it can interact with any visible UI element, including system UI, notifications, and other apps.
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Until;
@RunWith(AndroidJUnit4.class)
public class SystemUITest {
private UiDevice device;
@Before
public void setUp() {
device = UiDevice.getInstance(
InstrumentationRegistry.getInstrumentation());
}
@Test
public void openNotificationShade() {
device.openNotification();
device.wait(Until.hasObject(By.pkg("com.android.systemui")), 5000);
UiObject2 clearAll = device.findObject(
By.text("Clear all"));
assertThat(clearAll).isNotNull();
}
}
AOSP provides UIAutomator helpers in:
55.11.4 TAPL (Test Automation Platform Library)¶
TAPL provides high-level abstractions for testing system UI components like the Launcher, SystemUI, and Settings. It lives in:
platform_testing/libraries/systemui-tapl/ -- SystemUI TAPL
platform_testing/libraries/tapl-common/ -- Common TAPL utilities
TAPL creates page objects for system components:
// Using Launcher TAPL
LauncherInstrumentation launcher = new LauncherInstrumentation();
Workspace workspace = launcher.getWorkspace();
AllApps allApps = workspace.switchToAllApps();
AppIcon calculator = allApps.getAppIcon("Calculator");
calculator.launch();
The advantage of TAPL over raw UIAutomator is that it encapsulates the UI structure of system components, making tests more maintainable when the UI changes.
55.11.5 Flicker Testing¶
The Flicker framework detects visual regressions in window transitions. It captures window manager and surface flinger traces during transitions and validates invariants.
Source: platform_testing/libraries/flicker/
platform_testing/libraries/flicker/
Android.bp
src/ -- Flicker framework source
test/ -- Framework self-tests
utils/ -- Trace processing utilities
appHelpers/ -- App helper classes
collector/ -- Data collection
Flicker tests verify properties like:
- No flickering (rapid visibility changes) during transitions
- Correct layer ordering
- No unexpected blank frames
- Proper window animations
@RunWith(FlickerTestRunner.class)
public class OpenAppFromLauncherTest {
@FlickerBuilderProvider
public static FlickerBuilder buildFlicker(
FlickerTestParameter testSpec) {
return new FlickerBuilder(testSpec)
.withTransition(() -> {
testSpec.getDevice().launchApp("com.example.app");
})
.withAssertion(new WindowManagerTrace.Assertion(
"appWindowIsVisible") {
@Override
public void invoke(WindowManagerTrace trace) {
trace.visibleWindowsShownMoreThanOneConsecutiveEntry(
"com.example.app");
}
});
}
}
55.11.6 Screenshot Testing¶
Screenshot testing captures rendered UI and compares it against golden images to detect visual regressions.
Source: platform_testing/libraries/screenshot/
platform_testing/libraries/screenshot/
Android.bp
src/ -- Screenshot capture and comparison
deviceless/ -- Host-side screenshot tests
proto/ -- Protobuf definitions
scripts/ -- Helper scripts
update_goldens.py -- Golden image update tool
utils/ -- Utility functions
The workflow:
- Test renders a UI component
- Screenshot framework captures the rendered bitmap
- Bitmap is compared against a golden image
- Pixel-level differences are reported
@Test
public void testButtonAppearance() {
View button = createTestButton();
ScreenshotTestRule.assertScreenshot(
"button_default_state",
button,
/* maxPixelDifference= */ 0.01f
);
}
Golden images are updated with update_goldens.py when intentional visual
changes occur.
55.11.7 UI Testing Framework Comparison¶
graph LR
subgraph Scope["Test Scope"]
InApp["Within App<br>Same process"]
CrossApp["Cross App<br>System-level"]
Transition["Transitions<br>Window changes"]
Visual["Visual<br>Pixel comparison"]
end
subgraph Framework["Frameworks"]
Espresso["Espresso"]
UIA["UIAutomator"]
TAPL_F["TAPL"]
Flicker_F["Flicker"]
SS["Screenshot Testing"]
end
InApp --- Espresso
CrossApp --- UIA
CrossApp --- TAPL_F
Transition --- Flicker_F
Visual --- SS
| Framework | Scope | Speed | Reliability | Use Case |
|---|---|---|---|---|
| Espresso | In-app | Fast | High | Unit-level UI tests |
| UIAutomator | Cross-app | Medium | Medium | System integration |
| TAPL | System UI | Medium | High | Launcher, SystemUI |
| Flicker | Transitions | Slow | Medium | Animation quality |
| Screenshot | Visual | Medium | High | Design regression |
55.11.8 Espresso Idling Resources¶
Espresso's key advantage is synchronization with the UI thread. For asynchronous operations, Espresso uses idling resources:
public class NetworkIdlingResource implements IdlingResource {
private ResourceCallback callback;
private boolean isIdle = true;
@Override
public String getName() { return "NetworkIdling"; }
@Override
public boolean isIdleNow() { return isIdle; }
@Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
this.callback = callback;
}
public void setIdle(boolean idle) {
isIdle = idle;
if (idle && callback != null) {
callback.onTransitionToIdle();
}
}
}
// In test:
@Before
public void setUp() {
IdlingRegistry.getInstance().register(networkIdlingResource);
}
@After
public void tearDown() {
IdlingRegistry.getInstance().unregister(networkIdlingResource);
}
55.11.9 Flicker Test Assertions¶
Flicker tests define assertions on WindowManager and SurfaceFlinger traces:
// Common Flicker assertions:
// 1. No flickering (visibility does not change rapidly)
flicker.assertWm { wmTrace ->
wmTrace.visibleWindowsShownMoreThanOneConsecutiveEntry(componentName)
}
// 2. App window becomes visible
flicker.assertWmEnd { wmState ->
wmState.containsAppWindow(componentName)
}
// 3. No blank layers
flicker.assertLayers { layerTrace ->
layerTrace.visibleLayersShownMoreThanOneConsecutiveEntry()
}
// 4. Correct layer ordering
flicker.assertLayersEnd { layerState ->
layerState.isAbove(appLayer, wallpaperLayer)
}
55.12 Mocking Frameworks¶
55.12.1 Mockito¶
Mockito is the primary mocking framework used throughout AOSP for Java tests.
Source: external/mockito/
Mockito provides the familiar mock(), when(), verify() API:
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class PackageManagerTest {
@Mock
private PackageManager mockPm;
@Test
public void getInstalledPackages_returnsExpected() {
List<PackageInfo> packages = List.of(new PackageInfo());
when(mockPm.getInstalledPackages(anyInt())).thenReturn(packages);
assertEquals(1, mockPm.getInstalledPackages(0).size());
verify(mockPm).getInstalledPackages(0);
}
}
Common Mockito dependencies in Android builds:
mockito-target-minus-junit4-- For device testsmockito-robolectric-prebuilt-- For Robolectric testsmockito-target-extended-minus-junit4-- Extended mocking with inline support
55.12.2 Mockito-Kotlin¶
For Kotlin test code, Mockito-Kotlin provides idiomatic extensions:
import org.mockito.kotlin.*
@Test
fun `test service binding`() {
val mockContext: Context = mock()
val mockConnection: ServiceConnection = mock()
whenever(mockContext.bindService(any(), any(), anyInt()))
.thenReturn(true)
val result = ServiceBinder(mockContext).bind(mockConnection)
assertTrue(result)
verify(mockContext).bindService(any(), eq(mockConnection), eq(BIND_AUTO_CREATE))
}
55.12.3 Dexmaker¶
Dexmaker enables runtime mock generation on Android's ART runtime, where standard Java byte-code manipulation libraries do not work.
Source: external/dexmaker/
external/dexmaker/
dexmaker/ -- Core DEX generation
dexmaker-mockito/ -- Mockito adapter
dexmaker-mockito-inline/ -- Inline mocking (final classes)
dexmaker-mockito-inline-extended/ -- Extended inline mocking
dexmaker-mockito-inline-tests/ -- Tests
dexmaker-mockito-inline-extended-tests/ -- Extended tests
dexmaker-mockito-inline-dispatcher/ -- Dispatch mechanism
Dexmaker solves a fundamental Android challenge: the Dalvik/ART runtime cannot use cglib or ByteBuddy (the standard JVM mock generation libraries) because they generate JVM bytecode, not DEX bytecode. Dexmaker generates DEX files at runtime for mock classes.
The inline variant (dexmaker-mockito-inline) enables mocking of final classes
and methods, which is essential for Android framework classes that are
frequently declared final.
55.12.4 JUnit Integration¶
AOSP includes both JUnit 4 and JUnit 5 (jupiter). Most platform tests use JUnit 4 with the AndroidJUnit4 runner:
@RunWith(AndroidJUnit4.class)
@SmallTest
public class BundleTest {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Before
public void setUp() {
// ...
}
@Test
public void testBasicTypes() {
Bundle bundle = new Bundle();
bundle.putInt("key", 42);
assertEquals(42, bundle.getInt("key"));
}
@After
public void tearDown() {
// ...
}
}
Test annotations used in AOSP:
@SmallTest-- Unit tests (< 200ms)@MediumTest-- Integration tests (< 1000ms)@LargeTest-- End-to-end tests (no limit)@FlakyTest-- Known flaky tests@Presubmit-- Required for presubmit@RequiresDevice-- Needs a physical device
55.12.5 Mocking Android System Services¶
A common pattern in Android testing is mocking system services:
@RunWith(AndroidJUnit4.class)
public class ConnectivityTest {
@Mock private ConnectivityManager mockCm;
@Mock private Context mockContext;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mockContext.getSystemService(ConnectivityManager.class))
.thenReturn(mockCm);
}
@Test
public void testNetworkAvailable() {
NetworkInfo networkInfo = mock(NetworkInfo.class);
when(networkInfo.isConnected()).thenReturn(true);
when(mockCm.getActiveNetworkInfo()).thenReturn(networkInfo);
NetworkChecker checker = new NetworkChecker(mockContext);
assertTrue(checker.isNetworkAvailable());
}
@Test
public void testNoNetwork() {
when(mockCm.getActiveNetworkInfo()).thenReturn(null);
NetworkChecker checker = new NetworkChecker(mockContext);
assertFalse(checker.isNetworkAvailable());
}
}
55.12.6 Extended Mockito for Final Classes¶
Android framework classes are often final, which standard Mockito cannot
mock. The extended variant uses Dexmaker inline mocking:
// Use extended mockito for final class mocking
import static com.android.dx.mockito.inline.extended.ExtendedMockito.*;
@RunWith(AndroidJUnit4.class)
public class SettingsProviderTest {
@Test
public void testReadSetting() {
// Settings.Secure is a final class
mockitoSession()
.mockStatic(Settings.Secure.class)
.startMocking();
when(Settings.Secure.getString(any(), eq("my_setting")))
.thenReturn("mock_value");
assertEquals("mock_value",
Settings.Secure.getString(resolver, "my_setting"));
finishMocking();
}
}
55.12.7 Test Rules in AOSP¶
AOSP provides many custom JUnit rules:
// DeviceState rule for managing device configuration
@Rule
public final DeviceState deviceState = new DeviceState();
// Screen recording rule
@Rule
public final ScreenRecordRule screenRecord = new ScreenRecordRule();
// Activity scenario rule
@Rule
public ActivityScenarioRule<MyActivity> activityRule =
new ActivityScenarioRule<>(MyActivity.class);
// Feature flag rule
@Rule
public final SetFlagsRule flagRule = new SetFlagsRule();
55.12.8 Mocking Architecture¶
flowchart TB
subgraph Host["Host JVM"]
Mockito_H["Mockito<br>(ByteBuddy backend)"]
Mockito_H --> |"generates"| Proxy_H["Dynamic Proxy Classes"]
end
subgraph Device["ART Runtime"]
Mockito_D["Mockito<br>(Dexmaker backend)"]
Mockito_D --> |"generates"| DEX["DEX Bytecode"]
DEX --> |"loads"| ART["ART Runtime"]
DexInline["Dexmaker-Inline"]
DexInline --> |"instruments"| Final["Final Class Mocking"]
end
style Mockito_H fill:#c8e6c9
style Mockito_D fill:#fff9c4
55.13 Fuzzing¶
55.13.1 Overview¶
Fuzzing (fuzz testing) automatically generates random or semi-random inputs to discover crashes, memory corruption, and undefined behavior. Android's fuzzing infrastructure covers C/C++, Rust, and Java.
55.13.2 cc_fuzz¶
Defined in build/soong/cc/fuzz.go:
func init() {
android.RegisterModuleType("cc_fuzz", LibFuzzFactory)
android.RegisterParallelSingletonType("cc_fuzz_packaging", fuzzPackagingFactory)
android.RegisterParallelSingletonType("cc_fuzz_presubmit_packaging",
fuzzPackagingFactoryPresubmit)
}
The factory automatically enables sanitizers:
func NewFuzzer(hod android.HostOrDeviceSupported) *Module {
// ...
module.fuzzer.Properties.FuzzFramework = fuzz.LibFuzzer
android.AddLoadHook(module, func(ctx android.LoadHookContext) {
extraProps := struct {
Sanitize struct {
Fuzzer *bool
}
// ...
}{}
extraProps.Sanitize.Fuzzer = BoolPtr(true)
// Disable on Darwin and Linux Bionic
extraProps.Target.Darwin.Enabled = BoolPtr(false)
extraProps.Target.Linux_bionic.Enabled = BoolPtr(false)
ctx.AppendProperties(&extraProps)
// ...
})
return module
}
55.13.3 Fuzz Frameworks¶
From build/soong/fuzz/fuzz_common.go:
type Framework string
const (
AFL Framework = "afl"
LibFuzzer Framework = "libfuzzer"
Jazzer Framework = "jazzer"
UnknownFramework Framework = "unknownframework"
)
LibFuzzer (default): LLVM's coverage-guided fuzzer. Links
libFuzzerRuntimeLibrary.
AFL (American Fuzzy Lop): Alternative fuzzer using compile-time instrumentation:
func (fuzzer *fuzzer) flags(ctx ModuleContext, flags Flags) Flags {
if fuzzer.Properties.FuzzFramework == fuzz.AFL {
flags.Local.CFlags = append(flags.Local.CFlags, []string{
"-fsanitize-coverage=trace-pc-guard",
"-Wno-unused-result",
"-Wno-unused-parameter",
"-Wno-unused-function",
}...)
}
return flags
}
Jazzer: Java fuzzer (for java_fuzz modules).
55.13.4 Fuzz Config¶
Each fuzzer can include a configuration specifying its risk profile:
type Vector string
const (
unknown_access_vector Vector = "unknown_access_vector"
remote = "remote"
local_no_privileges_required = "local_no_privileges_required"
// ...
)
The fuzz_config block in Android.bp:
cc_fuzz {
name: "media_codec_fuzzer",
srcs: ["media_codec_fuzzer.cpp"],
shared_libs: ["libmedia", "libstagefright"],
corpus: ["corpus/*"],
dictionary: "media.dict",
fuzz_config: {
cc: ["security-team@google.com"],
componentid: 155276,
hotlists: ["4593311"],
description: "Fuzzer for media codec parsing",
vector: "remote",
service_privilege: "constrained",
users: "multi_user",
fuzzed_code_usage: "shipped",
use_for_presubmit: true,
},
}
55.13.5 Fuzz Packaging¶
The ccRustFuzzPackager singleton collects all fuzz targets and creates
distributable ZIP archives:
func (s *ccRustFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
archDirs := make(map[fuzz.ArchOs][]fuzz.FileToZip)
s.FuzzTargets = make(map[string]bool)
ctx.VisitAllModuleProxies(func(module android.ModuleProxy) {
// Collect fuzz modules, their shared libraries, corpus, config
// ...
files = s.PackageArtifacts(ctx, module, &fuzzInfo, archDir, builder)
files = append(files,
GetSharedLibsToZip(ccModule.FuzzSharedLibraries, ...))
files = append(files,
fuzz.FileToZip{SourceFilePath: android.OutputFileForModule(
ctx, module, "unstripped")})
// ...
})
s.CreateFuzzPackage(ctx, archDirs, fuzz.Cc, pctx)
ctx.Phony(s.phonyName, s.Packages...)
}
The make haiku target builds and packages all fuzzers.
55.13.6 rust_fuzz¶
Rust fuzz targets use libfuzzer-sys or LLVM's libFuzzer backend:
rust_fuzz {
name: "binder_parcel_fuzzer",
srcs: ["fuzz/parcel_fuzzer.rs"],
rustlibs: ["libbinder_rs"],
fuzz_config: {
vector: "local_no_privileges_required",
},
}
55.13.7 java_fuzz¶
Java fuzzing uses the Jazzer framework:
java_fuzz {
name: "xml_parser_fuzzer",
srcs: ["XmlParserFuzzer.java"],
libs: ["framework"],
fuzz_config: {
description: "Fuzzer for XML parsing",
},
}
55.13.8 Sanitizers¶
Fuzzers work best with sanitizers enabled. The build system supports:
| Sanitizer | Flag | Detects |
|---|---|---|
| ASan | -fsanitize=address |
Buffer overflows, use-after-free |
| HWASan | -fsanitize=hwaddress |
Same as ASan, lower overhead (ARM64) |
| UBSan | -fsanitize=undefined |
Undefined behavior |
| MSan | -fsanitize=memory |
Uninitialized memory reads |
| TSan | -fsanitize=thread |
Data races |
| CFI | -fsanitize=cfi |
Control-flow integrity violations |
When Fuzzer sanitizer is enabled, the build adds appropriate coverage
instrumentation:
func (fuzzBin *fuzzBinary) linkerDeps(ctx DepsContext, deps Deps) Deps {
if ctx.Config().Getenv("FUZZ_FRAMEWORK") == "AFL" {
deps.HeaderLibs = append(deps.HeaderLibs, "libafl_headers")
} else {
deps.StaticLibs = append(deps.StaticLibs,
config.LibFuzzerRuntimeLibrary())
if module, ok := ctx.Module().(*Module); ok {
if module.IsSanitizerEnabled(Hwasan) {
deps.StaticLibs = append(deps.StaticLibs,
config.LibFuzzerRuntimeInterceptors())
}
}
}
// ...
}
55.13.9 Fuzz Target Architecture¶
flowchart TB
subgraph Build["Build Phase"]
FuzzSrc["Fuzz Target Source"]
Corpus["Corpus Files"]
Dict["Dictionary"]
Config["Fuzz Config"]
end
subgraph Engine["Fuzz Engine"]
LibFuzzer["libFuzzer / AFL"]
Sanitizer["ASan / HWASan / UBSan"]
Coverage["Coverage Feedback"]
end
subgraph Execution["Execution"]
Mutator["Input Mutator"]
Target["Fuzz Target Function"]
Crash["Crash Reporter"]
end
FuzzSrc --> |"compile"| Target
Corpus --> Mutator
Dict --> Mutator
LibFuzzer --> Mutator
Mutator --> |"mutated input"| Target
Target --> |"coverage info"| Coverage
Coverage --> |"feedback"| Mutator
Target --> |"crash"| Crash
Sanitizer --> |"detect"| Crash
55.13.10 Fuzz Corpus Management¶
A corpus is a collection of seed inputs that the fuzzer uses as starting points for mutation. Good corpus management is critical for fuzzer effectiveness.
cc_fuzz {
name: "xml_parser_fuzzer",
srcs: ["xml_parser_fuzzer.cpp"],
corpus: ["corpus/*"], // Initial seed corpus
dictionary: "xml.dict", // Token dictionary
}
The dictionary file contains tokens the fuzzer should try:
55.13.11 Fuzz Config Details¶
The fuzz config specifies metadata for the fuzzing infrastructure:
type FuzzConfig struct {
// Contacts
Cc []string
// Component ID in bug tracker
Componentid int64
// Hotlist IDs
Hotlists []string
// Human-readable description
Description string
// Attack vector
Vector Vector
// Service privilege level
ServicePrivilege string
// User modes affected
Users string
// Usage: shipped, internal, experimental
FuzzedCodeUsage string
// Include in presubmit fuzzing
UseForPresubmit bool
}
The Vector field categorizes the attack surface:
| Vector | Meaning |
|---|---|
remote |
Reachable from network (e.g., media codecs) |
local_no_privileges_required |
Reachable by any app |
local_privileged |
Requires special permissions |
physical |
Requires physical access |
55.13.12 Continuous Fuzzing Infrastructure¶
Android runs fuzzers continuously in the background. The CI infrastructure:
- Builds all
cc_fuzzandrust_fuzztargets with sanitizers - Distributes fuzzers across a fuzzing cluster
- Runs each fuzzer for extended periods (hours to weeks)
- Reports new crashes to the security team
- Minimizes crashing inputs
- Checks for regressions when code changes
The make haiku target packages all fuzzers for the cluster:
func (s *ccRustFuzzPackager) GenerateBuildActions(
ctx android.SingletonContext) {
// ...
s.CreateFuzzPackage(ctx, archDirs, fuzz.Cc, pctx)
ctx.Phony(s.phonyName, s.Packages...)
ctx.DistForGoals([]string{s.phonyName}, s.Packages...)
}
The haiku-presubmit variant packages only fuzzers marked with
use_for_presubmit: true for faster presubmit runs:
func fuzzPackagingFactoryPresubmit() android.Singleton {
fuzzPackager := &ccRustFuzzPackager{
onlyIncludePresubmits: true,
phonyName: "haiku-presubmit",
}
return fuzzPackager
}
55.13.13 Writing Effective Fuzz Targets¶
Guidelines for writing effective fuzz targets:
// GOOD: Focused on a single parser
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Create a FuzzedDataProvider for structured fuzzing
FuzzedDataProvider fdp(data, size);
// Consume structured data from fuzz input
std::string format = fdp.ConsumeRandomLengthString(256);
int width = fdp.ConsumeIntegralInRange<int>(1, 8192);
int height = fdp.ConsumeIntegralInRange<int>(1, 8192);
std::vector<uint8_t> image_data =
fdp.ConsumeRemainingBytes<uint8_t>();
// Exercise the code under test
ImageDecoder decoder;
decoder.SetFormat(format);
decoder.Decode(image_data.data(), image_data.size(), width, height);
return 0;
}
Key principles:
- Single entry point: One
LLVMFuzzerTestOneInputper fuzzer - Structured fuzzing: Use
FuzzedDataProviderfor complex inputs - No global state: Each invocation should be independent
- Fast execution: Keep each iteration under 1ms
- Cover all error paths: Do not validate input before passing to SUT
- No memory leaks: The fuzzer runs millions of iterations
55.14 Code Coverage (JaCoCo)¶
55.14.1 Overview¶
JaCoCo (Java Code Coverage) measures which Java/Kotlin code paths are exercised
during test execution. AOSP integrates JaCoCo at the build system level via
build/soong/java/jacoco.go.
The external JaCoCo library lives at external/jacoco/.
55.14.2 Build System Integration¶
The JaCoCo build rule is defined in build/soong/java/jacoco.go:
var (
jacoco = pctx.AndroidStaticRule("jacoco", blueprint.RuleParams{
Command: `rm -rf $tmpDir && mkdir -p $tmpDir && ` +
`${config.Zip2ZipCmd} -i $in -o $strippedJar $stripSpec && ` +
`${config.JavaCmd} ${config.JavaVmFlags} ` +
` -jar ${config.JacocoCLIJar} ` +
` instrument --quiet --dest $tmpDir $strippedJar && ` +
`${config.MergeZipsCmd} --ignore-duplicates -j $out $tmpJar $in`,
CommandDeps: []string{
"${config.Zip2ZipCmd}",
"${config.JavaCmd}",
"${config.JacocoCLIJar}",
"${config.MergeZipsCmd}",
},
}, "strippedJar", "stripSpec", "tmpDir", "tmpJar")
)
55.14.3 Instrumentation Pipeline¶
The JaCoCo instrumentation pipeline works in three steps:
-
Strip: Extract relevant classes from the input JAR using
zip2zipwith include/exclude filters -
Instrument: Run
jacoco instrumenton the stripped JAR to insert coverage probes -
Merge: Combine the instrumented classes back with the original JAR, preferring instrumented versions
func jacocoInstrumentJar(ctx android.ModuleContext,
instrumentedJar, strippedJar android.WritablePath,
inputJar android.Path, stripSpec string) {
tmpJar := android.PathForModuleOut(ctx, "jacoco", "tmp", strippedJar.Base())
ctx.Build(pctx, android.BuildParams{
Rule: jacoco,
Description: "jacoco",
Output: instrumentedJar,
ImplicitOutput: strippedJar,
Input: inputJar,
Args: map[string]string{
"strippedJar": strippedJar.String(),
"stripSpec": stripSpec,
"tmpDir": filepath.Dir(tmpJar.String()),
"tmpJar": tmpJar.String(),
},
})
}
55.14.4 Filter Specifications¶
JaCoCo filters control which classes get instrumented. The filter syntax uses Java package notation with wildcards:
func jacocoFilterToSpec(filter string) (string, error) {
recursiveWildcard := strings.HasSuffix(filter, "**")
nonRecursiveWildcard := false
if !recursiveWildcard {
nonRecursiveWildcard = strings.HasSuffix(filter, "*")
filter = strings.TrimSuffix(filter, "*")
} else {
filter = strings.TrimSuffix(filter, "**")
}
spec := strings.Replace(filter, ".", "/", -1)
if recursiveWildcard {
spec += "**/*.class"
} else if nonRecursiveWildcard {
spec += "*.class"
} else {
spec += ".class"
}
return spec, nil
}
In Android.bp, modules specify coverage filters:
java_library {
name: "my_library",
srcs: ["src/**/*.java"],
jacoco: {
include_filter: ["com.android.mypackage.**"],
exclude_filter: ["com.android.mypackage.test.**"],
},
}
55.14.5 Dependencies Mutator¶
The jacocoDepsMutator automatically adds the jacocoagent dependency to
instrumentable modules:
func jacocoDepsMutator(ctx android.BottomUpMutatorContext) {
type instrumentable interface {
shouldInstrument(ctx android.BaseModuleContext) bool
shouldInstrumentInApex(ctx android.BaseModuleContext) bool
setInstrument(value bool)
}
j, ok := ctx.Module().(instrumentable)
if !ctx.Module().Enabled(ctx) || !ok {
return
}
if j.shouldInstrument(ctx) && ctx.ModuleName() != "jacocoagent" {
ctx.AddFarVariationDependencies(
ctx.Module().Target().Variations(), libTag, "jacocoagent")
}
}
55.14.6 Report ZIP Generation¶
The BuildJacocoZip() function collects instrumented classes from all modules
into a single ZIP for report generation:
func BuildJacocoZip(ctx BuildJacocoZipContext,
modules []android.ModuleProxy,
outputFile android.WritablePath) {
jacocoZipBuilder := android.NewRuleBuilder(pctx, ctx)
jacocoZipCmd := jacocoZipBuilder.Command().
BuiltTool("soong_zip").
FlagWithOutput("-o ", outputFile).
Flag("-L 0")
for _, m := range modules {
if javaInfo, ok := android.OtherModuleProvider(ctx, m,
JavaInfoProvider); ok && javaInfo.JacocoInfo.ReportClassesFile != nil {
jacoco := javaInfo.JacocoInfo
jacocoZipCmd.FlagWithArg("-e ",
fmt.Sprintf("out/target/common/obj/%s/%s_intermediates/"+
"jacoco-report-classes.jar",
jacoco.Class, jacoco.ModuleName)).
FlagWithInput("-f ", jacoco.ReportClassesFile)
}
}
// ...
}
55.14.7 Device Test Coverage¶
Device test coverage can be included via an environment variable:
func BuildJacocoZipWithPotentialDeviceTests(ctx android.ModuleContext,
modules []android.ModuleProxy,
outputFile android.WritablePath) {
if !ctx.Config().IsEnvTrue("JACOCO_PACKAGING_INCLUDE_DEVICE_TESTS") {
BuildJacocoZip(ctx, modules, outputFile)
return
}
// Merge device test coverage with regular coverage
// ...
}
The device_tests_jacoco_zip singleton collects JaCoCo data from all modules
in the device-tests suite:
func (d *deviceTestsJacocoZipSingleton) GenerateBuildActions(
ctx android.SingletonContext) {
var deviceTestModules []android.ModuleProxy
ctx.VisitAllModuleProxies(func(m android.ModuleProxy) {
if tsm, ok := android.OtherModuleProvider(ctx, m,
android.TestSuiteInfoProvider); ok {
if slices.Contains(tsm.TestSuites, "device-tests") {
deviceTestModules = append(deviceTestModules, m)
}
}
})
jacocoZip := DeviceTestsJacocoReportZip(ctx)
BuildJacocoZip(ctx, deviceTestModules, jacocoZip)
}
55.14.8 Running with Coverage¶
# Build with coverage enabled
EMMA_INSTRUMENT=true make MyModule
# Run tests with atest coverage flag
atest --experimental-coverage MyTestModule
# Generate coverage report
java -jar jacoco-cli.jar report \
coverage.exec \
--classfiles out/target/common/obj/ \
--html coverage-report/
55.14.9 Coverage Architecture¶
flowchart LR
subgraph Build["Build Phase"]
Source["Java Source"]
Compile["javac"]
Instrument["JaCoCo Instrument"]
end
subgraph Test["Test Phase"]
InstrJAR["Instrumented JAR"]
Runtime["JaCoCo Agent"]
ExecFile["coverage.exec"]
end
subgraph Report["Report Phase"]
CLI["JaCoCo CLI"]
HTML["HTML Report"]
XML["XML Report"]
end
Source --> Compile --> Instrument
Instrument --> InstrJAR
InstrJAR --> Runtime
Runtime --> |"probe data"| ExecFile
ExecFile --> CLI
CLI --> HTML
CLI --> XML
55.14.10 Native Code Coverage¶
For C/C++ code, AOSP supports native coverage using LLVM's source-based
coverage (-fprofile-instr-generate -fcoverage-mapping) and GCC-compatible
gcov format.
Native coverage is enabled via build flags:
The coverage data can be collected using:
# Pull coverage data from device
adb pull /data/misc/trace/ coverage_data/
# Generate report
llvm-cov show binary -instr-profile=coverage.profdata
55.14.11 Coverage in CI¶
The CI pipeline integrates coverage collection:
- Build phase: Instrument modules with JaCoCo / LLVM coverage
- Test phase: Run tests, collect
.execfiles (Java) or.profdata(native) - Report phase: Generate HTML/XML reports
- Gate phase: Block merge if coverage drops below threshold
flowchart LR
Build["Instrumented Build"] --> Test["Test Execution"]
Test --> Collect["Collect Coverage Data"]
Collect --> Report["Generate Report"]
Report --> Gate["Coverage Gate"]
Gate --> |"pass"| Merge["Allow Merge"]
Gate --> |"fail"| Block["Block Merge"]
55.15 Platform Testing Libraries¶
55.15.1 Overview¶
AOSP provides a rich collection of shared testing libraries under
platform_testing/libraries/ (35 subdirectories). These libraries encapsulate
common patterns, reduce boilerplate, and provide device interaction helpers.
55.15.2 Directory Listing¶
platform_testing/libraries/
annotations/ -- Custom test annotations
app-helpers/ -- App interaction helpers
audio-test-harness/ -- Audio testing framework
aupt-lib/ -- Automated User Performance Testing
automotive/ -- Automotive test utilities
automotive-helpers/ -- Automotive helper functions
car-helpers/ -- Car-specific test helpers
collectors-helper/ -- Metric collector helpers
compatibility-common-util/ -- CTS/VTS shared utilities
desktop-test-lib/ -- Desktop mode testing
device-collectors/ -- Device-side metric collectors
flag-helpers/ -- Feature flag test helpers
flicker/ -- Window transition testing (31.11.5)
health/ -- Device health checks
junit-rules/ -- Custom JUnit rules
junitxml/ -- JUnit XML result format
launcher-helper/ -- Launcher interaction helpers
media-helper/ -- Media test utilities
metrics-helper/ -- Metrics collection and reporting
motion/ -- Motion/gesture testing
notes-role-test-helper/ -- Notes role testing
power-helper/ -- Power measurement helpers
rdroidtest/ -- R Droid test utilities
runner/ -- Custom test runners
screenshot/ -- Screenshot testing (31.11.6)
sts-common-util/ -- STS shared utilities
system-helpers/ -- System interaction helpers
systemui-helper/ -- SystemUI test helpers
systemui-tapl/ -- SystemUI TAPL (31.11.4)
tapl-common/ -- Common TAPL utilities
timeresult-helper/ -- Time-based result helpers
tradefed-error-prone/ -- Error-prone rules for TF
uiautomator-helpers/ -- UIAutomator extensions
uinput-device-test-helper/ -- Synthetic input device helpers
55.15.3 Key Libraries¶
device-collectors/: Provides metric collectors that run alongside tests to gather performance data:
- CPU usage
- Memory allocation
- Battery drain
- JankStats (frame timing)
- Method tracing
collectors-helper/: Helpers for device collectors that simplify the setup and teardown of metric collection.
junit-rules/: Custom JUnit rules for common Android test patterns:
DeviceStateRule-- Manage device state across testsRavenRule-- Ravenwood-specific test rulesScreenRecordRule-- Record screen during test
flag-helpers/: Utilities for testing with Android feature flags:
@EnableFlags(Flags.FLAG_NEW_FEATURE)
@Test
public void testNewFeature_enabled() {
// Test code that exercises the new feature
}
@DisableFlags(Flags.FLAG_NEW_FEATURE)
@Test
public void testNewFeature_disabled() {
// Test code that exercises the old behavior
}
health/: Device health check utilities that verify device state before and after tests (battery level, disk space, network connectivity).
runner/: Custom test runner implementations that extend AndroidJUnitRunner with additional capabilities like test orchestration and result formatting.
sts-common-util/: Shared utilities for Security Test Suite tests, including exploit helpers and vulnerability verification tools.
55.15.4 Using Platform Testing Libraries¶
These libraries are available as build dependencies:
android_test {
name: "MyIntegrationTest",
srcs: ["src/**/*.java"],
static_libs: [
"platform-test-annotations",
"platform-test-rules",
"collector-device-lib",
"launcher-helper-lib",
"uiautomator-helpers",
],
test_suites: ["device-tests"],
}
55.15.5 Device Collectors¶
Device collectors (platform_testing/libraries/device-collectors/) gather
metrics during test execution. They implement the IMetricCollector interface
and are configured in TradeFed XML:
<metrics_collector
class="com.android.helpers.CpuUsageHelper" />
<metrics_collector
class="com.android.helpers.MemoryUsageHelper" />
<metrics_collector
class="com.android.helpers.PerfettoHelper">
<option name="pull-pattern-metric-key" value="perfetto_trace" />
</metrics_collector>
Common collectors:
- CpuUsageHelper: Measures CPU utilization during tests
- MemoryUsageHelper: Tracks memory allocation patterns
- BatteryStatsHelper: Records battery consumption
- JankHelper: Measures frame timing and jank
- PerfettoHelper: Captures system-wide Perfetto traces
- AppStartupHelper: Measures app cold/warm/hot start times
55.15.6 AUPT (Automated User Performance Testing)¶
AUPT (platform_testing/libraries/aupt-lib/) provides a framework for
long-running user-journey performance tests:
public class SettingsJourney extends AbstractAuptTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
mDevice = UiDevice.getInstance(getInstrumentation());
}
public void testBrowseSettings() throws Exception {
// Simulate user navigating through Settings
mDevice.pressHome();
openSettings();
navigateToDisplay();
navigateToSound();
navigateToSecurity();
// Metrics collected automatically throughout
}
}
AUPT automatically collects memory, CPU, and battery metrics throughout the user journey.
55.15.7 Annotations Library¶
The platform_testing/libraries/annotations/ library provides custom
annotations for Android tests:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface PlatformScenario {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface HermeticTest {
// Test does not require network or external services
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface NonHermeticTest {
String reason() default "";
}
55.15.8 Compatibility Common Util¶
The compatibility-common-util library provides shared utilities for CTS/VTS:
// Device info collection
DeviceInfo deviceInfo = DeviceInfo.getInstance(device);
String buildId = deviceInfo.getBuildId();
String model = deviceInfo.getModel();
int sdkVersion = deviceInfo.getSdkVersion();
// Test filtering
ModuleFilterHelper filter = new ModuleFilterHelper(
includeFilters, excludeFilters);
boolean shouldRun = filter.shouldRunModule(moduleName);
// Result aggregation
ResultAggregator aggregator = new ResultAggregator();
aggregator.addResult(moduleResult);
TestResultSummary summary = aggregator.getSummary();
55.15.9 Library Dependency Graph¶
graph TB
Test["Your Test Module"]
Test --> |"static_libs"| Runner["platform-test-runner"]
Test --> |"static_libs"| Annotations["platform-test-annotations"]
Test --> |"static_libs"| Rules["platform-test-rules"]
Test --> |"static_libs"| Collectors["collector-device-lib"]
Test --> |"static_libs"| UIA["uiautomator-helpers"]
Test --> |"static_libs"| SysUI["systemui-helper-lib"]
Test --> |"static_libs"| Flags["flag-junit-helper"]
Collectors --> |"depends"| Metrics["metrics-helper"]
UIA --> |"depends"| TaplCommon["tapl-common"]
SysUI --> |"depends"| TaplCommon
55.16 Other Test Suites¶
55.16.1 MTS (Mainline Test Suite)¶
MTS validates updatable Mainline modules. Each Mainline module (networking, media, permissions, etc.) can be updated independently via Google Play, and MTS ensures updates do not break compatibility.
MTS test modules declare their suite membership with mts prefix variants:
android_test {
name: "CtsNetTestCases",
test_suites: [
"cts",
"mts-networking",
"general-tests",
],
}
The build system automatically adds mts as a compatibility suite when any
mts-* prefix is present:
func (test *testDecorator) moduleInfoJSON(ctx android.ModuleContext,
moduleInfoJSON *android.ModuleInfoJSON) {
if android.PrefixInList(moduleInfoJSON.CompatibilitySuites, "mts-") &&
!android.InList("mts", moduleInfoJSON.CompatibilitySuites) {
moduleInfoJSON.CompatibilitySuites = append(
moduleInfoJSON.CompatibilitySuites, "mts")
}
}
MTS tests are parameterized with Test_mainline_modules to test specific module
combinations:
cc_test {
name: "resolv_integration_test",
test_mainline_modules: [
"CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+" +
"com.google.android.resolv.apex",
],
}
55.16.2 CTS-root¶
CTS-root contains CTS test modules that require root access on the device. These tests verify behaviors that are only accessible with elevated privileges but are still part of the compatibility contract.
55.16.3 Catbox¶
Catbox is the automotive compliance test suite. It runs a subset of CTS tests relevant to Android Automotive OS along with automotive-specific tests:
Catbox validates automotive-specific APIs including:
- Car service APIs
- Vehicle HAL interactions
- Automotive UI requirements
- Multi-display support
55.16.4 DittoSuite¶
DittoSuite is a benchmark and stress-testing framework for storage I/O performance. It generates configurable workloads to measure:
- Sequential and random read/write throughput
- IOPS (Input/Output Operations Per Second)
- Latency distribution
- Storage behavior under pressure
55.16.5 Suite Hierarchy¶
graph TB
subgraph Compliance["Compliance Suites"]
CTS["CTS<br>(App compatibility)"]
VTS["VTS<br>(Vendor/HAL)"]
GTS["GTS<br>(Google services)"]
end
subgraph Security["Security Suites"]
STS["STS<br>(Security patches)"]
end
subgraph Mainline["Mainline Suites"]
MTS["MTS<br>(Module updates)"]
end
subgraph Specialized["Specialized Suites"]
CTS_ROOT["CTS-root<br>(Root-required)"]
CATBOX["Catbox<br>(Automotive)"]
DITTO["DittoSuite<br>(Storage perf)"]
end
subgraph Development["Development Suites"]
GENERAL["general-tests<br>(Presubmit)"]
DEVICE["device-tests<br>(Device-side)"]
ROBO["robolectric-tests<br>(Host Robolectric)"]
RAVEN["ravenwood-tests<br>(Host Ravenwood)"]
end
CTS --> CTS_ROOT
CTS --> CATBOX
55.17 Try It: Write Tests at Every Level¶
This hands-on section walks through writing tests at each level of the Android
test pyramid, using a hypothetical StringUtils module as the system under test.
55.17.1 Exercise 1: Host-Side Unit Test (cc_test_host)¶
Create a native host-side unit test for a C++ utility library.
Step 1: Create the test source
// frameworks/libs/stringutils/tests/string_utils_test.cpp
#include <gtest/gtest.h>
#include "string_utils.h"
TEST(StringUtilsTest, TrimRemovesLeadingSpaces) {
EXPECT_EQ(trim(" hello"), "hello");
}
TEST(StringUtilsTest, TrimRemovesTrailingSpaces) {
EXPECT_EQ(trim("hello "), "hello");
}
TEST(StringUtilsTest, TrimPreservesMiddleSpaces) {
EXPECT_EQ(trim(" hello world "), "hello world");
}
TEST(StringUtilsTest, TrimHandlesEmptyString) {
EXPECT_EQ(trim(""), "");
}
TEST(StringUtilsTest, TrimHandlesAllSpaces) {
EXPECT_EQ(trim(" "), "");
}
Step 2: Create the build rule
// frameworks/libs/stringutils/tests/Android.bp
cc_test_host {
name: "string_utils_test",
srcs: ["string_utils_test.cpp"],
static_libs: ["libstringutils"],
test_suites: ["general-tests"],
test_options: {
unit_test: true,
},
}
Step 3: Run it
55.17.2 Exercise 2: Ravenwood Framework Test¶
Test an Android framework utility class on the host JVM.
Step 1: Create the test source
// frameworks/base/core/tests/ravenwood/src/android/util/SparseArrayRavenwoodTest.java
package android.util;
import static org.junit.Assert.*;
import android.util.SparseArray;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class SparseArrayRavenwoodTest {
@Test
public void testPutAndGet() {
SparseArray<String> array = new SparseArray<>();
array.put(1, "one");
array.put(2, "two");
assertEquals("one", array.get(1));
assertEquals("two", array.get(2));
}
@Test
public void testSize() {
SparseArray<String> array = new SparseArray<>();
assertEquals(0, array.size());
array.put(1, "one");
assertEquals(1, array.size());
}
@Test
public void testRemove() {
SparseArray<String> array = new SparseArray<>();
array.put(1, "one");
array.remove(1);
assertNull(array.get(1));
}
}
Step 2: Create the build rule
android_ravenwood_test {
name: "SparseArrayRavenwoodTest",
srcs: ["src/**/*.java"],
static_libs: [
"ravenwood-junit",
],
sdk_version: "test_current",
auto_gen_config: true,
}
Step 3: Run it
55.17.3 Exercise 3: Device Instrumentation Test¶
Write a test that exercises real device behavior.
Step 1: Create the test source
// packages/apps/MyApp/tests/src/com/example/myapp/MainActivityTest.java
package com.example.myapp;
import static org.junit.Assert.*;
import android.content.Intent;
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MainActivityTest {
@Test
public void testActivityLaunches() {
try (ActivityScenario<MainActivity> scenario =
ActivityScenario.launch(MainActivity.class)) {
scenario.onActivity(activity -> {
assertNotNull(activity);
assertFalse(activity.isFinishing());
});
}
}
}
Step 2: Create the build rule
android_test {
name: "MyAppTests",
srcs: ["tests/src/**/*.java"],
instrumentation_for: "MyApp",
static_libs: [
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
"truth",
],
test_suites: ["device-tests", "general-tests"],
}
Step 3: Create TEST_MAPPING
Step 4: Run it
55.17.4 Exercise 4: CTS-Style Compliance Test¶
Write a test that verifies API behavior as a CTS module.
Step 1: Create the test source
// cts/tests/myapi/src/android/myapi/cts/MyApiTest.java
package android.myapi.cts;
import static org.junit.Assert.*;
import android.myapi.MyApiManager;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class MyApiTest {
private MyApiManager mManager;
@Before
public void setUp() {
Context context = InstrumentationRegistry.getTargetContext();
mManager = context.getSystemService(MyApiManager.class);
assertNotNull("MyApiManager must be available", mManager);
}
@Test
public void testGetVersion_returnsNonNegative() {
int version = mManager.getVersion();
assertTrue("Version must be non-negative, got: " + version,
version >= 0);
}
}
Step 2: Create the build rule with CTS suite
android_test {
name: "CtsMyApiTestCases",
defaults: ["cts_defaults"],
srcs: ["src/**/*.java"],
test_suites: [
"cts",
"general-tests",
],
static_libs: [
"ctstestrunner-axt",
"compatibility-device-util-axt",
],
sdk_version: "test_current",
}
55.17.5 Exercise 5: Native Fuzz Target¶
Write a fuzzer for a parsing function.
Step 1: Create the fuzz target
// frameworks/libs/stringutils/fuzz/string_parser_fuzzer.cpp
#include <stdint.h>
#include <stddef.h>
#include "string_parser.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Create a null-terminated string from the fuzz input
std::string input(reinterpret_cast<const char*>(data), size);
// Exercise the parser with fuzz input
ParseResult result;
parse_string(input.c_str(), &result);
return 0;
}
Step 2: Create the build rule
cc_fuzz {
name: "string_parser_fuzzer",
srcs: ["string_parser_fuzzer.cpp"],
static_libs: ["libstringutils"],
corpus: ["corpus/*"],
fuzz_config: {
description: "Fuzzer for string parser",
vector: "local_no_privileges_required",
service_privilege: "constrained",
},
}
Step 3: Run it
# Build all fuzzers
make haiku
# Run the specific fuzzer
$ANDROID_HOST_OUT/fuzz/x86_64/string_parser_fuzzer/string_parser_fuzzer \
corpus/
55.17.6 Exercise 6: Robolectric Test¶
Test an Activity's behavior without a device.
Step 1: Create the test source
// packages/apps/Settings/tests/robotests/src/com/android/settings/
// SettingsActivityRoboTest.java
package com.android.settings;
import static org.junit.Assert.*;
import android.content.Intent;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
@RunWith(RobolectricTestRunner.class)
public class SettingsActivityRoboTest {
@Test
public void testOnCreate_doesNotCrash() {
ActivityController<SettingsActivity> controller =
Robolectric.buildActivity(SettingsActivity.class);
controller.create();
assertFalse(controller.get().isFinishing());
}
@Test
public void testStartedWithIntent_handlesNull() {
ActivityController<SettingsActivity> controller =
Robolectric.buildActivity(SettingsActivity.class, null);
controller.create().start().resume();
assertNotNull(controller.get());
}
}
Step 2: Build rule
android_robolectric_test {
name: "SettingsRoboTests",
srcs: ["tests/robotests/src/**/*.java"],
instrumentation_for: "Settings",
java_resource_dirs: ["tests/robotests/config"],
}
Step 3: Run it
55.17.7 Exercise 7: Screenshot Test¶
Write a screenshot test to catch visual regressions.
Step 1: Create the test source
// packages/apps/MyApp/tests/screenshot/src/com/example/myapp/
// ButtonScreenshotTest.java
package com.example.myapp;
import android.view.View;
import android.widget.Button;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import platform.test.screenshot.DeviceEmulationSpec;
import platform.test.screenshot.ScreenshotTestRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ButtonScreenshotTest {
@Rule
public final ScreenshotTestRule screenshotRule =
new ScreenshotTestRule(DeviceEmulationSpec.PHONE);
@Test
public void testPrimaryButton_defaultState() {
Button button = new Button(screenshotRule.getContext());
button.setText("Save");
button.setEnabled(true);
screenshotRule.assertBitmapAgainstGolden(
screenshotRule.render(button),
"primary_button_default"
);
}
@Test
public void testPrimaryButton_disabledState() {
Button button = new Button(screenshotRule.getContext());
button.setText("Save");
button.setEnabled(false);
screenshotRule.assertBitmapAgainstGolden(
screenshotRule.render(button),
"primary_button_disabled"
);
}
}
Step 2: Build rule
android_test {
name: "MyAppScreenshotTests",
srcs: ["tests/screenshot/src/**/*.java"],
static_libs: [
"platform-screenshot-diff-core",
"androidx.test.runner",
],
asset_dirs: ["tests/screenshot/goldens"],
test_suites: ["device-tests"],
}
Step 3: Update golden images when designs change
# Run tests to generate new golden images
atest MyAppScreenshotTests -- \
--update-goldens
# Or use the update script
python3 platform_testing/libraries/screenshot/update_goldens.py \
--module MyAppScreenshotTests
55.17.8 Exercise 8: Robolectric with Mockito¶
Combine Robolectric's environment with Mockito for isolated testing.
Step 1: Create the test
package com.android.settings.wifi;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import android.content.Context;
import android.net.wifi.WifiManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class WifiControllerRoboTest {
@Mock private WifiManager mockWifiManager;
private Context context;
private WifiController controller;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
context = RuntimeEnvironment.getApplication();
controller = new WifiController(context, mockWifiManager);
}
@Test
public void testToggleWifi_enablesWhenDisabled() {
when(mockWifiManager.isWifiEnabled()).thenReturn(false);
controller.toggleWifi();
verify(mockWifiManager).setWifiEnabled(true);
}
@Test
public void testToggleWifi_disablesWhenEnabled() {
when(mockWifiManager.isWifiEnabled()).thenReturn(true);
controller.toggleWifi();
verify(mockWifiManager).setWifiEnabled(false);
}
@Test
public void testGetWifiState_returnsCorrectString() {
when(mockWifiManager.getWifiState())
.thenReturn(WifiManager.WIFI_STATE_ENABLED);
assertEquals("Enabled", controller.getWifiStateString());
}
}
55.17.9 Exercise 9: Multi-Level Test Strategy¶
For a new system service, create tests at every level.
Level 1: Ravenwood unit tests (host, no device)
android_ravenwood_test {
name: "MyServiceUnitTestsRavenwood",
srcs: ["tests/ravenwood/src/**/*.java"],
// Tests pure logic, data structures, state machines
}
Level 2: Robolectric tests (host, with shadows)
android_robolectric_test {
name: "MyServiceRoboTests",
srcs: ["tests/robo/src/**/*.java"],
instrumentation_for: "MyServiceApp",
// Tests service behavior with simulated framework
}
Level 3: Device integration tests
android_test {
name: "MyServiceIntegrationTests",
srcs: ["tests/integration/src/**/*.java"],
test_suites: ["device-tests", "general-tests"],
// Tests real Binder calls, permissions, multi-process
}
Level 4: CTS compliance tests
android_test {
name: "CtsMyServiceTestCases",
defaults: ["cts_defaults"],
srcs: ["tests/cts/src/**/*.java"],
test_suites: ["cts", "general-tests"],
// Tests public API contract
}
Level 5: Fuzz targets
cc_fuzz {
name: "my_service_input_fuzzer",
srcs: ["fuzz/input_fuzzer.cpp"],
// Fuzzes native code in the service
}
flowchart TB
subgraph Pyramid["Multi-Level Test Strategy"]
L5["Level 5: Fuzz Targets<br>cc_fuzz, rust_fuzz<br><i>Find crashes</i>"]
L4["Level 4: CTS Compliance<br>android_test (cts suite)<br><i>API contracts</i>"]
L3["Level 3: Device Integration<br>android_test<br><i>Real system behavior</i>"]
L2["Level 2: Robolectric<br>android_robolectric_test<br><i>Shadow-based testing</i>"]
L1["Level 1: Ravenwood / Host Unit<br>android_ravenwood_test / cc_test_host<br><i>Pure logic, fastest</i>"]
end
L5 --- L4
L4 --- L3
L3 --- L2
L2 --- L1
style L1 fill:#c8e6c9
style L2 fill:#dcedc8
style L3 fill:#fff9c4
style L4 fill:#ffe0b2
style L5 fill:#ffccbc
55.17.10 Testing Checklist¶
Use this checklist when adding tests to your AOSP module:
- [ ] Unit tests exist for all public functions/methods
-
[ ] Host-preferred: Can the test run without a device? Use
cc_test_host,android_ravenwood_test, orandroid_robolectric_test -
[ ] TEST_MAPPING updated: Added test to
presubmitgroup - [ ] test_suites declared: Module specifies
general-testsat minimum - [ ] auto_gen_config: Let the build system generate TradeFed XML
- [ ] Security-critical code fuzzed: Created
cc_fuzzorrust_fuzztarget - [ ] Coverage measured: JaCoCo filters set for coverage reporting
- [ ] No flakiness: Test passes reliably in 100+ consecutive runs
- [ ] Fast execution: Unit tests complete in < 1 second
- [ ] Minimal device dependency: Only use device when truly necessary
55.17.11 Common Pitfalls and Solutions¶
| Pitfall | Symptom | Solution |
|---|---|---|
| Missing test_suites | Test not picked up by CI | Add "general-tests" to test_suites |
| No TEST_MAPPING | No presubmit coverage | Create TEST_MAPPING in your directory |
| Device-only when host possible | Slow presubmit | Convert to Ravenwood or Robolectric |
| Flaky timing assertions | Intermittent failures | Use polling/waiting instead of sleep |
| Hardcoded device paths | Fails on different devices | Use context/environment APIs |
| Missing auto_gen_config | Test not runnable by TF | Either provide AndroidTest.xml or set auto_gen_config: true |
| Wrong runner | Test executes but fails | Verify runner matches test framework |
| No data property | Test cannot find test files | Add data files to the data property |
| Shared mutable state | Tests interfere with each other | Use fresh state in @Before, clean in @After |
| Missing permissions | SecurityException | Use require_root or proper test manifest |
55.17.12 Test Decision Flowchart¶
flowchart TB
Start["Need to test code"] --> Q1{"Does it need<br>a real device?"}
Q1 --> |"No"| Q2{"Java/Kotlin or<br>C/C++?"}
Q1 --> |"Yes"| Q3{"UI testing<br>needed?"}
Q2 --> |"Java"| Q4{"Framework APIs<br>needed?"}
Q2 --> |"C/C++"| CCHost["cc_test_host"]
Q4 --> |"Yes, high fidelity"| Raven["android_ravenwood_test"]
Q4 --> |"Yes, broad coverage"| Robo["android_robolectric_test"]
Q4 --> |"No"| JTH["java_test_host"]
Q3 --> |"Within app"| Espresso["Espresso<br>(android_test)"]
Q3 --> |"Cross app"| UIAuto["UIAutomator<br>(android_test)"]
Q3 --> |"Transitions"| Flicker["Flicker<br>(platform_testing)"]
Q3 --> |"No"| Q5{"Compliance<br>test?"}
Q5 --> |"API contract"| CTS_T["CTS module<br>(android_test)"]
Q5 --> |"HAL/vendor"| VTS_T["VTS module<br>(cc_test)"]
Q5 --> |"No"| DeviceTest["android_test /<br>cc_test"]
style Raven fill:#c8e6c9
style Robo fill:#c8e6c9
style CCHost fill:#c8e6c9
style JTH fill:#c8e6c9
55.17.13 End-to-End Workflow: From Code Change to Test Execution¶
This section traces the complete path from a developer making a code change to the tests being executed in CI.
sequenceDiagram
participant Dev as Developer
participant Repo as Code Repository
participant CI as CI System
participant TM as TEST_MAPPING Parser
participant Soong as Soong Build System
participant TF as TradeFederation
participant Device as Test Device
Dev->>Repo: Upload CL (code change)
CI->>Repo: Detect changed files
CI->>TM: Walk directory tree for TEST_MAPPING
TM-->>CI: List of presubmit tests
CI->>Soong: Build test modules + dependencies
Soong->>Soong: Compile, link, generate TF configs
Soong-->>CI: Test APKs, binaries, configs
CI->>TF: Create invocation with test plan
TF->>TF: CommandScheduler.addCommand()
TF->>TF: Allocate device(s)
TF->>Device: Prepare (flash, install, configure)
loop For each test module
TF->>Device: Execute tests
Device-->>TF: Results
end
TF->>TF: Aggregate results, apply retry
TF-->>CI: Pass/Fail report
CI-->>Dev: Presubmit result
alt All tests pass
CI->>Repo: Allow merge
else Tests fail
CI->>Dev: Block merge, show failures
end
Step 1: Change Detection The CI system identifies which files changed in the CL and maps them to TEST_MAPPING files using directory walk-up.
Step 2: Test Selection
TEST_MAPPING files are parsed, and the presubmit group tests are collected.
file_patterns are matched against the changed files to scope tests.
Step 3: Build Soong compiles the required test modules and all their dependencies. It auto-generates TradeFed XML configs for each test module.
Step 4: Execution TradeFederation receives the test plan and:
- Allocates devices from the device pool
- Runs target preparers (install APKs, push files, etc.)
- Executes each test module
- Collects results via ITestInvocationListener
- Applies retry logic for failures
Step 5: Reporting Results are aggregated and reported back to the CI system, which updates the CL status.
55.17.14 Performance Optimization Tips¶
-
Minimize build targets: Use
--build-output briefwith atest to reduce build noise -
Use --host: Always add
--hostfor host-only tests to skip device setup -
Leverage caching: atest caches test discovery results; avoid
--clear-cacheunless necessary -
Parallel sharding: Use
--sharding Nfor large test suites across multiple devices -
Incremental testing: Use
atest --test-mappingto run only tests relevant to your change -
Skip install: Use
--steps testto skip build+install when iterating on test code changes (after initial build)
Summary¶
Android's testing infrastructure is a comprehensive ecosystem that spans the entire stack from kernel to UI, supporting billions of devices across hundreds of manufacturers. The key takeaways from this chapter:
-
TradeFederation is the central test harness that unifies all test execution. Its pluggable architecture of preparers, runners, and reporters supports every test type in the platform.
-
The build system provides dedicated module types (
cc_test,android_test,rust_test, etc.) that auto-generate TradeFed configurations, manage dependencies, and install tests correctly. -
TEST_MAPPING connects code changes to test execution, enabling targeted presubmit testing without manual configuration.
-
Host-side testing (Ravenwood, Robolectric, host GTest) provides fast feedback loops by eliminating device dependencies. New code should prefer host-side tests wherever possible.
-
Compliance suites (CTS, VTS, MTS) enforce the contracts that enable Android's ecosystem to function across diverse hardware.
-
Fuzzing is first-class, with build system support for
cc_fuzz,rust_fuzz, andjava_fuzztargets that integrate with LLVM sanitizers for comprehensive vulnerability discovery. -
atest bridges the gap between developers and the test infrastructure, providing a simple CLI that handles building, installing, and running any test in the tree.
The best testing strategy for any AOSP module follows the pyramid: maximize fast host-side unit tests, add focused device integration tests for behavior that requires real hardware, and ensure compliance with the relevant test suites for your component.
Test Infrastructure Component Map¶
graph TB
subgraph BuildSystem["Build System (Soong)"]
CCTest["cc_test / cc_test_host"]
AndroidTest["android_test"]
RustTest["rust_test"]
PythonTest["python_test_host"]
RavenTest["android_ravenwood_test"]
RoboTest["android_robolectric_test"]
CCFuzz["cc_fuzz"]
Autogen["tradefed/autogen.go<br>Auto-gen XML config"]
end
subgraph Harness["Test Harness (TradeFed)"]
Scheduler["CommandScheduler"]
Invocation["TestInvocation"]
Sharding["ShardHelper"]
Retry["BaseRetryDecision"]
Runners["Test Runners<br>(GTest, JUnit, Python, ...)"]
Preparers["Target Preparers"]
Reporters["Result Reporters"]
end
subgraph Discovery["Test Discovery"]
TestMapping["TEST_MAPPING"]
Atest["atest"]
Finders["Test Finders<br>(Module, Cache, TF, Suite)"]
end
subgraph Suites["Compliance Suites"]
CTS_S["CTS"]
VTS_S["VTS"]
MTS_S["MTS"]
STS_S["STS"]
end
subgraph Frameworks["Test Frameworks"]
GTest_F["GoogleTest (C++)"]
JUnit_F["JUnit 4/5 (Java)"]
Mockito_F["Mockito + Dexmaker"]
Espresso_F["Espresso (UI)"]
UIA_F["UIAutomator"]
Flicker_F["Flicker"]
Screenshot_F["Screenshot Testing"]
Robolectric_F["Robolectric (Shadows)"]
Ravenwood_F["Ravenwood (Host JVM)"]
Fuzzing_F["libFuzzer / AFL / Jazzer"]
end
subgraph Coverage["Coverage"]
JaCoCo_C["JaCoCo (Java)"]
LLVM_C["LLVM Coverage (C++)"]
end
BuildSystem --> Autogen
Autogen --> Harness
Discovery --> Harness
Harness --> Frameworks
Harness --> Suites
Frameworks --> Coverage
Quick Reference: Module Type Selection¶
| I want to test... | Language | Module type | Needs device? |
|---|---|---|---|
| Pure logic / data structures | Java/Kotlin | android_ravenwood_test |
No |
| Activity/Fragment behavior | Java/Kotlin | android_robolectric_test |
No |
| Public SDK API contracts | Java/Kotlin | android_test (CTS) |
Yes |
| App UI behavior | Java/Kotlin | android_test + Espresso |
Yes |
| Cross-app / system UI | Java/Kotlin | android_test + UIAutomator |
Yes |
| Native library logic | C/C++ | cc_test_host |
No |
| Native system behavior | C/C++ | cc_test |
Yes |
| HAL implementation | C/C++ | cc_test (VTS) |
Yes |
| Rust library | Rust | rust_test |
Depends |
| Python automation | Python | python_test_host |
No |
| Security fuzzing | C/C++ | cc_fuzz |
No (host) |
| Security fuzzing | Rust | rust_fuzz |
No (host) |
| Security fuzzing | Java | java_fuzz |
No (host) |
| Host-driven device test | Java | java_test_host |
Yes (remote) |
| Performance benchmark | C/C++ | cc_benchmark |
Yes |
| Window transitions | Java | Flicker library | Yes |
| Visual regression | Java | Screenshot library | Yes |
Key Source Files Referenced¶
| File | Section |
|---|---|
tools/tradefederation/core/src/com/android/tradefed/ |
31.2 |
tools/tradefederation/core/src/com/android/tradefed/invoker/TestInvocation.java |
31.2.2 |
tools/tradefederation/core/src/com/android/tradefed/invoker/shard/ShardHelper.java |
31.2.4 |
tools/tradefederation/core/src/com/android/tradefed/command/CommandScheduler.java |
31.2.2 |
tools/tradefederation/core/src/com/android/tradefed/retry/BaseRetryDecision.java |
31.2.5 |
tools/asuite/atest/atest_main.py |
31.3 |
tools/asuite/atest/test_finders/ |
31.3.3 |
system/libbase/TEST_MAPPING |
31.4.2 |
frameworks/base/TEST_MAPPING |
31.4.2 |
build/soong/cc/test.go |
31.5.3, 31.10 |
build/soong/rust/test.go |
31.5.5 |
build/soong/python/test.go |
31.5.6 |
build/soong/tradefed/autogen.go |
31.5.8 |
cts/ |
31.6 |
cts/apps/CtsVerifier/ |
31.6.4 |
test/vts/ |
31.7 |
test/vts-testcase/ |
31.7.2 |
build/soong/java/ravenwood.go |
31.8 |
build/soong/java/robolectric.go |
31.9 |
external/robolectric/ |
31.9 |
external/googletest/ |
31.10 |
build/soong/cc/fuzz.go |
31.13.2 |
build/soong/fuzz/fuzz_common.go |
31.13.3 |
build/soong/java/jacoco.go |
31.14 |
external/jacoco/ |
31.14 |
external/mockito/ |
31.12.1 |
external/dexmaker/ |
31.12.3 |
platform_testing/libraries/ |
31.15 |
platform_testing/libraries/flicker/ |
31.11.5 |
platform_testing/libraries/screenshot/ |
31.11.6 |
platform_testing/libraries/systemui-tapl/ |
31.11.4 |