mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Work towards https://github.com/flutter/flutter/issues/143299.
Work towards https://github.com/flutter/flutter/issues/160043.
---
This PR implements, end-to-end, support for `matchesGoldenFile` when (a)
running with `package:integration_test` (b) on a device, such as an
Android emulator, Android device, iOS simulator, or iOS device, where
the _runner_ of a test file does not have process and local-file system
access.
There are multiple parts to this PR; I could make it smaller than 1K
lines, but the bulk of that is tests, and it would mean landing PRs that
are incomplete and unused, which does not seem useful - so instead here
is a quick overview of the PR's contents - questions/feedback welcome,
and I am willing to break code out or land incremental refactors if
requested.
1. Augmented `flutter_platform.dart` (used for iOS and Android), similar
to
[`flutter_web_platform.dart`](1398dc7eec/packages/flutter_tools/lib/src/test/flutter_web_platform.dart (L117-L128)
),
now creates and uses
[`test_golden_comparator.dart`](https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/test/test_golden_comparator.dart)
to proxy calls (coming from the VM service protocol) for golden-file
updates and comparisons to a `flutter_tester` process. A full
explanation of how (or why) it works this way is too hard to include
here, but see https://github.com/flutter/flutter/pull/160215 for more
details.
1. Added `VmServiceProxyGoldenFileComparator`, which is a currently
unused (outside of a single e2e test) comparator that forwards calls to
`compare` and `update` to the VM service protocol (of which, the other
side of this is implemented above, in `flutter_platform.dart`. The idea
is that this comparator would be used automatically when running in an
integration test on a device that requires it (similar to how web works
today), but that is **not** wired up yet and requires additional work in
`flutter_tools`.
1. Added two unit tests (of both the client and server), and a full
e2e-test using it to run `matchesGoldenFile`.
298 lines
11 KiB
Dart
298 lines
11 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:convert';
|
|
import 'dart:developer' as dev;
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:integration_test/integration_test.dart' as integration_test;
|
|
|
|
void main() {
|
|
setUp(() {
|
|
// Ensure that we reset to a throwing comparator by default.
|
|
goldenFileComparator = const _NullGoldenFileComparator();
|
|
});
|
|
|
|
group('useIfRunningOnDevice', () {
|
|
test('is skipped on the web', () {
|
|
integration_test.VmServiceProxyGoldenFileComparator.useIfRunningOnDevice();
|
|
expect(goldenFileComparator, isInstanceOf<_NullGoldenFileComparator>());
|
|
}, testOn: 'js');
|
|
|
|
test('is skipped on desktop platforms', () {
|
|
integration_test.VmServiceProxyGoldenFileComparator.useIfRunningOnDevice();
|
|
expect(goldenFileComparator, isInstanceOf<_NullGoldenFileComparator>());
|
|
}, testOn: 'windows || mac-os || linux');
|
|
|
|
test('is set on mobile platforms', () {
|
|
integration_test.VmServiceProxyGoldenFileComparator.useIfRunningOnDevice();
|
|
expect(
|
|
goldenFileComparator,
|
|
isInstanceOf<integration_test.VmServiceProxyGoldenFileComparator>(),
|
|
);
|
|
}, testOn: 'ios || android');
|
|
});
|
|
|
|
group('handleEvent', () {
|
|
late integration_test.VmServiceProxyGoldenFileComparator goldenFileComparator;
|
|
|
|
setUp(() {
|
|
goldenFileComparator = integration_test.VmServiceProxyGoldenFileComparator.forTesting(
|
|
(String operation, Map<Object?, Object?> params, {String stream = ''}) {},
|
|
);
|
|
});
|
|
|
|
test('"id" must be provided', () async {
|
|
final dev.ServiceExtensionResponse response = await goldenFileComparator.handleEvent(
|
|
<String, String>{'result': 'true'},
|
|
);
|
|
expect(response.errorDetail, contains('Required parameter "id" not present in response'));
|
|
});
|
|
|
|
test('"id" must be an integer', () async {
|
|
final dev.ServiceExtensionResponse response = await goldenFileComparator.handleEvent(
|
|
<String, String>{'id': 'not-an-integer', 'result': 'true'},
|
|
);
|
|
expect(
|
|
response.errorDetail,
|
|
stringContainsInOrder(<String>[
|
|
'Required parameter "id" not a valid integer',
|
|
'not-an-integer',
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('"id" must match a pending request (never occurred)', () async {
|
|
final dev.ServiceExtensionResponse response = await goldenFileComparator.handleEvent(
|
|
<String, String>{'id': '12345', 'result': 'true'},
|
|
);
|
|
expect(
|
|
response.errorDetail,
|
|
stringContainsInOrder(<String>['No pending request with method ID', '12345']),
|
|
);
|
|
});
|
|
|
|
test('"id" must match a pending request (already occurred)', () async {
|
|
// This is based on an implementation detail of knowing how IDs are generated.
|
|
const int nextId = 1;
|
|
goldenFileComparator.update(Uri(path: 'some-file'), Uint8List(0));
|
|
|
|
dev.ServiceExtensionResponse response;
|
|
|
|
response = await goldenFileComparator.handleEvent(<String, String>{
|
|
'id': '$nextId',
|
|
'result': 'true',
|
|
});
|
|
expect(response.errorDetail, isNull);
|
|
|
|
response = await goldenFileComparator.handleEvent(<String, String>{
|
|
'id': '$nextId',
|
|
'result': 'true',
|
|
});
|
|
expect(
|
|
response.errorDetail,
|
|
stringContainsInOrder(<String>['No pending request with method ID', '1']),
|
|
);
|
|
});
|
|
|
|
test('requests that contain "error" completes it as an error', () async {
|
|
// This is based on an implementation detail of knowing how IDs are generated.
|
|
const int nextId = 1;
|
|
|
|
expect(
|
|
goldenFileComparator.compare(Uint8List(0), Uri(path: 'some-file')),
|
|
throwsA(contains('We did a bad')),
|
|
);
|
|
|
|
final dev.ServiceExtensionResponse response = await goldenFileComparator.handleEvent(
|
|
<String, String>{'id': '$nextId', 'error': 'We did a bad'},
|
|
);
|
|
expect(response.errorDetail, isNull);
|
|
expect(response.result, '{}');
|
|
});
|
|
|
|
test('requests that do not contain "error" return an empty response', () async {
|
|
// This is based on an implementation detail of knowing how IDs are generated.
|
|
const int nextId = 1;
|
|
goldenFileComparator.update(Uri(path: 'some-file'), Uint8List(0));
|
|
|
|
final dev.ServiceExtensionResponse response = await goldenFileComparator.handleEvent(
|
|
<String, String>{'id': '$nextId', 'result': 'true'},
|
|
);
|
|
expect(response.errorDetail, isNull);
|
|
expect(response.result, '{}');
|
|
});
|
|
|
|
test('"result" must be provided if "error" is omitted', () async {
|
|
// This is based on an implementation detail of knowing how IDs are generated.
|
|
const int nextId = 1;
|
|
goldenFileComparator.update(Uri(path: 'some-file'), Uint8List(0));
|
|
|
|
final dev.ServiceExtensionResponse response = await goldenFileComparator.handleEvent(
|
|
<String, String>{'id': '$nextId'},
|
|
);
|
|
expect(response.errorDetail, contains('Required parameter "result" not present in response'));
|
|
});
|
|
|
|
test('"result" must be a boolean', () async {
|
|
// This is based on an implementation detail of knowing how IDs are generated.
|
|
const int nextId = 1;
|
|
goldenFileComparator.update(Uri(path: 'some-file'), Uint8List(0));
|
|
|
|
final dev.ServiceExtensionResponse response = await goldenFileComparator.handleEvent(
|
|
<String, String>{'id': '$nextId', 'result': 'not-a-boolean'},
|
|
);
|
|
expect(
|
|
response.errorDetail,
|
|
stringContainsInOrder(<String>[
|
|
'Required parameter "result" not a valid boolean',
|
|
'not-a-boolean',
|
|
]),
|
|
);
|
|
});
|
|
|
|
group('compare', () {
|
|
late integration_test.VmServiceProxyGoldenFileComparator goldenFileComparator;
|
|
late List<(String, Map<Object?, Object?>)> postedEvents;
|
|
|
|
setUp(() {
|
|
postedEvents = <(String, Map<Object?, Object?>)>[];
|
|
goldenFileComparator = integration_test.VmServiceProxyGoldenFileComparator.forTesting((
|
|
String operation,
|
|
Map<Object?, Object?> params, {
|
|
String stream = '',
|
|
}) {
|
|
postedEvents.add((operation, params));
|
|
});
|
|
});
|
|
|
|
test('posts an event and returns true', () async {
|
|
// This is based on an implementation detail of knowing how IDs are generated.
|
|
const int nextId = 1;
|
|
|
|
final Uint8List bytes = Uint8List.fromList(<int>[1, 2, 3, 4, 5]);
|
|
expect(goldenFileComparator.compare(bytes, Uri(path: 'golden-path')), completion(true));
|
|
|
|
await goldenFileComparator.handleEvent(<String, String>{'id': '$nextId', 'result': 'true'});
|
|
|
|
final (String event, Map<Object?, Object?> params) = postedEvents.single;
|
|
expect(event, 'compare');
|
|
expect(params, <Object?, Object?>{
|
|
'id': nextId,
|
|
'path': 'golden-path',
|
|
'bytes': base64.encode(bytes),
|
|
});
|
|
});
|
|
|
|
test('posts an event and returns false', () async {
|
|
// This is based on an implementation detail of knowing how IDs are generated.
|
|
const int nextId = 1;
|
|
|
|
final Uint8List bytes = Uint8List.fromList(<int>[1, 2, 3, 4, 5]);
|
|
expect(goldenFileComparator.compare(bytes, Uri(path: 'golden-path')), completion(false));
|
|
|
|
await goldenFileComparator.handleEvent(<String, String>{
|
|
'id': '$nextId',
|
|
'result': 'false',
|
|
});
|
|
|
|
final (String event, Map<Object?, Object?> params) = postedEvents.single;
|
|
expect(event, 'compare');
|
|
expect(params, <Object?, Object?>{
|
|
'id': nextId,
|
|
'path': 'golden-path',
|
|
'bytes': base64.encode(bytes),
|
|
});
|
|
});
|
|
|
|
test('posts an event and returns an error', () async {
|
|
// This is based on an implementation detail of knowing how IDs are generated.
|
|
const int nextId = 1;
|
|
|
|
final Uint8List bytes = Uint8List.fromList(<int>[1, 2, 3, 4, 5]);
|
|
expect(
|
|
goldenFileComparator.compare(bytes, Uri(path: 'golden-path')),
|
|
throwsA(contains('We did a bad')),
|
|
);
|
|
|
|
await goldenFileComparator.handleEvent(<String, String>{
|
|
'id': '$nextId',
|
|
'error': 'We did a bad',
|
|
});
|
|
|
|
final (String event, Map<Object?, Object?> params) = postedEvents.single;
|
|
expect(event, 'compare');
|
|
expect(params, <Object?, Object?>{
|
|
'id': nextId,
|
|
'path': 'golden-path',
|
|
'bytes': base64.encode(bytes),
|
|
});
|
|
});
|
|
});
|
|
|
|
group('update', () {
|
|
late integration_test.VmServiceProxyGoldenFileComparator goldenFileComparator;
|
|
late List<(String, Map<Object?, Object?>)> postedEvents;
|
|
|
|
setUp(() {
|
|
postedEvents = <(String, Map<Object?, Object?>)>[];
|
|
goldenFileComparator = integration_test.VmServiceProxyGoldenFileComparator.forTesting((
|
|
String operation,
|
|
Map<Object?, Object?> params, {
|
|
String stream = '',
|
|
}) {
|
|
postedEvents.add((operation, params));
|
|
});
|
|
});
|
|
|
|
test('posts an event and returns', () async {
|
|
// This is based on an implementation detail of knowing how IDs are generated.
|
|
const int nextId = 1;
|
|
|
|
final Uint8List bytes = Uint8List.fromList(<int>[1, 2, 3, 4, 5]);
|
|
expect(goldenFileComparator.update(Uri(path: 'golden-path'), bytes), completes);
|
|
|
|
await goldenFileComparator.handleEvent(<String, String>{'id': '$nextId', 'result': 'true'});
|
|
|
|
final (String event, Map<Object?, Object?> params) = postedEvents.single;
|
|
expect(event, 'update');
|
|
expect(params, <Object?, Object?>{
|
|
'id': nextId,
|
|
'path': 'golden-path',
|
|
'bytes': base64.encode(bytes),
|
|
});
|
|
});
|
|
|
|
test('posts an event and returns an error', () async {
|
|
// This is based on an implementation detail of knowing how IDs are generated.
|
|
const int nextId = 1;
|
|
|
|
final Uint8List bytes = Uint8List.fromList(<int>[1, 2, 3, 4, 5]);
|
|
expect(
|
|
goldenFileComparator.update(Uri(path: 'golden-path'), bytes),
|
|
throwsA(contains('We did a bad')),
|
|
);
|
|
|
|
await goldenFileComparator.handleEvent(<String, String>{
|
|
'id': '$nextId',
|
|
'error': 'We did a bad',
|
|
});
|
|
|
|
final (String event, Map<Object?, Object?> params) = postedEvents.single;
|
|
expect(event, 'update');
|
|
expect(params, <Object?, Object?>{
|
|
'id': nextId,
|
|
'path': 'golden-path',
|
|
'bytes': base64.encode(bytes),
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
final class _NullGoldenFileComparator with Fake implements GoldenFileComparator {
|
|
const _NullGoldenFileComparator();
|
|
}
|