flutter/dev/devicelab/bin/tasks/flavors_test.dart
Mikhail Novoseltsev 937965ffae
Add integration test for Gradle-initiated android builds with flavors (#163737)
<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

This request is a subset of #157871 and follows up #162907.

#162907 actually fixed behaviour of building with native tooling not
only for iOS/macOS, but also for Android builds too: now on master, if
you start build with `gradle` instead of `flutter build ...` – you also
get the correct behaviour and `appFlavor` won't be null in runtime.
Tests in this PR will check exactly this behavior.

Another diff is the change in the method of obtaining flavor at runtime
inside test project. Before this change, method channels were used for
this, after – `appFlavor` constant. Both methods do more or less the
same thing right now, but they may diverge in future, so I guess this is
the right way to check correctness here. Also, this change was requested
and approved by Andrew in
https://github.com/flutter/flutter/pull/157871#discussion_r1931153339

issue for checklist: #155951 

New test was tested here:
https://ci.chromium.org/ui/p/flutter/builders/try/Linux_pixel_7pro%20flavors_test/63/overview


## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2025-02-24 04:40:29 +00:00

148 lines
4.7 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' show File, Platform;
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/task_result.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:flutter_devicelab/tasks/integration_tests.dart';
import 'package:path/path.dart' as path;
import 'package:standard_message_codec/standard_message_codec.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(() async {
await createFlavorsTest().call();
await createIntegrationTestFlavorsTest().call();
final String projectPath = '${flutterDirectory.path}/dev/integration_tests/flavors';
final TaskResult installTestsResult = await inDirectory(projectPath, () async {
final List<TaskResult> testResults = <TaskResult>[
await _testInstallDebugPaidFlavor(projectPath),
await _testInstallBogusFlavor(),
];
final TaskResult? firstInstallFailure = testResults.firstWhereOrNull(
(TaskResult element) => element.failed,
);
return firstInstallFailure ?? TaskResult.success(null);
});
await _testFlavorsWhenBuildStartsWithGradle(projectPath);
return installTestsResult;
});
}
// Ensures installation works. Also tests asset bundling while we are at it.
Future<TaskResult> _testInstallDebugPaidFlavor(String projectDir) async {
await evalFlutter('install', options: <String>['--debug', '--flavor', 'paid']);
final Uint8List assetManifestFileData =
File(
path.join(
projectDir,
'build',
'app',
'intermediates',
'assets',
'paidDebug',
'mergePaidDebugAssets',
'flutter_assets',
'AssetManifest.bin',
),
).readAsBytesSync();
final Map<Object?, Object?> assetManifest =
const StandardMessageCodec().decodeMessage(ByteData.sublistView(assetManifestFileData))
as Map<Object?, Object?>;
if (assetManifest.containsKey('assets/free/free.txt')) {
return TaskResult.failure(
'Expected the asset "assets/free/free.txt", which '
' was declared with a flavor of "free" to not be included in the asset bundle '
' because the --flavor was set to "paid".',
);
}
if (!assetManifest.containsKey('assets/paid/paid.txt')) {
return TaskResult.failure(
'Expected the asset "assets/paid/paid.txt", which '
' was declared with a flavor of "paid" to be included in the asset bundle '
' because the --flavor was set to "paid".',
);
}
await flutter('install', options: <String>['--debug', '--flavor', 'paid', '--uninstall-only']);
return TaskResult.success(null);
}
Future<TaskResult> _testInstallBogusFlavor() async {
final StringBuffer stderr = StringBuffer();
await evalFlutter(
'install',
canFail: true,
stderr: stderr,
options: <String>['--flavor', 'bogus'],
);
final String stderrString = stderr.toString();
final String expectedApkPath = path.join(
'build',
'app',
'outputs',
'flutter-apk',
'app-bogus-release.apk',
);
if (!stderrString.contains('"$expectedApkPath" does not exist.')) {
print(stderrString);
return TaskResult.failure('Should not succeed with bogus flavor');
}
return TaskResult.success(null);
}
Future<TaskResult> _testFlavorsWhenBuildStartsWithGradle(String projectDir) async {
final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew';
final String gradlewExecutable = Platform.isWindows ? '.\\$gradlew' : './$gradlew';
final String androidDirPath = '$projectDir/android';
final StringBuffer stdout = StringBuffer();
// Prebuild the project to generate the Android gradle wrapper files.
await inDirectory(projectDir, () async {
await flutter('build', options: <String>['apk', '--config-only']);
});
await inDirectory(androidDirPath, () async {
await exec(gradlewExecutable, <String>['clean']);
await exec(gradlewExecutable, <String>[':app:assemblePaidDebug', '--info'], output: stdout);
});
final String stdoutString = stdout.toString();
if (!stdoutString.contains('-dFlavor=paid')) {
return TaskResult.failure('Expected to see -dFlavor=paid in the gradle verbose output');
}
final String appPath = path.join(
projectDir,
'build',
'app',
'outputs',
'flutter-apk',
'app-paid-debug.apk',
);
return createFlavorsTest(
extraOptions: <String>['--flavor', 'paid', '--use-application-binary=$appPath'],
).call();
}