How GTests work on Android

gtests are 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.

APKs

GN

Gtest APKs are built by default by the test template, e.g.

test("sample_gtest") {
  # ...
}

This uses gn's native shared_library target type along with the unittest_apk 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 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-based classes (NativeUnitTestActivity, NativeUnitTestNativeActivity, and NativeBrowserTestActivity) 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, 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.
  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 template, e.g.

test("sample_gtest_executable") {
  if (is_android) {
    use_raw_android_executable = true
  }
  # ...
}

This uses gn's native executable 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.