[ci] Add partial LUCI Android platform tests (#4381)

Adds an initial Android platform tests LUCI script, and initial targets in bringup mode (using 6 shards instead of the 8 we have in Cirrus since the added shards were to try to address what in retrospect was likely a device availability issue, and since for now this will be running fewer tests; once everything is migrated we can evaluate whether we need more shards).

Rather than wait for emulator and/or FTL support in LUCI to do the migration of this target, this will partially migrate; the script currently does only the parts that don't require any kind of device. That will let us set up a baseline of LUCI Android platform tests bots to easily expand from as we figure out those pieces, and we can turn down these parts of the tests in Cirrus once these come out of bringup mode to minimize duplication.

To avoid having to run a full `flutter build` for both versions, this also updates the repo tooling to use the new `flutter build apk --config-only` option to create `gradlew` without doing a full build.

Part of https://github.com/flutter/flutter/issues/114373
This commit is contained in:
stuartmorgan
2023-07-06 18:48:01 -04:00
committed by GitHub
parent 6bc75c8b08
commit ff00264ec5
8 changed files with 272 additions and 21 deletions

View File

@ -251,6 +251,7 @@ class FirebaseTestLabCommand extends PackageLoopingCommand {
<String>[
'build',
'apk',
'--config-only',
if (experiment.isNotEmpty) '--enable-experiment=$experiment',
],
workingDir: project.androidDirectory);

View File

@ -43,9 +43,15 @@ class LintAndroidCommand extends PackageLoopingCommand {
processRunner: processRunner, platform: platform);
if (!project.isConfigured()) {
// TODO(stuartmorgan): Replace this with a --config-only build once
// that's available on stable.
return PackageResult.fail(<String>['Build examples before linting']);
final int exitCode = await processRunner.runAndStream(
flutterCommand,
<String>['build', 'apk', '--config-only'],
workingDir: example.directory,
);
if (exitCode != 0) {
printError('Unable to configure Gradle project.');
return PackageResult.fail(<String>['Unable to configure Gradle.']);
}
}
final String packageName = package.directory.basename;

View File

@ -276,7 +276,6 @@ this command.
bool ranUnitTests = false;
bool ranAnyTests = false;
bool failed = false;
bool hasMissingBuild = false;
bool hasMisconfiguredIntegrationTest = false;
// Iterate through all examples (in the rare case that there is more than
// one example); running any tests found for each one. Requirements on what
@ -311,12 +310,16 @@ this command.
platform: platform,
);
if (!project.isConfigured()) {
printError('ERROR: Run "flutter build apk" on $exampleName, or run '
'this tool\'s "build-examples --apk" command, '
'before executing tests.');
failed = true;
hasMissingBuild = true;
continue;
final int exitCode = await processRunner.runAndStream(
flutterCommand,
<String>['build', 'apk', '--config-only'],
workingDir: example.directory,
);
if (exitCode != 0) {
printError('Unable to configure Gradle project.');
failed = true;
continue;
}
}
if (runUnitTests) {
@ -379,10 +382,7 @@ this command.
}
if (failed) {
return _PlatformResult(RunState.failed,
error: hasMissingBuild
? 'Examples must be built before testing.'
: null);
return _PlatformResult(RunState.failed);
}
if (hasMisconfiguredIntegrationTest) {
return _PlatformResult(RunState.failed,

View File

@ -622,7 +622,7 @@ public class MainActivityTest {
orderedEquals(<ProcessCall>[
ProcessCall(
'flutter',
'build apk'.split(' '),
'build apk --config-only'.split(' '),
'/packages/plugin/example/android',
),
ProcessCall(

View File

@ -109,12 +109,54 @@ void main() {
]));
});
test('fails if gradlew is missing', () async {
test('runs --config-only build if gradlew is missing', () async {
final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
});
final Directory androidDir =
plugin.getExamples().first.platformDirectory(FlutterPlatform.android);
final List<String> output =
await runCapturingPrint(runner, <String>['lint-android']);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>['build', 'apk', '--config-only'],
plugin.getExamples().first.directory.path,
),
ProcessCall(
androidDir.childFile('gradlew').path,
const <String>['plugin1:lintDebug'],
androidDir.path,
),
]),
);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin1'),
contains('No issues found!'),
]));
});
test('fails if gradlew generation fails', () async {
createFakePlugin('plugin1', packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
});
processRunner
.mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
<FakeProcessInfo>[
FakeProcessInfo(MockProcess(exitCode: 1)),
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['lint-android'], errorHandler: (Error e) {
@ -126,7 +168,7 @@ void main() {
output,
containsAllInOrder(
<Matcher>[
contains('Build examples before linting'),
contains('Unable to configure Gradle project'),
],
));
});

View File

@ -773,7 +773,44 @@ public class FlutterActivityTest {
);
});
test('fails when the app needs to be built', () async {
test('runs a config-only build when the app needs to be built', () async {
final RepositoryPackage package = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'example/android/app/src/test/example_test.java',
],
);
final RepositoryPackage example = package.getExamples().first;
final Directory androidFolder =
example.platformDirectory(FlutterPlatform.android);
await runCapturingPrint(runner, <String>['native-test', '--android']);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>['build', 'apk', '--config-only'],
example.path,
),
ProcessCall(
androidFolder.childFile('gradlew').path,
const <String>[
'app:testDebugUnitTest',
'plugin:testDebugUnitTest',
],
androidFolder.path,
),
]),
);
});
test('fails when the gradlew generation fails', () async {
createFakePlugin(
'plugin',
packagesDir,
@ -785,6 +822,10 @@ public class FlutterActivityTest {
],
);
processRunner
.mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
<FakeProcessInfo>[FakeProcessInfo(MockProcess(exitCode: 1))];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['native-test', '--android'],
@ -797,9 +838,7 @@ public class FlutterActivityTest {
expect(
output,
containsAllInOrder(<Matcher>[
contains('ERROR: Run "flutter build apk" on plugin/example'),
contains('plugin:\n'
' Examples must be built before testing.')
contains('Unable to configure Gradle project'),
]),
);
});