133 lines
7.3 KiB
Markdown
133 lines
7.3 KiB
Markdown
# How GTests work on Android
|
|
|
|
gtests are [googletest](https://github.com/google/googletest)-based C++ tests.
|
|
On Android, they run on a device. In most cases, they're packaged as APKs, but
|
|
there are a few cases where they're run as raw executables. The latter is
|
|
necessary in a few cases, particularly when manipulating signal handlers, but
|
|
isn't possible when the suite needs to call back through the JNI into Java code.
|
|
|
|
[TOC]
|
|
|
|
## APKs
|
|
|
|
### GN
|
|
|
|
Gtest APKs are built by default by the
|
|
[test](https://codesearch.chromium.org/chromium/src/testing/test.gni?type=cs&q=file:%5Esrc%5C/testing%5C/test.gni$+template%5C("test"%5C)&sq=package:chromium)
|
|
template, e.g.
|
|
|
|
```python
|
|
test("sample_gtest") {
|
|
# ...
|
|
}
|
|
```
|
|
|
|
This uses gn's native
|
|
[shared_library](https://chromium.googlesource.com/chromium/src/+/main/tools/gn/docs/reference.md#shared_library_Declare-a-shared-library-target)
|
|
target type along with the
|
|
[unittest_apk](https://codesearch.chromium.org/chromium/src/build/config/android/rules.gni?type=cs&q=file:%5Esrc%5C/build%5C/config%5C/android%5C/rules.gni$+template%5C(%5C"unittest_apk%5C"%5C)&sq=package:chromium)
|
|
template to build an APK containing:
|
|
|
|
- One or more .so files containing the native code on which the test suite
|
|
depends
|
|
- One or more .dex files containing the Java code on which the test suite
|
|
depends
|
|
- A [manifest](https://developer.android.com/guide/topics/manifest/manifest-intro.html)
|
|
file that contains `<instrumentation>` and `<activity>` elements (among others).
|
|
|
|
### Harness
|
|
|
|
GTest APKs are packaged with a harness that consists of:
|
|
|
|
- [NativeTestInstrumentationTestRunner], an instrumentation entry point that
|
|
handles running one or more sequential instances of a test Activity. Typically,
|
|
unit test suites will only use one instance of the Activity and will run all of
|
|
the specified tests in it, while browser test suites will use multiple instances
|
|
and will only run one test per instance.
|
|
- Three [Activity](https://developer.android.com/reference/android/app/Activity.html)-based
|
|
classes
|
|
([NativeUnitTestActivity](https://codesearch.chromium.org/chromium/src/testing/android/native_test/java/src/org/chromium/native_test/NativeUnitTestActivity.java),
|
|
[NativeUnitTestNativeActivity](https://codesearch.chromium.org/chromium/src/testing/android/native_test/java/src/org/chromium/native_test/NativeUnitTestNativeActivity.java),
|
|
and
|
|
[NativeBrowserTestActivity](https://codesearch.chromium.org/chromium/src/testing/android/native_test/java/src/org/chromium/native_test/NativeBrowserTestActivity.java))
|
|
that primarily act as process entry points for individual test shards.
|
|
Only one is used in any given suite.
|
|
- [NativeTest] and [NativeUnitTest],
|
|
which handle formatting arguments for googletest and transferring control across
|
|
the JNI.
|
|
- [testing::android::RunTests](https://codesearch.chromium.org/chromium/src/testing/android/native_test/native_test_launcher.cc?q=file:%5Esrc%5C/testing%5C/android%5C/native_test%5C/native_test_launcher.cc$+RunTests&sq=package:chromium),
|
|
the function on the native side, which initializes the native command-line,
|
|
redirects stdout either to a FIFO or a regular file, optionally waits for a
|
|
debugger to attach to the process, sets up the test data directories, and then
|
|
dispatches to googletest's `main` function.
|
|
|
|
### Runtime
|
|
|
|
1. The test runner calls `am instrument` with a bunch of arguments,
|
|
includes several extras that are arguments to either
|
|
[NativeTestInstrumentationTestRunner] or [NativeTest]. This results in an
|
|
intent being sent to [NativeTestInstrumentationTestRunner].
|
|
2. [NativeTestInstrumentationTestRunner] is created. In its onCreate, it
|
|
parses its own arguments from the intent and retains all other arguments
|
|
to be passed to the Activities it'll start later. It also creates a
|
|
temporary file in the external storage directory for stdout. It finally
|
|
starts itself.
|
|
3. [NativeTestInstrumentationTestRunner] is started. In its onStart, it prepares
|
|
to receive notifications about the start and end of the test run from the
|
|
Activities it's about to start. It then creates [ShardStarter]
|
|
that will start the first test shard and adds that to the current
|
|
[Handler](https://developer.android.com/reference/android/os/Handler.html).
|
|
4. The [ShardStarter] is executed, starting the test Activity.
|
|
5. The Activity starts, possibly doing some process initialization, and hands
|
|
off to the [NativeTest].
|
|
6. The [NativeTest] handles some initialization and informs the
|
|
[NativeTestInstrumentationTestRunner] that it has started. On hearing this,
|
|
the [NativeTestInstrumentationTestRunner] creates a [ShardMonitor]
|
|
that will monitor the execution of the test Activity.
|
|
7. The [NativeTest] hands off to testing::android::RunTests. The tests run.
|
|
8. The [NativeTest] informs the [NativeTestInstrumentationTestRunner] that is has
|
|
completed. On hearing this, the [ShardMonitor] creates a [ShardEnder].
|
|
9. The [ShardEnder] is executed, killing the child process (if applicable),
|
|
parsing the results from the stdout file, and either launching the next
|
|
shard via [ShardStarter] (in which case the process returns to #4) or sending
|
|
the results out to the test runner and finishing the instrumentation.
|
|
|
|
## Executables
|
|
|
|
### GN
|
|
|
|
Gtest executables are built by passing
|
|
`use_raw_android_executable = True` to the
|
|
[test](https://codesearch.chromium.org/chromium/src/testing/test.gni?type=cs&q=file:%5Esrc%5C/testing%5C/test.gni$+template%5C("test"%5C)&sq=package:chromium)
|
|
template, e.g.
|
|
|
|
```python
|
|
test("sample_gtest_executable") {
|
|
if (is_android) {
|
|
use_raw_android_executable = true
|
|
}
|
|
# ...
|
|
}
|
|
```
|
|
|
|
This uses gn's native
|
|
[executable](https://chromium.googlesource.com/chromium/src/+/main/tools/gn/docs/reference.md#executable_Declare-an-executable-target)
|
|
target type, then copies the resulting executable and any requisite shared libraries
|
|
to ```${root_out_dir}/${target_name}__dist``` (e.g. ```out/Debug/breakpad_unittests__dist```).
|
|
|
|
### Harness
|
|
|
|
Unlike APKs, gtest suites built as executables require no Android-specific harnesses.
|
|
|
|
### Runtime
|
|
|
|
The test runner simply executes the binary on the device directly and parses the
|
|
stdout on its own.
|
|
|
|
[NativeTest]: https://codesearch.chromium.org/chromium/src/testing/android/native_test/java/src/org/chromium/native_test/NativeTest.java
|
|
[NativeTestInstrumentationTestRunner]: https://codesearch.chromium.org/chromium/src/testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java
|
|
[NativeUnitTest]: https://codesearch.chromium.org/chromium/src/testing/android/native_test/java/src/org/chromium/native_test/NativeUnitTest.java
|
|
[ShardEnder]: https://codesearch.chromium.org/chromium/src/testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java?q=file:NativeTestInstrumentationTestRunner.java+class:ShardEnder&sq=package:chromium
|
|
[ShardMonitor]: https://codesearch.chromium.org/chromium/src/testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java?q=file:NativeTestInstrumentationTestRunner.java+class:ShardMonitor&sq=package:chromium
|
|
[ShardStarter]: https://codesearch.chromium.org/chromium/src/testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java?q=file:NativeTestInstrumentationTestRunner.java+class:ShardStarter&sq=package:chromium
|