chromium/docs/testing/test_executable_api.md

# The Chromium Test Executable API

[bit.ly/chromium-test-runner-api][1] (*)


[TOC]

## Introduction

This document defines the API that test executables must implement in order to
be run on the Chromium continuous integration infrastructure (the
[LUCI][2]
system using the `chromium` and `chromium_trybot` recipes).

*** note
**NOTE:** This document specifies the existing `isolated_scripts` API in the
Chromium recipe. Currently we also support other APIs (e.g., for
GTests), but we should migrate them to use the `isolated_scripts` API.
That work is not currently scheduled.
***

This spec applies only to functional tests and does not attempt to
specify how performance tests should work, though in principle they
could probably work the same way and possibly just produce different
output.

This document is specifically targeted at Chromium and assumes you are
using GN and Ninja for your build system. It should be possible to adapt
these APIs to other projects and build recipes, but this is not an
immediate goal. Similarly, if a project adapts this API and the related
specifications it should be able to reuse the functionality and tooling
we've built out for Chromium's CI system more easily in other LUCI
deployments.

***
**NOTE:** It bears repeating that this describes the current state of
affairs, and not the desired end state. A companion doc,
[Cleaning up the Chromium Testing Environment][3],
discusses a possible path forward and end state.
***

## Building and Invoking a Test Executable

There are lots of different kinds of tests, but we want to be able to
build and invoke them uniformly, regardless of how they are implemented.

We will call the thing being executed to run the tests a _test
executable_ (or executable for short). This is not an ideal name, as
this doesn't necessarily refer to a GN executable target type; it may be
a wrapper script that invokes other binaries or scripts to run the
tests.

We expect the test executable to run one or more tests. A _test_ must be
an atomically addressable thing with a name that is unique to that
invocation of the executable, i.e., we expect that we can pass a list of
test names to the test executable and only run just those tests. Test
names must not contain a "::" (which is used as a separator between test
names) and must not contain a "*" (which could be confused with a glob
character) or start with a "-" (which would be confused with an
indicator that you should skip the test). Test names should generally
only contain ASCII code points, as the infrastructure does not currently
guarantee that non-ASCII code points will work correctly everywhere. We
do not specify test naming conventions beyond these requirements, and it
is fully permissible for a test to contain multiple assertions which may
pass or fail; this design does not specify a way to interpret or handle
those "sub-atomic" assertions; their existence is opaque to this design.
In particular, this spec does not provide a particular way to identify
and handle parameterized tests, or to do anything with test suites
beyond a supporting a limited form of globbing for specifying sets of
test names.

To configure a new test, you need to modify one to three files:

*   The test must be listed in one or more test suites in
    [//testing/buildbot/test_suites.pyl][4].  Most commonly the test will be
    defined as a single string (e.g., "base_unittests"), which keys into an
    entry in [//testing/buildbot/gn_isolate_map.pyl][5].  In some cases, tests
    will reference a target and add additional command line arguments. These
    entries (along with [//testing/buildbot/test_suite_exceptions.pyl][6] and
    [//testing/buildbot/waterfalls.pyl][7]) determine where the tests will be
    run. For more information on how these files work, see
    [//testing/buildbot/README.md][8]
*   Tests entries must ultimately reference an entry in
    //testing/buildbot/gn_isolate_map.pyl. This file contains the mapping of
    ninja compile targets to GN targets (specifying the GN label for the
    latter); we need this mapping in order to be able to run `gn analyze`
    against a patch to see which targets are affected by a patch. This file
    also tells MB what kind of test an entry is (so we can form the correct
    command line) and may specify additional command line flags. If you are
    creating a test that is only a variant of an existing test, this may be the
    only file you need to modify. (Technically, you could define a new test
    solely in test_suites.pyl and reference existing gn_isolate_map.pyl
    entries, but this is considered bad practice).
*   Add the GN target itself to the appropriate build files. Make sure this GN
    target contains all of the data and data_deps entries needed to ensure the
    test isolate has all the files the test needs to run.  If your test doesn't
    depend on new build targets or add additional data file dependencies, you
    likely don't need this. However, this is increasingly uncommon.

### Command Line Arguments

The executable must support the following command line arguments (aka flags):

```
--isolated-outdir=[PATH]
```

This argument is required, and should be set to the directory created
by the swarming task for the task to write outputs into.

```
--out-dir=[PATH]
```

This argument mirrors `--isolated-outdir`, but may appear in addition to
it depending on the bot configuration (e.g. IOS bots that specify the
`out_dir_arg` mixin in //testing/buildbot/waterfalls.pyl). It only needs
to be handled in these cases.

```
--isolated-script-test-output=[FILENAME]
```

This argument is optional. If this argument is provided, the executable
must write the results of the test run in the [JSON Test
Results Format](json_test_results_format.md) into
that file. If this argument is not given to the executable, the
executable must not write the output anywhere. The executable should
only write a valid version of the file, and generally should only do
this at the end of the test run. This means that if the run is
interrupted, you may not get the results of what did run, but that is
acceptable.

```
--isolated-script-test-filter=[STRING]
```

This argument is optional. If this argument is provided, it must be a
double-colon-separated list of strings, where each string either
uniquely identifies a full test name or is a prefix plus a "*" on the
end (to form a glob). The executable must run only the test matching
those names or globs. "*" is _only_ supported at the end, i.e., 'Foo.*'
is legal, but '*.bar' is not. If the string has a "-" at the front, the
test (or glob of tests) must be skipped, not run. This matches how test
names are specified in the simple form of the [Chromium Test List
Format][9]. We use the double
colon as a separator because most other common punctuation characters
can occur in test names (some test suites use URLs as test names, for
example). This argument may be provided multiple times; how to treat
multiple occurrences (and how this arg interacts with
--isolated-script-test-filter-file) is described below.

```
--isolated-script-test-filter-file=[FILENAME]
```

If provided, the executable must read the given filename to determine
which tests to run and what to expect their results to be. The file must
be in the [Chromium Test List Format][9] (either the simple or
tagged formats are fine). This argument may be provided multiple times;
how to treat multiple occurrences (and how this arg interacts with
`--isolated-script-test-filter`) is described below.

```
--isolated-script-test-launcher-retry-limit=N
```

By default, tests are run only once if they succeed. If they fail, we
will retry the test up to N times (so, for N+1 total invocations of the
test) looking for a success (and stop retrying once the test has
succeed). By default, the value of N is 3. To turn off retries, pass
`--isolated-script-test-launcher-retry-limit=0`. If this flag is provided,
it is an error to also pass `--isolated-script-test-repeat` (since -repeat
specifies an explicit number of times to run the test, it makes no sense
to also pass --retry-limit).

```
--isolated-script-test-repeat=N
```

If provided, the executable must run a given test N times (total),
regardless of whether the test passes or fails. By default, tests are
only run once (N=1) if the test matches an expected result or passes,
otherwise it may be retried until it succeeds, as governed by
`--isolated-script-test-launcher-retry-limit`, above. If this flag is
provided, it is an error to also pass
`--isolated-script-test-launcher-retry-limit` (since -repeat specifies an
explicit number of times to run the test, it makes no sense to also pass
-retry-limit).

```
--xcode-build-version [VERSION]
```

This flag is passed to scripts on IOS bots only, due to the `xcode_14_main`
mixin in //testing/builtbot/waterfalls.pyl.

```
--xctest
```

This flag is passed to scripts on IOS bots only, due to the `xctest`
mixin in //testing/builtbot/waterfalls.pyl.

If "`--`" is passed as an argument:

*   If the executable is a wrapper that invokes another underlying
    executable, then the wrapper must handle arguments passed before the
    "--" on the command line (and must error out if it doesn't know how
    to do that), and must pass through any arguments following the "--"
    unmodified to the underlying executable (and otherwise ignore them
    rather than erroring out if it doesn't know how to interpret them).
*   If the executable is not a wrapper, but rather invokes the tests
    directly, it should handle all of the arguments and otherwise ignore
    the "--". The executable should error out if it gets arguments it
    can't handle, but it is not required to do so.

If "--" is not passed, the executable should error out if it gets
arguments it doesn't know how to handle, but it is not required to do
so.

If the test executable produces artifacts, they should be written to the
location specified by the dirname of the `--isolated-script-test-output`
argument). If the `--isolated-script-test-output-argument` is not
specified, the executable should store the tests somewhere under the
root_build_dir, but there is no standard for how to do this currently
(most tests do not produce artifacts).

The flag names are purposely chosen to be long in order to not conflict
with other flags the executable might support.

### Environment variables

The executable must check for and honor the following environment variables:

```
GTEST_SHARD_INDEX=[N]
```

This environment variable is optional, but if it is provided, it
partially determines (along with `GTEST_TOTAL_SHARDS`) which fixed
subset of tests (or "shard") to run. `GTEST_TOTAL_SHARDS` must also be
set, and `GTEST_SHARD_INDEX` must be set to an integer between 0 and
`GTEST_TOTAL_SHARDS`. Determining which tests to run is described
below.

```
GTEST_TOTAL_SHARDS=[N]
```

This environment variable is optional, but if it is provided, it
partially determines (along with `GTEST_TOTAL_SHARDS`) which fixed subset
of tests (or "shard") to run. It must be set to a non-zero integer.
Determining which tests to run is described below.

### Exit codes (aka return codes or return values)

The executable must return 0 for a completely successful run, and a
non-zero result if something failed. The following codes are recommended
(2 and 130 coming from UNIX conventions):

| Value    | Meaning |
|--------- | ------- |
| 0 (zero) | The executable ran to completion and all tests either ran as expected or passed unexpectedly.          |
| 1        | The executable ran to completion but some tests produced unexpectedly failing results.                 |
| 2        | The executable failed to start, most likely due to unrecognized or unsupported command line arguments. |
| 130      | The executable run was aborted the user (or caller) in a semi-orderly manner (aka SIGKILL or Ctrl-C).  |

## Filtering which tests to run

By default, the executable must run every test it knows about. However,
as noted above, the `--isolated-script-test-filter` and
`--isolated-script-test-filter-file` flags can be used to customize which
tests to run. Either or both flags may be used, and either may be
specified multiple times.

The interaction is as follows:

*   A test should be run only if it would be run when **every** flag is
    evaluated individually.
*   A test should be skipped if it would be skipped if **any** flag was
    evaluated individually.

If multiple filters in a flag match a given test name, the longest match
takes priority (longest match wins). I.e.,. if you had
`--isolated-script-test-filter='a*::-ab*'`, then `ace.html` would run but
`abd.html` would not. The order of the filters should not matter. It is
an error to have multiple expressions of the same length that conflict
(e.g., `a*::-a*`).

Examples are given below.

It may not be obvious why we need to support these flags being used multiple
times, or together. There are two main sets of reasons:
*   First, you may want to use multiple -filter-file arguments to specify
    multiple sets of test expectations (e.g., the base test expectations and
    then MSAN-specific expectations), or to specify expectations in one file
    and list which tests to run in a separate file.
*   Second, the way the Chromium recipes work, in order to retry a test step to
    confirm test failures, the recipe doesn't want to have to parse the
    existing command line, it just wants to append
    --isolated-script-test-filter and list the
    tests that fail, and this can cause the --isolated-script-test-filter
    argument to be listed multiple times (or in conjunction with
    --isolated-script-test-filter-file).

You cannot practically use these mechanisms to run equally sized subsets of the
tests, so if you want to do the latter, use `GTEST_SHARD_INDEX` and
`GTEST_TOTAL_SHARDS` instead, as described in the next section.

## Running equally-sized subsets of tests (shards)

If the `GTEST_SHARD_INDEX` and `GTEST_TOTAL_SHARDS` environment variables are
set, `GTEST_TOTAL_SHARDS` must be set to a non-zero integer N, and
`GTEST_SHARD_INDEX` must be set to an integer M between 0 and N-1. Given those
two values, the executable must run only every N<sup>th</sup> test starting at
test number M (i.e., every i<sup>th</sup> test where (i mod N) == M).  dd

This mechanism produces roughly equally-sized sets of tests that will hopefully
take roughly equal times to execute, but cannot guarantee the latter property
to any degree of precision. If you need them to be as close to the same
duration as possible, you will need a more complicated process. For example,
you could run all of the tests once to determine their individual running
times, and then build up lists of tests based on that, or do something even
more complicated based on multiple test runs to smooth over variance in test
execution times. Chromium does not currently attempt to do this for functional
tests, but we do something similar for performance tests in order to better
achieve equal running times and device affinity for consistent results.

You cannot practically use the sharding mechanism to run a stable named set of
tests, so if you want to do the latter, use the `--isolated-script-test-filter`
flags instead, as described in the previous section.

Which tests are in which shard must be determined **after** tests have been
filtered out using the `--isolated-script-test-filter(-file)` flags.

The order that tests are run in is not otherwise specified, but tests are
commonly run either in lexicographic order or in a semi-fixed random order; the
latter is useful to help identify inter-test dependencies, i.e., tests that
rely on the results of previous tests having run in order to pass (such tests
are generally considered to be undesirable).

## Examples

Assume that out/Default is a debug build (i.e., that the "Debug" tag will
apply), and that you have tests named Foo.Bar.bar{1,2,3}, Foo.Bar.baz,
and Foo.Quux.quux, and the following two filter files:

```sh
$ cat filter1
Foo.Bar.*
-Foo.Bar.bar3
$ cat filter2
# tags: [ Debug Release ]
[ Debug ] Foo.Bar.bar2 [ Skip ]
$
```

#### Filtering tests on the command line

```sh
$ out/Default/bin/run_foo_tests \
    --isolated_script-test-filter='Foo.Bar.*::-Foo.Bar.bar3'
[1/2] Foo.Bar.bar1 passed in 0.1s
[2/2] Foo.Bar.bar2 passed in 0.13s

2 tests passed in 0.23s, 0 skipped, 0 failures.
$
```

#### Using a filter file

```sh
$ out/Default/bin/run_foo_tests --isolated-script-test-filter-file=filter1
[1/2] Foo.Bar.bar1 passed in 0.1s
[2/2] Foo.Bar.bar2 passed in 0.13s

2 tests passed in 0.23s, 0 skipped, 0 failures.
```

#### Combining multiple filters

```sh
$ out/Default/bin/run_foo_tests --isolated-script-test-filter='Foo.Bar.*' \
    --isolated-script-test-filter='Foo.Bar.bar2'
[1/1] Foo.Bar.bar2 passed in 0.13s

All 2 tests completed successfully in 0.13s
$ out/Default/bin/run_foo_tests --isolated-script-test-filter='Foo.Bar.* \
    --isolated-script-test-filter='Foo.Baz.baz'
No tests to run.
$ out/Default/bin/run_foo_tests --isolated-script-test-filter-file=filter2 \
    --isolated-script-test-filter=-FooBaz.baz
[1/4] Foo.Bar.bar1 passed in 0.1s
[2/4] Foo.Bar.bar3 passed in 0.13s
[3/4] Foo.Baz.baz passed in 0.05s

3 tests passed in 0.28s, 2 skipped, 0 failures.
$
```

#### Running one shard of tests

```sh
$ GTEST_TOTAL_SHARDS=3 GTEST_SHARD_INDEX=1 out/Default/bin/run_foo_tests
Foo.Bar.bar2 passed in 0.13s
Foo.Quux.quux1 passed in 0.02s

2 tests passed in 0.15s, 0 skipped, 0 failures.
$
```

## Related Work

This document only partially makes sense in isolation.

The [JSON Test Results Format](json_test_results_format.md) document
specifies how the results of the test run should be reported.

The [Chromium Test List Format][14] specifies in more detail how we can specify
which tests to run and which to skip, and whether the tests are expected to
pass or fail.

Implementing everything in this document plus the preceding three documents
should fully specify how tests are run in Chromium. And, if we do this,
implementing tools to manage tests should be significantly easier.

[On Naming Chromium Builders and Build Steps][15] is a related proposal that
has been partially implemented; it is complementary to this work, but not
required.

[Cleaning up the Chromium Testing Conventions][3] describes a series of
changes we might want to make to this API and the related infrastructure to
simplify things.

Additional documents that may be of interest:
*   [Testing Configuration Files][8]
*   [The MB (Meta-Build wrapper) User Guide][10]
*   [The MB (Meta-Build wrapper) Design Spec][11]
*   [Test Activation / Deactivation (TADA)][12] (internal Google document only,
    sorry)
*   [Standardize Artifacts for Chromium Testing][13] is somewhat dated but goes
    into slightly greater detail on how to store artifacts produced by tests
    than the JSON Test Results Format does.

## Document history

\[ Significant changes only. \]

| Date       | Comment  |
| ---------- | -------- |
| 2017-12-13 | Initial version. This tried to be a full-featured spec that defined common flags that devs might want with friendly names, as well the flags needed to run tests on the bots. |
| 2019-05-24 | Second version. The spec was significantly revised to just specify the minimal subset needed to run tests consistently on bots given the current infrastructure. |
| 2019-05-29 | All TODOs and discussion of future work was stripped out; now the spec only specifies how the `isolated_scripts` currently behave. Future work was moved to a new doc, [Cleaning up the Chromium Testing Environment][3]. |
| 2019-09-16 | Add comment about ordering of filters and longest match winning for `--isolated-script-test-filter`. |
| 2020-07-01 | Moved into the src repo and converted to Markdown. No content changes otherwise. |

## Notes

(*) The initial version of this document talked about test runners instead of
test executables, so the bit.ly shortcut URL refers to the test-runner-api instead of
the test-executable-api. The author attempted to create a test-executable-api link,
but pointed it at the wrong document by accident. bit.ly URLs can't easily be
updated :(.

[1]: https://bit.ly/chromium-test-runner-api
[2]: https://chromium.googlesource.com/infra/infra/+/main/doc/users/services/about_luci.md
[3]: https://docs.google.com/document/d/1MwnIx8kavuLSpZo3JmL9T7nkjTz1rpaJA4Vdj_9cRYw/edit?usp=sharing
[4]: ../../testing/buildbot/test_suites.pyl
[5]: ../../testing/buildbot/gn_isolate_map.pyl
[6]: ../../testing/buildbot/test_suite_exceptions.pyl
[7]: ../../testing/buildbot/waterfalls.pyl
[8]: ../../testing/buildbot/README.md
[9]: https://bit.ly/chromium-test-list-format
[10]: ../../tools/mb/docs/user_guide.md
[11]: ../../tools/mb/docs/design_spec.md
[12]: https://goto.google.com/chops-tada
[13]: https://bit.ly/chromium-test-artifacts
[14]: https://bit.ly/chromium-test-list-format
[15]: https://bit.ly/chromium-build-naming