blob: ddee374eee0d7ad8943a7eecd6d7bb7b430ed10b [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Todd Volkert65079ad2018-05-03 07:39:41 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
Kate Lovett9e3e44e2019-04-19 11:32:16 -07004
Todd Volkert65079ad2018-05-03 07:39:41 -07005import 'dart:async';
Kate Lovettb1ca7f42019-11-15 13:04:59 -08006import 'dart:io' as io;
Kate Lovett1374a412019-11-27 11:29:53 -08007import 'dart:math' as math;
Todd Volkert65079ad2018-05-03 07:39:41 -07008import 'dart:typed_data';
9
Todd Volkert65079ad2018-05-03 07:39:41 -070010import 'package:file/file.dart';
11import 'package:file/local.dart';
12import 'package:flutter_test/flutter_test.dart';
13import 'package:meta/meta.dart';
Kate Lovett616794f2019-07-28 12:26:06 -070014import 'package:platform/platform.dart';
Todd Volkert65079ad2018-05-03 07:39:41 -070015
Kate Lovett616794f2019-07-28 12:26:06 -070016import 'package:flutter_goldens_client/skia_client.dart';
Kate Lovett616794f2019-07-28 12:26:06 -070017export 'package:flutter_goldens_client/skia_client.dart';
Kate Lovett7e542fc2019-04-19 14:33:49 -070018
Kate Lovett7bc02032019-10-25 15:05:21 -070019// If you are here trying to figure out how to use golden files in the Flutter
20// repo itself, consider reading this wiki page:
21// https://github.com/flutter/flutter/wiki/Writing-a-golden-file-test-for-package%3Aflutter
22
23const String _kFlutterRootKey = 'FLUTTER_ROOT';
24
Todd Volkert65079ad2018-05-03 07:39:41 -070025/// Main method that can be used in a `flutter_test_config.dart` file to set
26/// [goldenFileComparator] to an instance of [FlutterGoldenFileComparator] that
Kate Lovett616794f2019-07-28 12:26:06 -070027/// works for the current test. _Which_ FlutterGoldenFileComparator is
28/// instantiated is based on the current testing environment.
Todd Volkert65079ad2018-05-03 07:39:41 -070029Future<void> main(FutureOr<void> testMain()) async {
Kate Lovett616794f2019-07-28 12:26:06 -070030 const Platform platform = LocalPlatform();
Kate Lovett7bc02032019-10-25 15:05:21 -070031 if (FlutterSkiaGoldFileComparator.isAvailableForEnvironment(platform)) {
32 goldenFileComparator = await FlutterSkiaGoldFileComparator.fromDefaultComparator(platform);
33 } else if (FlutterPreSubmitFileComparator.isAvailableForEnvironment(platform)) {
34 goldenFileComparator = await FlutterPreSubmitFileComparator.fromDefaultComparator(platform);
Kate Lovettb1ca7f42019-11-15 13:04:59 -080035 } else if (FlutterSkippingGoldenFileComparator.isAvailableForEnvironment(platform)) {
36 goldenFileComparator = FlutterSkippingGoldenFileComparator.fromDefaultComparator(
37 'Golden file testing is unavailable on LUCI and some Cirrus shards.'
38 );
Kate Lovett7bc02032019-10-25 15:05:21 -070039 } else {
40 goldenFileComparator = await FlutterLocalFileComparator.fromDefaultComparator(platform);
Kate Lovett616794f2019-07-28 12:26:06 -070041 }
Kate Lovettb1ca7f42019-11-15 13:04:59 -080042
Todd Volkert65079ad2018-05-03 07:39:41 -070043 await testMain();
44}
45
Kate Lovett616794f2019-07-28 12:26:06 -070046/// Abstract base class golden file comparator specific to the `flutter/flutter`
47/// repository.
Kate Lovett7bc02032019-10-25 15:05:21 -070048///
49/// Golden file testing for the `flutter/flutter` repository is handled by three
50/// different [FlutterGoldenFileComparator]s, depending on the current testing
51/// environment.
52///
53/// * The [FlutterSkiaGoldFileComparator] is utilized during post-submit
54/// testing, after a pull request has landed on the master branch. This
55/// comparator uses the [SkiaGoldClient] and the `goldctl` tool to upload
56/// tests to the [Flutter Gold dashboard](https://flutter-gold.skia.org).
57/// Flutter Gold manages the master golden files for the `flutter/flutter`
58/// repository.
59///
60/// * The [FlutterPreSubmitFileComparator] is utilized in pre-submit testing,
61/// before a pull request can land on the master branch. This comparator
62/// uses the [SkiaGoldClient] to request the baseline images kept by the
63/// [Flutter Gold dashboard](https://flutter-gold.skia.org). It then
64/// compares the current test image to the baseline images using the
65/// standard [GoldenFileComparator.compareLists] to detect any pixel
66/// difference. The [SkiaGoldClient] is also used here to check the active
67/// ignores from the dashboard, in order to allow intended changes to pass
68/// tests.
69///
70/// * The [FlutterLocalFileComparator] is used for any other tests run outside
71/// of the above conditions. Similar to the
72/// [FlutterPreSubmitFileComparator], this comparator will use the
73/// [SkiaGoldClient] to request baseline images from
74/// [Flutter Gold](https://flutter-gold.skia.org) and compares for the
75/// current test image. If a difference is detected, this comparator will
76/// generate failure output illustrating the found difference. If a baseline
77/// is not found for a given test image, it will consider it a new test and
78/// output the new image for verification.
79/// The [FlutterSkippingGoldenFileComparator] is utilized to skip tests outside
80/// of the appropriate environments. Currently, tests executing in post-submit
81/// on the LUCI build environment are skipped, as post-submit checks are done
Kate Lovettb1ca7f42019-11-15 13:04:59 -080082/// on Cirrus. This comparator is also used when an internet connection is
83/// unavailable.
Kate Lovett616794f2019-07-28 12:26:06 -070084abstract class FlutterGoldenFileComparator extends GoldenFileComparator {
85 /// Creates a [FlutterGoldenFileComparator] that will resolve golden file
Kate Lovett7bc02032019-10-25 15:05:21 -070086 /// URIs relative to the specified [basedir], and retrieve golden baselines
87 /// using the [skiaClient]. The [basedir] is used for writing and accessing
88 /// information and files for interacting with the [skiaClient]. When testing
89 /// locally, the [basedir] will also contain any diffs from failed tests, or
90 /// goldens generated from newly introduced tests.
Kate Lovett616794f2019-07-28 12:26:06 -070091 ///
Kate Lovett7bc02032019-10-25 15:05:21 -070092 /// The [fs] and [platform] parameters are useful in tests, where the default
93 /// file system and platform can be replaced by mock instances.
Kate Lovett616794f2019-07-28 12:26:06 -070094 @visibleForTesting
95 FlutterGoldenFileComparator(
Kate Lovett7bc02032019-10-25 15:05:21 -070096 this.basedir,
97 this.skiaClient, {
Kate Lovett616794f2019-07-28 12:26:06 -070098 this.fs = const LocalFileSystem(),
99 this.platform = const LocalPlatform(),
100 }) : assert(basedir != null),
Kate Lovett7bc02032019-10-25 15:05:21 -0700101 assert(skiaClient != null),
Kate Lovett616794f2019-07-28 12:26:06 -0700102 assert(fs != null),
103 assert(platform != null);
104
105 /// The directory to which golden file URIs will be resolved in [compare] and
Kate Lovett7bc02032019-10-25 15:05:21 -0700106 /// [update], cannot be null.
Kate Lovett616794f2019-07-28 12:26:06 -0700107 final Uri basedir;
108
Kate Lovett7bc02032019-10-25 15:05:21 -0700109 /// A client for uploading image tests and making baseline requests to the
110 /// Flutter Gold Dashboard, cannot be null.
111 final SkiaGoldClient skiaClient;
112
Kate Lovett616794f2019-07-28 12:26:06 -0700113 /// The file system used to perform file access.
114 @visibleForTesting
115 final FileSystem fs;
116
117 /// A wrapper for the [dart:io.Platform] API.
118 @visibleForTesting
119 final Platform platform;
120
121 @override
122 Future<void> update(Uri golden, Uint8List imageBytes) async {
123 final File goldenFile = getGoldenFile(golden);
124 await goldenFile.parent.create(recursive: true);
125 await goldenFile.writeAsBytes(imageBytes, flush: true);
126 }
127
Kate Lovett7bc02032019-10-25 15:05:21 -0700128 @override
129 Uri getTestUri(Uri key, int version) => key;
130
Kate Lovett616794f2019-07-28 12:26:06 -0700131 /// Calculate the appropriate basedir for the current test context.
Kate Lovett9011cec2019-12-04 15:43:36 -0800132 ///
133 /// The optional [suffix] argument is used by the
134 /// [FlutterSkiaGoldFileComparator] and the [FlutterPreSubmitFileComparator].
135 /// These [FlutterGoldenFileComparators] randomize their base directories to
136 /// maintain thread safety while using the `goldctl` tool.
Kate Lovett616794f2019-07-28 12:26:06 -0700137 @protected
138 @visibleForTesting
Kate Lovett1c15cd82020-02-05 11:03:02 -0800139 static Directory getBaseDirectory(
140 LocalFileComparator defaultComparator,
141 Platform platform, {
142 String suffix = '',
143 bool local = false,
144 }) {
Kate Lovett7bc02032019-10-25 15:05:21 -0700145 const FileSystem fs = LocalFileSystem();
146 final Directory flutterRoot = fs.directory(platform.environment[_kFlutterRootKey]);
Kate Lovett1c15cd82020-02-05 11:03:02 -0800147 Directory comparisonRoot;
148
149 if (!local) {
150 comparisonRoot = fs.systemTempDirectory.childDirectory(
151 'skia_goldens$suffix'
152 );
153 } else {
154 comparisonRoot = flutterRoot.childDirectory(
155 fs.path.join(
156 'bin',
157 'cache',
158 'pkg',
159 'skia_goldens',
160 )
161 );
162 }
163
Kate Lovett616794f2019-07-28 12:26:06 -0700164 final Directory testDirectory = fs.directory(defaultComparator.basedir);
Kate Lovett7bc02032019-10-25 15:05:21 -0700165 final String testDirectoryRelativePath = fs.path.relative(
166 testDirectory.path,
167 from: flutterRoot.path,
168 );
169 return comparisonRoot.childDirectory(testDirectoryRelativePath);
Kate Lovett616794f2019-07-28 12:26:06 -0700170 }
171
172 /// Returns the golden [File] identified by the given [Uri].
173 @protected
174 File getGoldenFile(Uri uri) {
Kate Lovett616794f2019-07-28 12:26:06 -0700175 final File goldenFile = fs.directory(basedir).childFile(fs.file(uri).path);
Kate Lovett616794f2019-07-28 12:26:06 -0700176 return goldenFile;
177 }
Kate Lovett616794f2019-07-28 12:26:06 -0700178
Greg Spencera60bf8e2019-11-22 08:43:55 -0800179 /// Prepends the golden URL with the library name that encloses the current
Kate Lovett7bc02032019-10-25 15:05:21 -0700180 /// test.
181 Uri _addPrefix(Uri golden) {
182 final String prefix = basedir.pathSegments[basedir.pathSegments.length - 2];
183 return Uri.parse(prefix + '.' + golden.toString());
Todd Volkert65079ad2018-05-03 07:39:41 -0700184 }
Kate Lovett616794f2019-07-28 12:26:06 -0700185}
186
187/// A [FlutterGoldenFileComparator] for testing golden images with Skia Gold.
188///
189/// For testing across all platforms, the [SkiaGoldClient] is used to upload
190/// images for framework-related golden tests and process results. Currently
191/// these tests are designed to be run post-submit on Cirrus CI, informed by the
192/// environment.
193///
194/// See also:
195///
196/// * [GoldenFileComparator], the abstract class that
197/// [FlutterGoldenFileComparator] implements.
Kate Lovett7bc02032019-10-25 15:05:21 -0700198/// * [FlutterPreSubmitFileComparator], another
199/// [FlutterGoldenFileComparator] that tests golden images before changes are
200/// merged into the master branch.
201/// * [FlutterLocalFileComparator], another
202/// [FlutterGoldenFileComparator] that tests golden images locally on your
203/// current machine.
Kate Lovett616794f2019-07-28 12:26:06 -0700204class FlutterSkiaGoldFileComparator extends FlutterGoldenFileComparator {
205 /// Creates a [FlutterSkiaGoldFileComparator] that will test golden file
206 /// images against Skia Gold.
207 ///
Kate Lovett7bc02032019-10-25 15:05:21 -0700208 /// The [fs] and [platform] parameters are useful in tests, where the default
209 /// file system and platform can be replaced by mock instances.
Kate Lovett616794f2019-07-28 12:26:06 -0700210 FlutterSkiaGoldFileComparator(
211 final Uri basedir,
Kate Lovett7bc02032019-10-25 15:05:21 -0700212 final SkiaGoldClient skiaClient, {
213 final FileSystem fs = const LocalFileSystem(),
214 final Platform platform = const LocalPlatform(),
Kate Lovett616794f2019-07-28 12:26:06 -0700215 }) : super(
216 basedir,
Kate Lovett7bc02032019-10-25 15:05:21 -0700217 skiaClient,
Kate Lovett616794f2019-07-28 12:26:06 -0700218 fs: fs,
219 platform: platform,
220 );
221
Kate Lovett616794f2019-07-28 12:26:06 -0700222 /// Creates a new [FlutterSkiaGoldFileComparator] that mirrors the relative
223 /// path resolution of the default [goldenFileComparator].
224 ///
225 /// The [goldens] and [defaultComparator] parameters are visible for testing
226 /// purposes only.
Kate Lovett7bc02032019-10-25 15:05:21 -0700227 static Future<FlutterSkiaGoldFileComparator> fromDefaultComparator(
228 final Platform platform, {
Kate Lovett616794f2019-07-28 12:26:06 -0700229 SkiaGoldClient goldens,
230 LocalFileComparator defaultComparator,
231 }) async {
Kate Lovett8df0d652019-10-21 16:45:56 -0700232
Alexandre Ardhuin1f3ff5c2019-11-21 17:46:37 +0100233 defaultComparator ??= goldenFileComparator as LocalFileComparator;
Kate Lovett7bc02032019-10-25 15:05:21 -0700234 final Directory baseDirectory = FlutterGoldenFileComparator.getBaseDirectory(
235 defaultComparator,
236 platform,
Kate Lovett1374a412019-11-27 11:29:53 -0800237 suffix: '${math.Random().nextInt(10000)}',
Kate Lovett7bc02032019-10-25 15:05:21 -0700238 );
Kate Lovett9011cec2019-12-04 15:43:36 -0800239 baseDirectory.createSync(recursive: true);
Kate Lovett7bc02032019-10-25 15:05:21 -0700240
241 goldens ??= SkiaGoldClient(baseDirectory);
242 await goldens.auth();
Kate Lovett616794f2019-07-28 12:26:06 -0700243 await goldens.imgtestInit();
244 return FlutterSkiaGoldFileComparator(baseDirectory.uri, goldens);
Todd Volkert65079ad2018-05-03 07:39:41 -0700245 }
Kate Lovetteb0b1792019-07-12 12:23:04 -0700246
Kate Lovett616794f2019-07-28 12:26:06 -0700247 @override
248 Future<bool> compare(Uint8List imageBytes, Uri golden) async {
249 golden = _addPrefix(golden);
250 await update(golden, imageBytes);
Kate Lovett3a3939a2019-10-21 17:31:54 -0700251 final File goldenFile = getGoldenFile(golden);
Kate Lovett616794f2019-07-28 12:26:06 -0700252
Kate Lovett7bc02032019-10-25 15:05:21 -0700253 return skiaClient.imgtestAdd(golden.path, goldenFile);
254 }
Kate Lovett3a3939a2019-10-21 17:31:54 -0700255
Kate Lovett616794f2019-07-28 12:26:06 -0700256 /// Decides based on the current environment whether goldens tests should be
257 /// performed against Skia Gold.
Kate Lovett7bc02032019-10-25 15:05:21 -0700258 static bool isAvailableForEnvironment(Platform platform) {
Kate Lovett616794f2019-07-28 12:26:06 -0700259 final String cirrusPR = platform.environment['CIRRUS_PR'] ?? '';
260 final String cirrusBranch = platform.environment['CIRRUS_BRANCH'] ?? '';
Kate Lovett26d09f12019-10-31 11:44:07 -0700261 return platform.environment.containsKey('CIRRUS_CI')
Kate Lovett616794f2019-07-28 12:26:06 -0700262 && cirrusPR.isEmpty
263 && cirrusBranch == 'master'
Kate Lovett26d09f12019-10-31 11:44:07 -0700264 && platform.environment.containsKey('GOLD_SERVICE_ACCOUNT');
Kate Lovett616794f2019-07-28 12:26:06 -0700265 }
Kate Lovett7bc02032019-10-25 15:05:21 -0700266}
Kate Lovett3a3939a2019-10-21 17:31:54 -0700267
Kate Lovett7bc02032019-10-25 15:05:21 -0700268/// A [FlutterGoldenFileComparator] for testing golden images before changes are
269/// merged into the master branch.
270///
271/// This comparator utilizes the [SkiaGoldClient] to request baseline images for
272/// the given device under test for comparison. This comparator is only
273/// initialized during pre-submit testing on Cirrus CI.
274///
275/// See also:
276///
277/// * [GoldenFileComparator], the abstract class that
278/// [FlutterGoldenFileComparator] implements.
279/// * [FlutterSkiaGoldFileComparator], another
280/// [FlutterGoldenFileComparator] that uploads tests to the Skia Gold
281/// dashboard.
282/// * [FlutterLocalFileComparator], another
283/// [FlutterGoldenFileComparator] that tests golden images locally on your
284/// current machine.
285class FlutterPreSubmitFileComparator extends FlutterGoldenFileComparator {
286 /// Creates a [FlutterPreSubmitFileComparator] that will test golden file
287 /// images against baselines requested from Flutter Gold.
288 ///
289 /// The [fs] and [platform] parameters are useful in tests, where the default
290 /// file system and platform can be replaced by mock instances.
291 FlutterPreSubmitFileComparator(
292 final Uri basedir,
293 final SkiaGoldClient skiaClient, {
294 final FileSystem fs = const LocalFileSystem(),
295 final Platform platform = const LocalPlatform(),
296 }) : super(
297 basedir,
298 skiaClient,
299 fs: fs,
300 platform: platform,
301 );
302
303 /// Creates a new [FlutterPreSubmitFileComparator] that mirrors the
304 /// relative path resolution of the default [goldenFileComparator].
305 ///
306 /// The [goldens] and [defaultComparator] parameters are visible for testing
307 /// purposes only.
308 static Future<FlutterGoldenFileComparator> fromDefaultComparator(
309 final Platform platform, {
310 SkiaGoldClient goldens,
311 LocalFileComparator defaultComparator,
Kate Lovett84aa29c2020-01-09 08:08:03 -0800312 final Directory testBasedir,
Kate Lovett7bc02032019-10-25 15:05:21 -0700313 }) async {
314
Alexandre Ardhuin1f3ff5c2019-11-21 17:46:37 +0100315 defaultComparator ??= goldenFileComparator as LocalFileComparator;
Kate Lovett84aa29c2020-01-09 08:08:03 -0800316 final Directory baseDirectory = testBasedir ?? FlutterGoldenFileComparator.getBaseDirectory(
Kate Lovett7bc02032019-10-25 15:05:21 -0700317 defaultComparator,
318 platform,
Kate Lovett9011cec2019-12-04 15:43:36 -0800319 suffix: '${math.Random().nextInt(10000)}',
Kate Lovett7bc02032019-10-25 15:05:21 -0700320 );
Kate Lovett84aa29c2020-01-09 08:08:03 -0800321
322 if (!baseDirectory.existsSync())
323 baseDirectory.createSync(recursive: true);
Kate Lovett7bc02032019-10-25 15:05:21 -0700324
325 goldens ??= SkiaGoldClient(baseDirectory);
Kate Lovett84aa29c2020-01-09 08:08:03 -0800326
327 final bool hasWritePermission = !platform.environment['GOLD_SERVICE_ACCOUNT'].startsWith('ENCRYPTED');
328 if (hasWritePermission) {
329 await goldens.auth();
330 await goldens.tryjobInit();
331 return _AuthorizedFlutterPreSubmitComparator(
332 baseDirectory.uri,
333 goldens,
334 platform: platform,
335 );
336 }
337
338 goldens.emptyAuth();
339 return _UnauthorizedFlutterPreSubmitComparator(
340 baseDirectory.uri,
341 goldens,
342 platform: platform,
343 );
Kate Lovett7bc02032019-10-25 15:05:21 -0700344 }
345
346 @override
347 Future<bool> compare(Uint8List imageBytes, Uri golden) async {
Kate Lovett84aa29c2020-01-09 08:08:03 -0800348 assert(
349 false,
350 'The FlutterPreSubmitFileComparator has been used to execute a golden '
351 'file test; this should never happen. Presubmit golden file testing '
352 'should be executed by either the _AuthorizedFlutterPreSubmitComparator '
353 'or the _UnauthorizedFlutterPreSubmitComparator based on contributor '
354 'permissions.'
355 );
356 return false;
357 }
358
359 /// Decides based on the current environment whether goldens tests should be
360 /// performed as pre-submit tests with Skia Gold.
361 static bool isAvailableForEnvironment(Platform platform) {
362 final String cirrusPR = platform.environment['CIRRUS_PR'] ?? '';
363 return platform.environment.containsKey('CIRRUS_CI')
364 && cirrusPR.isNotEmpty
365 && platform.environment.containsKey('GOLD_SERVICE_ACCOUNT');
366 }
367}
368
369class _AuthorizedFlutterPreSubmitComparator extends FlutterPreSubmitFileComparator {
370 _AuthorizedFlutterPreSubmitComparator(
371 final Uri basedir,
372 final SkiaGoldClient skiaClient, {
373 final FileSystem fs = const LocalFileSystem(),
374 final Platform platform = const LocalPlatform(),
375 }) : super(
376 basedir,
377 skiaClient,
378 fs: fs,
379 platform: platform,
380 );
381
382 @override
383 Future<bool> compare(Uint8List imageBytes, Uri golden) async {
Kate Lovett7bc02032019-10-25 15:05:21 -0700384 golden = _addPrefix(golden);
Kate Lovett9011cec2019-12-04 15:43:36 -0800385 await update(golden, imageBytes);
386 final File goldenFile = getGoldenFile(golden);
Kate Lovett7bc02032019-10-25 15:05:21 -0700387
Kate Lovett9011cec2019-12-04 15:43:36 -0800388 return skiaClient.tryjobAdd(golden.path, goldenFile);
Kate Lovett7bc02032019-10-25 15:05:21 -0700389 }
Kate Lovett84aa29c2020-01-09 08:08:03 -0800390}
Kate Lovett7bc02032019-10-25 15:05:21 -0700391
Kate Lovett84aa29c2020-01-09 08:08:03 -0800392class _UnauthorizedFlutterPreSubmitComparator extends FlutterPreSubmitFileComparator {
393 _UnauthorizedFlutterPreSubmitComparator(
394 final Uri basedir,
395 final SkiaGoldClient skiaClient, {
396 final FileSystem fs = const LocalFileSystem(),
397 final Platform platform = const LocalPlatform(),
398 }) : super(
399 basedir,
400 skiaClient,
401 fs: fs,
402 platform: platform,
403 );
404
405 @override
406 Future<bool> compare(Uint8List imageBytes, Uri golden) async {
407 golden = _addPrefix(golden);
408 await update(golden, imageBytes);
409 final File goldenFile = getGoldenFile(golden);
410
411 // Check for match to existing baseline.
412 if (await skiaClient.imgtestCheck(golden.path, goldenFile))
413 return true;
414
415 // We do not have a matching image, so we need to check a few things
416 // manually. We wait until this point to do this work so request traffic
417 // low.
418 skiaClient.getExpectations();
419 final String testName = skiaClient.cleanTestName(golden.path);
420 final List<String> testExpectations = skiaClient.expectations[testName];
421 if (testExpectations == null) {
422 // This is a new test.
423 print('No expectations provided by Skia Gold for test: $golden. '
424 'This may be a new test. If this is an unexpected result, check '
425 'https://flutter-gold.skia.org.\n'
426 );
427 return true;
428 }
429
430 // Contributors without the proper permissions to execute a tryjob can make
431 // a golden file change through Gold's ignore feature instead.
432 final bool ignoreResult = await skiaClient.testIsIgnoredForPullRequest(
433 platform.environment['CIRRUS_PR'] ?? '',
434 golden.path,
435 );
436 // If true, this is an intended change.
437 return ignoreResult;
Kate Lovett26d09f12019-10-31 11:44:07 -0700438 }
439}
440
441/// A [FlutterGoldenFileComparator] for controlling post-submit testing
442/// conditions that do not execute golden file tests.
443///
444/// Currently, this comparator is used in post-submit checks on LUCI and with
Kate Lovettb1ca7f42019-11-15 13:04:59 -0800445/// some Cirrus shards that do not run framework tests. This comparator is also
446/// used when an internet connection is not available for contacting Gold.
Kate Lovett26d09f12019-10-31 11:44:07 -0700447///
448/// See also:
449///
450/// * [FlutterGoldensRepositoryFileComparator], another
451/// [FlutterGoldenFileComparator] that tests golden images using the
452/// flutter/goldens repository.
453/// * [FlutterSkiaGoldFileComparator], another [FlutterGoldenFileComparator]
454/// that tests golden images through Skia Gold.
455/// * [FlutterPreSubmitFileComparator], another
456/// [FlutterGoldenFileComparator] that tests golden images before changes are
457/// merged into the master branch.
458/// * [FlutterLocalFileComparator], another
459/// [FlutterGoldenFileComparator] that tests golden images locally on your
460/// current machine.
461class FlutterSkippingGoldenFileComparator extends FlutterGoldenFileComparator {
462 /// Creates a [FlutterSkippingGoldenFileComparator] that will skip tests that
463 /// are not in the right environment for golden file testing.
464 FlutterSkippingGoldenFileComparator(
465 final Uri basedir,
466 final SkiaGoldClient skiaClient,
Kate Lovettb1ca7f42019-11-15 13:04:59 -0800467 this.reason,
468 ) : assert(reason != null),
469 super(basedir, skiaClient);
470
471 /// Describes the reason for using the [FlutterSkippingGoldenFileComparator].
472 ///
473 /// Cannot be null.
474 final String reason;
Kate Lovett26d09f12019-10-31 11:44:07 -0700475
476 /// Creates a new [FlutterSkippingGoldenFileComparator] that mirrors the
477 /// relative path resolution of the default [goldenFileComparator].
Kate Lovettb1ca7f42019-11-15 13:04:59 -0800478 static FlutterSkippingGoldenFileComparator fromDefaultComparator(
479 String reason, {
Kate Lovett26d09f12019-10-31 11:44:07 -0700480 LocalFileComparator defaultComparator,
481 }) {
Alexandre Ardhuin1f3ff5c2019-11-21 17:46:37 +0100482 defaultComparator ??= goldenFileComparator as LocalFileComparator;
Kate Lovett26d09f12019-10-31 11:44:07 -0700483 const FileSystem fs = LocalFileSystem();
484 final Uri basedir = defaultComparator.basedir;
485 final SkiaGoldClient skiaClient = SkiaGoldClient(fs.directory(basedir));
Kate Lovettb1ca7f42019-11-15 13:04:59 -0800486 return FlutterSkippingGoldenFileComparator(basedir, skiaClient, reason);
Kate Lovett26d09f12019-10-31 11:44:07 -0700487 }
488
489 @override
490 Future<bool> compare(Uint8List imageBytes, Uri golden) async {
491 print(
Kate Lovettb1ca7f42019-11-15 13:04:59 -0800492 'Skipping "$golden" test : $reason'
Kate Lovett26d09f12019-10-31 11:44:07 -0700493 );
494 return true;
495 }
496
497 @override
498 Future<void> update(Uri golden, Uint8List imageBytes) => null;
499
500 /// Decides based on the current environment whether this comparator should be
501 /// used.
502 static bool isAvailableForEnvironment(Platform platform) {
Kate Lovett84aa29c2020-01-09 08:08:03 -0800503 return platform.environment.containsKey('SWARMING_TASK_ID')
504 || platform.environment.containsKey('CIRRUS_CI');
Kate Lovett3a3939a2019-10-21 17:31:54 -0700505 }
Kate Lovett616794f2019-07-28 12:26:06 -0700506}
507
Kate Lovett7bc02032019-10-25 15:05:21 -0700508/// A [FlutterGoldenFileComparator] for testing golden images locally on your
509/// current machine.
510///
511/// This comparator utilizes the [SkiaGoldClient] to request baseline images for
Kate Lovett26d09f12019-10-31 11:44:07 -0700512/// the given device under test for comparison. This comparator is initialized
513/// when conditions for all other [FlutterGoldenFileComparators] have not been
514/// met, see the `isAvailableForEnvironment` method for each one listed below.
515///
516/// The [FlutterLocalFileComparator] is intended to run on local machines and
517/// serve as a smoke test during development. As such, it will not be able to
518/// detect unintended changes on environments other than the currently executing
519/// machine, until they are tested using the [FlutterPreSubmitFileComparator].
Kate Lovett7bc02032019-10-25 15:05:21 -0700520///
521/// See also:
522///
523/// * [GoldenFileComparator], the abstract class that
524/// [FlutterGoldenFileComparator] implements.
525/// * [FlutterSkiaGoldFileComparator], another
526/// [FlutterGoldenFileComparator] that uploads tests to the Skia Gold
527/// dashboard.
528/// * [FlutterPreSubmitFileComparator], another
529/// [FlutterGoldenFileComparator] that tests golden images before changes are
530/// merged into the master branch.
Kate Lovett26d09f12019-10-31 11:44:07 -0700531/// * [FlutterSkippingGoldenFileComparator], another
532/// [FlutterGoldenFileComparator] that controls post-submit testing
533/// conditions that do not execute golden file tests.
Kate Lovett7bc02032019-10-25 15:05:21 -0700534class FlutterLocalFileComparator extends FlutterGoldenFileComparator with LocalComparisonOutput {
535 /// Creates a [FlutterLocalFileComparator] that will test golden file
536 /// images against baselines requested from Flutter Gold.
537 ///
538 /// The [fs] and [platform] parameters are useful in tests, where the default
539 /// file system and platform can be replaced by mock instances.
540 FlutterLocalFileComparator(
541 final Uri basedir,
542 final SkiaGoldClient skiaClient, {
543 final FileSystem fs = const LocalFileSystem(),
544 final Platform platform = const LocalPlatform(),
545 }) : super(
546 basedir,
547 skiaClient,
548 fs: fs,
549 platform: platform,
550 );
551
552 /// Creates a new [FlutterLocalFileComparator] that mirrors the
553 /// relative path resolution of the default [goldenFileComparator].
554 ///
Kate Lovettb1ca7f42019-11-15 13:04:59 -0800555 /// The [goldens], [defaultComparator], and [baseDirectory] parameters are
556 /// visible for testing purposes only.
Kate Lovett7bc02032019-10-25 15:05:21 -0700557 static Future<FlutterGoldenFileComparator> fromDefaultComparator(
558 final Platform platform, {
559 SkiaGoldClient goldens,
560 LocalFileComparator defaultComparator,
Kate Lovettb1ca7f42019-11-15 13:04:59 -0800561 Directory baseDirectory,
Kate Lovett7bc02032019-10-25 15:05:21 -0700562 }) async {
Alexandre Ardhuin1f3ff5c2019-11-21 17:46:37 +0100563 defaultComparator ??= goldenFileComparator as LocalFileComparator;
Kate Lovettb1ca7f42019-11-15 13:04:59 -0800564 baseDirectory ??= FlutterGoldenFileComparator.getBaseDirectory(
Kate Lovett7bc02032019-10-25 15:05:21 -0700565 defaultComparator,
566 platform,
Kate Lovett1c15cd82020-02-05 11:03:02 -0800567 local: true,
Kate Lovett7bc02032019-10-25 15:05:21 -0700568 );
569
570 if(!baseDirectory.existsSync()) {
571 baseDirectory.createSync(recursive: true);
572 }
573
574 goldens ??= SkiaGoldClient(baseDirectory);
Kate Lovettb1ca7f42019-11-15 13:04:59 -0800575
576 try {
577 await goldens.getExpectations();
578 } on io.OSError catch (_) {
579 return FlutterSkippingGoldenFileComparator(
580 baseDirectory.uri,
581 goldens,
Kate Lovett6397c022020-01-10 14:43:01 -0800582 'OSError occurred, could not reach Gold. '
583 'Switching to FlutterSkippingGoldenFileComparator.',
584 );
585 } on io.SocketException catch (_) {
586 return FlutterSkippingGoldenFileComparator(
587 baseDirectory.uri,
588 goldens,
589 'SocketException occurred, could not reach Gold. '
590 'Switching to FlutterSkippingGoldenFileComparator.',
Kate Lovettb1ca7f42019-11-15 13:04:59 -0800591 );
592 }
Kate Lovett7bc02032019-10-25 15:05:21 -0700593
594 return FlutterLocalFileComparator(baseDirectory.uri, goldens);
595 }
596
597 @override
598 Future<bool> compare(Uint8List imageBytes, Uri golden) async {
599 golden = _addPrefix(golden);
600 final String testName = skiaClient.cleanTestName(golden.path);
601 final List<String> testExpectations = skiaClient.expectations[testName];
602 if (testExpectations == null) {
603 // There is no baseline for this test
604 print('No expectations provided by Skia Gold for test: $golden. '
605 'This may be a new test. If this is an unexpected result, check '
606 'https://flutter-gold.skia.org.\n'
607 'Validate image output found at $basedir'
608 );
609 update(golden, imageBytes);
610 return true;
611 }
612
613 ComparisonResult result;
614 final Map<String, ComparisonResult> failureDiffs = <String, ComparisonResult>{};
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100615 for (final String expectation in testExpectations) {
Kate Lovett7bc02032019-10-25 15:05:21 -0700616 final List<int> goldenBytes = await skiaClient.getImageBytes(expectation);
617
618 result = GoldenFileComparator.compareLists(
619 imageBytes,
620 goldenBytes,
621 );
622
623 if (result.passed) {
624 return true;
625 }
626 failureDiffs[expectation] = result;
627 }
Kate Lovettdfbbfcd2019-11-07 13:01:32 -0800628
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100629 for (final MapEntry<String, ComparisonResult> entry in failureDiffs.entries) {
Kate Lovettdfbbfcd2019-11-07 13:01:32 -0800630 if (await skiaClient.isValidDigestForExpectation(entry.key, golden.path))
631 generateFailureOutput(entry.value, golden, basedir, key: entry.key);
632 }
Kate Lovett7bc02032019-10-25 15:05:21 -0700633 return false;
634 }
635}