Make the Skia expectation parser more resilient and update to "master_str". (#62898)
diff --git a/packages/flutter_goldens/test/flutter_goldens_test.dart b/packages/flutter_goldens/test/flutter_goldens_test.dart
index 919aa93..5045a09 100644
--- a/packages/flutter_goldens/test/flutter_goldens_test.dart
+++ b/packages/flutter_goldens/test/flutter_goldens_test.dart
@@ -34,6 +34,17 @@
120, 1, 99, 249, 207, 240, 255, 63, 0, 7, 18, 3, 2, 164, 147, 160, 197, 0,
0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130];
+Future<void> testWithOutput(String name, Future<void> body(), String expectedOutput) async {
+ test(name, () async {
+ final StringBuffer output = StringBuffer();
+ void _recordPrint(Zone self, ZoneDelegate parent, Zone zone, String line) {
+ output.write(line);
+ }
+ await runZoned<Future<void>>(body, zoneSpecification: ZoneSpecification(print: _recordPrint));
+ expect(output.toString(), expectedOutput);
+ });
+}
+
void main() {
MemoryFileSystem fs;
FakePlatform platform;
@@ -341,6 +352,24 @@
);
});
+ test('sets up expectations with temporary key', () async {
+ url = Uri.parse('https://flutter-gold.skia.org/json/expectations/commit/HEAD');
+ final MockHttpClientResponse mockHttpResponse = MockHttpClientResponse(
+ utf8.encode(rawExpectationsTemplateWithTemporaryKey())
+ );
+ when(mockHttpClient.getUrl(url))
+ .thenAnswer((_) => Future<MockHttpClientRequest>.value(mockHttpRequest));
+ when(mockHttpRequest.close())
+ .thenAnswer((_) => Future<MockHttpClientResponse>.value(mockHttpResponse));
+
+ await skiaClient.getExpectations();
+ expect(skiaClient.expectations, isNotNull);
+ expect(
+ skiaClient.expectations['flutter.golden_test.1'],
+ contains(expectation),
+ );
+ });
+
test('detects invalid digests SkiaDigest', () {
const String testName = 'flutter.golden_test.2';
final Map<String, dynamic> skiaJson = json.decode(digestResponseTemplate()) as Map<String, dynamic>;
@@ -851,7 +880,7 @@
);
});
- test('passes non-existent baseline for new test', () async {
+ testWithOutput('passes non-existent baseline for new test', () async {
when(mockSkiaClient.cleanTestName('library.flutter.new_golden_test.1.png'))
.thenReturn('flutter.new_golden_test.1');
expect(
@@ -861,7 +890,8 @@
),
isTrue,
);
- });
+ }, 'No expectations provided by Skia Gold for test: library.flutter.new_golden_test.1.png. '
+ 'This may be a new test. If this is an unexpected result, check https://flutter-gold.skia.org.\n');
});
});
@@ -937,7 +967,7 @@
);
});
- test('passes non-existent baseline for new test', () async {
+ testWithOutput('passes non-existent baseline for new test', () async {
expect(
await comparator.compare(
Uint8List.fromList(_kFailPngBytes),
@@ -945,7 +975,10 @@
),
isTrue,
);
- });
+ }, 'No expectations provided by Skia Gold for test: library.flutter.new_golden_test.1. '
+ 'This may be a new test. If this is an unexpected result, check https://flutter-gold.skia.org.\n'
+ 'Validate image output found at flutter/test/library/'
+ );
test('compare properly awaits validation & output before failing.', () async {
final Completer<bool> completer = Completer<bool>();
diff --git a/packages/flutter_goldens/test/json_templates.dart b/packages/flutter_goldens/test/json_templates.dart
index f5f96ad..65bb70a 100644
--- a/packages/flutter_goldens/test/json_templates.dart
+++ b/packages/flutter_goldens/test/json_templates.dart
@@ -58,6 +58,29 @@
};
}
+/// Same as [rawExpectationsTemplate] but with the temporary key.
+String rawExpectationsTemplateWithTemporaryKey() {
+ return '''
+ {
+ "md5": "a7489b00e03a1846e43500b7c14dd7b0",
+ "master_str": {
+ "flutter.golden_test.1": {
+ "55109a4bed52acc780530f7a9aeff6c0": 1
+ },
+ "flutter.golden_test.3": {
+ "87cb35131e6ad4b57d4d09d59ae743c3": 1,
+ "dc94eb2c39c0c8ae11a4efd090b72f94": 1,
+ "f2583c9003978a06b7888878bdc089e2": 1
+ },
+ "flutter.golden_test.2": {
+ "eb03a5e3114c9ecad5e4f1178f285a49": 1,
+ "f14631979de24fca6e14ad247d5f2bd6": 1
+ }
+ }
+ }
+ ''';
+}
+
/// Json response template for Skia Gold digest request:
/// https://flutter-gold.skia.org/json/details?test=[testName]&digest=[expectation]
String digestResponseTemplate({
diff --git a/packages/flutter_goldens_client/lib/skia_client.dart b/packages/flutter_goldens_client/lib/skia_client.dart
index 912524d..0710eeb 100644
--- a/packages/flutter_goldens_client/lib/skia_client.dart
+++ b/packages/flutter_goldens_client/lib/skia_client.dart
@@ -438,20 +438,30 @@
final Uri requestForExpectations = Uri.parse(
'https://flutter-gold.skia.org/json/expectations/commit/HEAD'
);
+ const String mainKey = 'master';
+ const String temporaryKey = 'master_str';
String rawResponse;
try {
final io.HttpClientRequest request = await httpClient.getUrl(requestForExpectations);
final io.HttpClientResponse response = await request.close();
rawResponse = await utf8.decodeStream(response);
- final Map<String, dynamic> skiaJson = json.decode(rawResponse)['master'] as Map<String, dynamic>;
-
+ final dynamic jsonResponse = json.decode(rawResponse);
+ if (jsonResponse is! Map<String, dynamic>)
+ throw const FormatException('Skia gold expectations do not match expected format.');
+ final Map<String, dynamic> skiaJson = (jsonResponse[mainKey] ?? jsonResponse[temporaryKey]) as Map<String, dynamic>;
+ if (skiaJson == null)
+ throw FormatException('Skia gold expectations are missing the "$mainKey" key (and also doesn\'t have "$temporaryKey")! Available keys: ${jsonResponse.keys.join(", ")}');
skiaJson.forEach((String key, dynamic value) {
final Map<String, dynamic> hashesMap = value as Map<String, dynamic>;
_expectations[key] = hashesMap.keys.toList();
});
- } on FormatException catch(_) {
- print('Formatting error detected requesting expectations from Flutter Gold.\n'
- 'rawResponse: $rawResponse');
+ } on FormatException catch (error) {
+ print(
+ 'Formatting error detected requesting expectations from Flutter Gold.\n'
+ 'error: $error\n'
+ 'url: $requestForExpectations\n'
+ 'response: $rawResponse'
+ );
rethrow;
}
},