mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Fix local testing, gradle XML errors, and enable on CI. (#152383)
TIL you cannot have XML comments before the initial `<?xml` declaration. Follow-up to https://github.com/flutter/flutter/pull/152326.
This commit is contained in:
parent
7f4f1a0a62
commit
4ff9462be8
6
.ci.yaml
6
.ci.yaml
@ -1297,11 +1297,15 @@ targets:
|
|||||||
- name: Linux_android_emu flutter_driver_android_test
|
- name: Linux_android_emu flutter_driver_android_test
|
||||||
recipe: flutter/flutter_drone
|
recipe: flutter/flutter_drone
|
||||||
timeout: 60
|
timeout: 60
|
||||||
bringup: true
|
bringup: false
|
||||||
properties:
|
properties:
|
||||||
shard: flutter_driver_android
|
shard: flutter_driver_android
|
||||||
tags: >
|
tags: >
|
||||||
["framework", "hostonly", "shard", "linux"]
|
["framework", "hostonly", "shard", "linux"]
|
||||||
|
dependencies: >-
|
||||||
|
[
|
||||||
|
{"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"}
|
||||||
|
]
|
||||||
|
|
||||||
- name: Linux realm_checker
|
- name: Linux realm_checker
|
||||||
recipe: flutter/flutter_drone
|
recipe: flutter/flutter_drone
|
||||||
|
@ -2,18 +2,50 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io' as io;
|
||||||
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import '../run_command.dart';
|
import '../run_command.dart';
|
||||||
import '../utils.dart';
|
import '../utils.dart';
|
||||||
|
|
||||||
|
/// To run this test locally:
|
||||||
|
///
|
||||||
|
/// 1. Connect an Android device or emulator.
|
||||||
|
/// 2. Run the following command from the root of the Flutter repository:
|
||||||
|
///
|
||||||
|
/// ```sh
|
||||||
|
/// SHARD=flutter_driver_android bin/cache/dart-sdk/bin/dart dev/bots/test.dart
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// For debugging, it is recommended to instead just run and launch these tests
|
||||||
|
/// individually _in_ the `dev/integration_tests/android_driver_test` directory.
|
||||||
Future<void> runFlutterDriverAndroidTests() async {
|
Future<void> runFlutterDriverAndroidTests() async {
|
||||||
print('Running Flutter Driver Android tests...');
|
print('Running Flutter Driver Android tests...');
|
||||||
|
|
||||||
|
// Print out the results of `adb devices`, for uh, science:
|
||||||
|
print('Listing devices...');
|
||||||
|
final io.ProcessResult devices = await _adb(
|
||||||
|
<String>[
|
||||||
|
'devices',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
print(devices.stdout);
|
||||||
|
print(devices.stderr);
|
||||||
|
|
||||||
|
// We need to configure the emulator to disable confirmations before the
|
||||||
|
// application starts. Some of these configuration options won't work once
|
||||||
|
// the application is running.
|
||||||
|
print('Configuring device...');
|
||||||
|
await _configureForScreenshotTesting();
|
||||||
|
|
||||||
// TODO(matanlurey): Should we be using another instrumentation method?
|
// TODO(matanlurey): Should we be using another instrumentation method?
|
||||||
await runCommand(
|
await runCommand(
|
||||||
'flutter',
|
'flutter',
|
||||||
<String>[
|
<String>[
|
||||||
'drive',
|
'drive',
|
||||||
|
'--test-arguments=test',
|
||||||
|
'--test-arguments=--reporter=expanded',
|
||||||
],
|
],
|
||||||
workingDirectory: path.join(
|
workingDirectory: path.join(
|
||||||
'dev',
|
'dev',
|
||||||
@ -22,3 +54,60 @@ Future<void> runFlutterDriverAndroidTests() async {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(matanlurey): Move this code into flutter_driver instead of here.
|
||||||
|
Future<void> _configureForScreenshotTesting() async {
|
||||||
|
// Disable confirmation for immersive mode.
|
||||||
|
final io.ProcessResult immersive = await _adb(
|
||||||
|
<String>[
|
||||||
|
'shell',
|
||||||
|
'settings',
|
||||||
|
'put',
|
||||||
|
'secure',
|
||||||
|
'immersive_mode_confirmations',
|
||||||
|
'confirmed',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (immersive.exitCode != 0) {
|
||||||
|
throw StateError('Failed to configure device: ${immersive.stderr}');
|
||||||
|
}
|
||||||
|
|
||||||
|
const Map<String, String> settings = <String, String>{
|
||||||
|
'show_surface_updates': '1',
|
||||||
|
'transition_animation_scale': '0',
|
||||||
|
'window_animation_scale': '0',
|
||||||
|
'animator_duration_scale': '0',
|
||||||
|
};
|
||||||
|
|
||||||
|
for (final MapEntry<String, String> entry in settings.entries) {
|
||||||
|
final io.ProcessResult result = await _adb(
|
||||||
|
<String>[
|
||||||
|
'shell',
|
||||||
|
'settings',
|
||||||
|
'put',
|
||||||
|
'global',
|
||||||
|
entry.key,
|
||||||
|
entry.value,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.exitCode != 0) {
|
||||||
|
throw StateError('Failed to configure device: ${result.stderr}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<io.ProcessResult> _adb(
|
||||||
|
List<String> args, {
|
||||||
|
Encoding? stdoutEncoding = io.systemEncoding,
|
||||||
|
}) {
|
||||||
|
// TODO(matanlurey): Ideally we should specify the device target here.
|
||||||
|
return io.Process.run(
|
||||||
|
'adb',
|
||||||
|
<String>[
|
||||||
|
...args,
|
||||||
|
],
|
||||||
|
stdoutEncoding: stdoutEncoding,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
Use of this source code is governed by a BSD-style license that can be
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
found in the LICENSE file. -->
|
found in the LICENSE file. -->
|
||||||
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="?android:colorBackground" />
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
Use of this source code is governed by a BSD-style license that can be
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
found in the LICENSE file. -->
|
found in the LICENSE file. -->
|
||||||
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="@android:color/white" />
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
Use of this source code is governed by a BSD-style license that can be
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
found in the LICENSE file. -->
|
found in the LICENSE file. -->
|
||||||
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
Use of this source code is governed by a BSD-style license that can be
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
found in the LICENSE file. -->
|
found in the LICENSE file. -->
|
||||||
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
@ -19,6 +19,8 @@ import 'package:platform/platform.dart';
|
|||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
const LocalFileSystem _localFs = LocalFileSystem();
|
const LocalFileSystem _localFs = LocalFileSystem();
|
||||||
|
const String _kGoldctlKey = 'GOLDCTL';
|
||||||
|
const String _kGoldctlPresubmitKey = 'GOLD_TRYJOB';
|
||||||
|
|
||||||
// TODO(matanlurey): Refactor flutter_goldens to just re-use that code instead.
|
// TODO(matanlurey): Refactor flutter_goldens to just re-use that code instead.
|
||||||
Future<void> testExecutable(
|
Future<void> testExecutable(
|
||||||
@ -31,19 +33,41 @@ Future<void> testExecutable(
|
|||||||
'where the "goldenFileComparator" has not yet been set. This is to ensure '
|
'where the "goldenFileComparator" has not yet been set. This is to ensure '
|
||||||
'that the correct comparator is used for the current test environment.',
|
'that the correct comparator is used for the current test environment.',
|
||||||
);
|
);
|
||||||
final io.Directory tmpDir = io.Directory.systemTemp.createTempSync('android_driver_test');
|
if (!io.Platform.environment.containsKey(_kGoldctlKey)) {
|
||||||
goldenFileComparator = _GoldenFileComparator(
|
io.stderr.writeln(
|
||||||
SkiaGoldClient(
|
'Environment variable $_kGoldctlKey is not set. Assuming this is a local '
|
||||||
_localFs.directory(tmpDir.path),
|
'test run and will not upload results to Skia Gold.',
|
||||||
fs: _localFs,
|
);
|
||||||
process: const LocalProcessManager(),
|
return testMain();
|
||||||
platform: const LocalPlatform(),
|
}
|
||||||
httpClient: io.HttpClient(),
|
final io.Directory tmpDir = io.Directory.systemTemp.createTempSync(
|
||||||
log: io.stderr.writeln,
|
'android_driver_test',
|
||||||
),
|
|
||||||
namePrefix: namePrefix,
|
|
||||||
isPresubmit: false,
|
|
||||||
);
|
);
|
||||||
|
final bool isPresubmit = io.Platform.environment.containsKey(
|
||||||
|
_kGoldctlPresubmitKey,
|
||||||
|
);
|
||||||
|
io.stderr.writeln(
|
||||||
|
'=== Using Skia Gold ===\n'
|
||||||
|
'Environment variable $_kGoldctlKey is set, using Skia Gold: \n'
|
||||||
|
' - tmpDir: ${tmpDir.path}\n'
|
||||||
|
' - namePrefix: $namePrefix\n'
|
||||||
|
' - isPresubmit: $isPresubmit\n',
|
||||||
|
);
|
||||||
|
final SkiaGoldClient skiaGoldClient = SkiaGoldClient(
|
||||||
|
_localFs.directory(tmpDir.path),
|
||||||
|
fs: _localFs,
|
||||||
|
process: const LocalProcessManager(),
|
||||||
|
platform: const LocalPlatform(),
|
||||||
|
httpClient: io.HttpClient(),
|
||||||
|
log: io.stderr.writeln,
|
||||||
|
);
|
||||||
|
await skiaGoldClient.auth();
|
||||||
|
goldenFileComparator = _GoldenFileComparator(
|
||||||
|
skiaGoldClient,
|
||||||
|
namePrefix: namePrefix,
|
||||||
|
isPresubmit: isPresubmit,
|
||||||
|
);
|
||||||
|
return testMain();
|
||||||
}
|
}
|
||||||
|
|
||||||
final class _GoldenFileComparator extends GoldenFileComparator {
|
final class _GoldenFileComparator extends GoldenFileComparator {
|
||||||
@ -68,11 +92,22 @@ final class _GoldenFileComparator extends GoldenFileComparator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
golden = _addPrefix(golden);
|
golden = _addPrefix(golden);
|
||||||
await update(golden, imageBytes);
|
final io.File goldenFile = await update(golden, imageBytes);
|
||||||
|
|
||||||
final io.File goldenFile = _getGoldenFile(golden);
|
|
||||||
if (isPresubmit) {
|
if (isPresubmit) {
|
||||||
await skiaClient.tryjobAdd(golden.path, _localFs.file(goldenFile.path));
|
final String? result = await skiaClient.tryjobAdd(
|
||||||
|
golden.path,
|
||||||
|
_localFs.file(goldenFile.path),
|
||||||
|
);
|
||||||
|
if (result != null) {
|
||||||
|
io.stderr.writeln(
|
||||||
|
'Skia Gold detected an error when comparing "$golden":\n\n$result',
|
||||||
|
);
|
||||||
|
io.stderr.writeln('Still succeeding, will be triaged in Flutter Gold');
|
||||||
|
} else {
|
||||||
|
io.stderr.writeln(
|
||||||
|
'Skia Gold comparison succeeded comparing "$golden".',
|
||||||
|
);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return skiaClient.imgtestAdd(golden.path, _localFs.file(goldenFile.path));
|
return skiaClient.imgtestAdd(golden.path, _localFs.file(goldenFile.path));
|
||||||
@ -80,10 +115,14 @@ final class _GoldenFileComparator extends GoldenFileComparator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> update(Uri golden, Uint8List imageBytes) async {
|
Future<io.File> update(Uri golden, Uint8List imageBytes) async {
|
||||||
|
io.stderr.writeln(
|
||||||
|
'Updating golden file: $golden (${imageBytes.length} bytes)...',
|
||||||
|
);
|
||||||
final io.File goldenFile = _getGoldenFile(golden);
|
final io.File goldenFile = _getGoldenFile(golden);
|
||||||
await goldenFile.parent.create(recursive: true);
|
await goldenFile.parent.create(recursive: true);
|
||||||
await goldenFile.writeAsBytes(imageBytes, flush: true);
|
await goldenFile.writeAsBytes(imageBytes, flush: true);
|
||||||
|
return goldenFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
io.File _getGoldenFile(Uri uri) {
|
io.File _getGoldenFile(Uri uri) {
|
||||||
|
@ -84,33 +84,6 @@ final class AndroidNativeDriver implements NativeDriver {
|
|||||||
await _tmpDir.delete(recursive: true);
|
await _tmpDir.delete(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> configureForScreenshotTesting() async {
|
|
||||||
const Map<String, String> settings = <String, String>{
|
|
||||||
'show_surface_updates': '1',
|
|
||||||
'transition_animation_scale': '0',
|
|
||||||
'window_animation_scale': '0',
|
|
||||||
'animator_duration_scale': '0',
|
|
||||||
};
|
|
||||||
|
|
||||||
for (final MapEntry<String, String> entry in settings.entries) {
|
|
||||||
final io.ProcessResult result = await _adb(
|
|
||||||
<String>[
|
|
||||||
'shell',
|
|
||||||
'settings',
|
|
||||||
'put',
|
|
||||||
'global',
|
|
||||||
entry.key,
|
|
||||||
entry.value,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.exitCode != 0) {
|
|
||||||
throw StateError('Failed to configure device: ${result.stderr}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<NativeScreenshot> screenshot() async {
|
Future<NativeScreenshot> screenshot() async {
|
||||||
// Similar pause to the one in `<FlutterDriver>.screenshot()`.
|
// Similar pause to the one in `<FlutterDriver>.screenshot()`.
|
||||||
|
@ -26,16 +26,6 @@ abstract interface class NativeDriver {
|
|||||||
/// After calling this method, the driver is no longer usable.
|
/// After calling this method, the driver is no longer usable.
|
||||||
Future<void> close();
|
Future<void> close();
|
||||||
|
|
||||||
/// Configures the device for screenshot testing.
|
|
||||||
///
|
|
||||||
/// Where possible, this method should suppress system UI elements that are
|
|
||||||
/// not part of the application under test, such as the status bar or
|
|
||||||
/// navigation bar, and disable animations that might interfere with
|
|
||||||
/// screenshot comparison.
|
|
||||||
///
|
|
||||||
/// The exact details of what is configured are platform-specific.
|
|
||||||
Future<void> configureForScreenshotTesting();
|
|
||||||
|
|
||||||
/// Take a screenshot using a platform-specific mechanism.
|
/// Take a screenshot using a platform-specific mechanism.
|
||||||
///
|
///
|
||||||
/// The image is returned as an opaque handle that can be used to retrieve
|
/// The image is returned as an opaque handle that can be used to retrieve
|
||||||
|
@ -331,7 +331,11 @@ class SkiaGoldClient {
|
|||||||
///
|
///
|
||||||
/// The [testName] and [goldenFile] parameters reference the current
|
/// The [testName] and [goldenFile] parameters reference the current
|
||||||
/// comparison being evaluated by the [FlutterPreSubmitFileComparator].
|
/// comparison being evaluated by the [FlutterPreSubmitFileComparator].
|
||||||
Future<void> tryjobAdd(String testName, File goldenFile) async {
|
///
|
||||||
|
/// If the tryjob fails due to pixel differences, the method will succeed
|
||||||
|
/// as the failure will be triaged in the 'Flutter Gold' dashboard, and the
|
||||||
|
/// `stdout` will contain the failure message; otherwise will return `null`.
|
||||||
|
Future<String?> tryjobAdd(String testName, File goldenFile) async {
|
||||||
final List<String> imgtestCommand = <String>[
|
final List<String> imgtestCommand = <String>[
|
||||||
_goldctl,
|
_goldctl,
|
||||||
'imgtest', 'add',
|
'imgtest', 'add',
|
||||||
@ -368,6 +372,7 @@ class SkiaGoldClient {
|
|||||||
..writeln('result-state.json: ${resultContents ?? 'No result file found.'}');
|
..writeln('result-state.json: ${resultContents ?? 'No result file found.'}');
|
||||||
throw SkiaException(buf.toString());
|
throw SkiaException(buf.toString());
|
||||||
}
|
}
|
||||||
|
return result.exitCode == 0 ? null : resultStdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructs arguments for `goldctl` for controlling how pixels are compared.
|
// Constructs arguments for `goldctl` for controlling how pixels are compared.
|
||||||
|
@ -1182,7 +1182,7 @@ class FakeSkiaGoldClient extends Fake implements SkiaGoldClient {
|
|||||||
@override
|
@override
|
||||||
Future<void> tryjobInit() async => tryInitCalls += 1;
|
Future<void> tryjobInit() async => tryInitCalls += 1;
|
||||||
@override
|
@override
|
||||||
Future<bool> tryjobAdd(String testName, File goldenFile) async => true;
|
Future<String?> tryjobAdd(String testName, File goldenFile) async => null;
|
||||||
|
|
||||||
Map<String, List<int>> imageBytesValues = <String, List<int>>{};
|
Map<String, List<int>> imageBytesValues = <String, List<int>>{};
|
||||||
@override
|
@override
|
||||||
|
Loading…
Reference in New Issue
Block a user