The flutter/packages repository has a variety of tests; while many are relatively self-explanatory (for instance, a failure in dart_unit_tests
in presubmit can be debugged simply by running the failing Dart unit tests locally), others are less straightforward to understand. This page covers test structure and details for the repository, and how to investigate failures.
Almost every test run by CI is run via the repository tooling. In general, the CI configuration for a task is a minimal wrapper around one (or, rarely, more than one) repository tool command. This has several benefits:
CI often runs commands via the script/tool_runner.sh
script. This is just a thin wrapper that passes arguments that are commonly used for CI (such as --packages-for-branch
, which makes CI behave differently depending on the branch being run) but are unlikely to be useful when running locally. To run a failing test locally, substitute dart run script/tool/bin/flutter_plugin_tools.dart
for script/tool_runner.sh
.
flutter/packages uses the same LUCI infrastructure as most of the rest of Flutter. The main exception is the release
step, which uses GitHub Actions.
This is the CI system used by flutter/flutter and flutter/engine. For information about LUCI results pages, see Understanding a LUCI build failure.
Results for LUCI runs are available on the Packages dashboard.
LUCI tasks are configured in .ci.yaml
, and files in .ci/
. To find the commands corresponding to a given failing target and task, look in .ci.yaml
for name: your-target-name-here
, find the YAML file listed in the target_file
entry, and look in .ci/targets/that_yaml_file_name.yaml
. Each task will be an entry in that file, which runs script
(a file relative to the repository root, usually either script/tool_runner.sh
or in .ci/scripts/
), optionally with given args
.
GitHub Actions results only show up in the GitHub UI: click a check mark (passed), red X (failed) or yellow circle (running) for the list of tasks.
GitHub Actions tasks are configured in .github/workflows/
.
The overall testing structure for flutter/packages is:
Most tests are run on only one host platform: Linux when possible, or the relevant platform if necessary (e.g., Windows target tests must run on Windows hosts, and iOS and macOS tests must run on macOS hosts).
Most tests are run with both Flutter master
and Flutter stable
(see discussion of supported platforms for more details).
stable
-only failures are very rare, CI is currently configured to only run stable
in post-submit to reduce CI time and costs.Architecture coverage is as-needed; we generally don't duplicate tests across architectures. For plugins, where architecture is most likely to be an issue, we try to run:
*_platform_tests
) on the most popular architecture, andbuild_all_packages
on the other.This gives us build coverage on both architectures.
Many test commands in the repository tooling are configured to make having no tests of that type an error, to avoid cases where packages are misconfigured such that we aren't running tests that we think they are (or that an important category of tests is just forgotten). In cases where a missing test is intentional, you can add it to the relevant exclusion file, which are files that are passed to the relevant CI runs.
Exclusions must include explanatory comments, and in cases where they are not intended to be permanent, a link to an issue tracking its removal.
In addition to Dart unit tests, Dart integration tests, and for plugins the various kinds of native plugin tests, there are a number of tests that check for repository best practices or to catch problems that have affected packages in the past. As a rule of thumb, any time we find a bug or mistake only after publishing a package, we try to add a check for that in CI to prevent similar issues in the future.
Below are descriptions of many of the less self-evident CI tests, and common solution to failures. Anyone adding new tests is encouraged to document them here.
submit-queue
: This only shows in PRs (presubmit), and reflects the current state of the tree: if it is red, the tree is currently closed to commits (which can mean either that the tree is red, or that a recently landed PR is still running post-submit tests), and if it is green the tree is open. This has no relation to the PR itself, and as a PR author you do not need to do anything about it; Flutter team members monitor the state of the tree, and will handle failures.
main
to force updates, or you can reach out to a Flutter team member (in a comment on the PR, or in Discord) to override the incorrect check.*_platform_tests
: This runs each package's integration tests on the given target platform, as well as any native plugin tests for that platform. This can also include native-language-specific analysis or lint checks.
analyze
: The initial analyze
step is a straightforward Dart analyze
run, but the pathified_analyze
step is more complicated; it is intended to find issues that will cause out-of-band breakage in our own repository when the change being tested is published (most commonly with federated plugins). It does this by finding all packages that have non-breaking-version changes in the PR, and then rewriting all references to those packages in the repository to be path:
dependencies, then re-running analysis.
A failure here does not necessarily mean that the PR is wrong; because we use very strict analysis options, changes that are not considered breaking by Dart semver conventions can break our CI. Common sources of failures here include:
// ignore: deprecated_member_use
annotations in the other packages using that method, with a comment linking to the issue that tracks updating it. Once it lands, do a follow-up PR to update the calls and remove the ignore
s.// ignore: exhaustive_cases
annotations, with a comment linking to the issue that tracks updating it.ignore
.legacy_version_analyze
: Runs analyze
with older versions of Flutter than the current stable
on any package that claims to support them; see the supported version policy for details.
pubspec.yaml
to exclude the failing version(s) of Flutter.analyze_downgraded
: Runs analyze
after running a pub downgrade
, to ensure that minimum constraints are correct (e.g., that you don't add usage of an API from version x.1
of a dependency that is specified as ^x.0
in pubspec.yaml
).
*_build_all_packages
: Builds all packages into the same application, to ensure that there are no conflicts between dependencies, as we generally want clients to be able to use the latest versions of any of our packages in the same project.
exclude_all_packages_app.yaml
exclusion file (see discussion of exclusion files above).repo_checks
: Enforces various best practices (formatting, style, common mistakes, etc.). In most cases, the error messages should give clear and actionable explanations of what is wrong and how to fix it. Some general notes on specific steps:
license_script
: All code files must have the repository copyright/license block at the top. In most cases a failure here just means you forgot to add the license to a new file.federated_safety_script
: Changing interdependent sub-packages of a federated plugin in the same PR can mask serious issues, such as undesired breaking changes in platform APIs. See the documentation on changing federated plugins for next steps.The flutter/packages repository is more prone to out-of-band failures—persistent failures that do not originate with a PR within the repository—than flutter/engine and flutter/flutter. These are more difficult to debug than most failures since the source may not be obvious, and can be difficult to resolve since there's not necessarily anything that can be reverted to fix them. This page collects information on sources of these failures to help debug and resolve them.
This section doesn't cover flakes. While these failures are easily confused with flakes at first, they can be distinguished by being persistent across retries, as well as showing up in in-progress PRs.
This category covers anything that is a function of the CI infrastructure itself: services, machines, etc. It occurs across all repositories, but instances may be specific to one repository.
LUCI tasks are run on Flutter-infrastructure-managed VMs, using an out-of-repo recipe. Since LUCI images are changed by infrastructure team rollouts, and recipes are out-of-repo, almost any LUCI change is out of band. Potential failure sources include:
Integration tests on Android are run in real devices through the Firebase Test Lab infrastructure by the firebase-test-lab
command in the repository tooling. From time to time, the Firebase Test Lab will change what devices are available for testing, and tests will start timing out.
firebase_test_lab
task starts timing out. The output is normally just: Timed out!
(Example.)firebase_test_lab
timing out is almost always related to this. Either because of devices becoming unavailable, or by a temporary lack of resource availability.flutter/engine
and flutter/flutter
have had the same problem. Use the same devices picked by them.There are some interdependencies between packages in the repository (notably federated plugins, but a few other cases as well, such as examples of one package that depend on another package). Inter-package dependencies are only tested with published versions (#52115), so publishing a package can potentially break other packages.
On platforms where the plugin system includes a dependency management system, there are build-time dependencies on external servers (e.g., Maven for Android, Cocoapods for iOS and macOS). Potential failure sources include:
pub
The publish
command periodically enables new checks. Rarely, such a check will be enabled after presubmits for a PR have run, but before it's submitted.
publish
validation step or the release
step will fail with a clear error message from the publish
command.