// 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. // See also dev/automated_tests/flutter_test/flutter_gold_test.dart import 'dart:convert'; import 'dart:io' hide Directory; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_goldens/flutter_goldens.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:platform/platform.dart'; import 'package:process/process.dart'; import 'json_templates.dart'; const String _kFlutterRoot = '/flutter'; // 1x1 transparent pixel const List<int> _kTestPngBytes = <int>[ 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1, 8, 6, 0, 0, 0, 31, 21, 196, 137, 0, 0, 0, 11, 73, 68, 65, 84, 120, 1, 99, 97, 0, 2, 0, 0, 25, 0, 5, 144, 240, 54, 245, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130, ]; void main() { late MemoryFileSystem fs; late FakePlatform platform; late FakeProcessManager process; late FakeHttpClient fakeHttpClient; setUp(() { fs = MemoryFileSystem(); platform = FakePlatform( environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot}, operatingSystem: 'macos' ); process = FakeProcessManager(); fakeHttpClient = FakeHttpClient(); fs.directory(_kFlutterRoot).createSync(recursive: true); }); group('SkiaGoldClient', () { late SkiaGoldClient skiaClient; late Directory workDirectory; setUp(() { workDirectory = fs.directory('/workDirectory') ..createSync(recursive: true); skiaClient = SkiaGoldClient( workDirectory, fs: fs, process: process, platform: platform, httpClient: fakeHttpClient, ); }); test('web HTML test', () async { platform = FakePlatform( environment: <String, String>{ 'GOLDCTL': 'goldctl', 'FLUTTER_ROOT': _kFlutterRoot, 'FLUTTER_TEST_BROWSER': 'Chrome', 'FLUTTER_WEB_RENDERER': 'html', }, operatingSystem: 'macos' ); skiaClient = SkiaGoldClient( workDirectory, fs: fs, process: process, platform: platform, httpClient: fakeHttpClient, ); final File goldenFile = fs.file('/workDirectory/temp/golden_file_test.png') ..createSync(recursive: true); const RunInvocation goldctlInvocation = RunInvocation( <String>[ 'goldctl', 'imgtest', 'add', '--work-dir', '/workDirectory/temp', '--test-name', 'golden_file_test', '--png-file', '/workDirectory/temp/golden_file_test.png', '--passfail', '--add-test-optional-key', 'image_matching_algorithm:fuzzy', '--add-test-optional-key', 'fuzzy_max_different_pixels:20', '--add-test-optional-key', 'fuzzy_pixel_delta_threshold:4', ], null, ); process.processResults[goldctlInvocation] = ProcessResult(123, 0, '', ''); expect( await skiaClient.imgtestAdd('golden_file_test.png', goldenFile), isTrue, ); }); test('web CanvasKit test', () async { platform = FakePlatform( environment: <String, String>{ 'GOLDCTL': 'goldctl', 'FLUTTER_ROOT': _kFlutterRoot, 'FLUTTER_TEST_BROWSER': 'Chrome', 'FLUTTER_WEB_RENDERER': 'canvaskit', }, operatingSystem: 'macos' ); skiaClient = SkiaGoldClient( workDirectory, fs: fs, process: process, platform: platform, httpClient: fakeHttpClient, ); final File goldenFile = fs.file('/workDirectory/temp/golden_file_test.png') ..createSync(recursive: true); const RunInvocation goldctlInvocation = RunInvocation( <String>[ 'goldctl', 'imgtest', 'add', '--work-dir', '/workDirectory/temp', '--test-name', 'golden_file_test', '--png-file', '/workDirectory/temp/golden_file_test.png', '--passfail', ], null, ); process.processResults[goldctlInvocation] = ProcessResult(123, 0, '', ''); expect( await skiaClient.imgtestAdd('golden_file_test.png', goldenFile), isTrue, ); }); test('auth performs minimal work if already authorized', () async { final File authFile = fs.file('/workDirectory/temp/auth_opt.json') ..createSync(recursive: true); authFile.writeAsStringSync(authTemplate()); process.fallbackProcessResult = ProcessResult(123, 0, '', ''); await skiaClient.auth(); expect(process.workingDirectories, isEmpty); }); test('gsutil is checked when authorization file is present', () async { final File authFile = fs.file('/workDirectory/temp/auth_opt.json') ..createSync(recursive: true); authFile.writeAsStringSync(authTemplate(gsutil: true)); expect( await skiaClient.clientIsAuthorized(), isFalse, ); }); test('throws for error state from auth', () async { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'GOLD_SERVICE_ACCOUNT' : 'Service Account', 'GOLDCTL' : 'goldctl', }, operatingSystem: 'macos' ); skiaClient = SkiaGoldClient( workDirectory, fs: fs, process: process, platform: platform, httpClient: fakeHttpClient, ); process.fallbackProcessResult = ProcessResult(123, 1, 'Fallback failure', 'Fallback failure'); expect( skiaClient.auth(), throwsException, ); }); test('throws for error state from init', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'GOLDCTL' : 'goldctl', }, operatingSystem: 'macos' ); skiaClient = SkiaGoldClient( workDirectory, fs: fs, process: process, platform: platform, httpClient: fakeHttpClient, ); const RunInvocation gitInvocation = RunInvocation( <String>['git', 'rev-parse', 'HEAD'], '/flutter', ); const RunInvocation goldctlInvocation = RunInvocation( <String>[ 'goldctl', 'imgtest', 'init', '--instance', 'flutter', '--work-dir', '/workDirectory/temp', '--commit', '12345678', '--keys-file', '/workDirectory/keys.json', '--failure-file', '/workDirectory/failures.json', '--passfail', ], null, ); process.processResults[gitInvocation] = ProcessResult(12345678, 0, '12345678', ''); process.processResults[goldctlInvocation] = ProcessResult(123, 1, 'Expected failure', 'Expected failure'); process.fallbackProcessResult = ProcessResult(123, 1, 'Fallback failure', 'Fallback failure'); expect( skiaClient.imgtestInit(), throwsException, ); }); test('Only calls init once', () async { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'GOLDCTL' : 'goldctl', }, operatingSystem: 'macos' ); skiaClient = SkiaGoldClient( workDirectory, fs: fs, process: process, platform: platform, httpClient: fakeHttpClient, ); const RunInvocation gitInvocation = RunInvocation( <String>['git', 'rev-parse', 'HEAD'], '/flutter', ); const RunInvocation goldctlInvocation = RunInvocation( <String>[ 'goldctl', 'imgtest', 'init', '--instance', 'flutter', '--work-dir', '/workDirectory/temp', '--commit', '1234', '--keys-file', '/workDirectory/keys.json', '--failure-file', '/workDirectory/failures.json', '--passfail', ], null, ); process.processResults[gitInvocation] = ProcessResult(1234, 0, '1234', ''); process.processResults[goldctlInvocation] = ProcessResult(5678, 0, '5678', ''); process.fallbackProcessResult = ProcessResult(123, 1, 'Fallback failure', 'Fallback failure'); // First call await skiaClient.imgtestInit(); // Remove fake process result. // If the init call is executed again, the fallback process will throw. process.processResults.remove(goldctlInvocation); // Second call await skiaClient.imgtestInit(); }); test('Only calls tryjob init once', () async { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'GOLDCTL' : 'goldctl', 'SWARMING_TASK_ID' : '4ae997b50dfd4d11', 'LOGDOG_STREAM_PREFIX' : 'buildbucket/cr-buildbucket.appspot.com/8885996262141582672', 'GOLD_TRYJOB' : 'refs/pull/49815/head', }, operatingSystem: 'macos' ); skiaClient = SkiaGoldClient( workDirectory, fs: fs, process: process, platform: platform, httpClient: fakeHttpClient, ); const RunInvocation gitInvocation = RunInvocation( <String>['git', 'rev-parse', 'HEAD'], '/flutter', ); const RunInvocation goldctlInvocation = RunInvocation( <String>[ 'goldctl', 'imgtest', 'init', '--instance', 'flutter', '--work-dir', '/workDirectory/temp', '--commit', '1234', '--keys-file', '/workDirectory/keys.json', '--failure-file', '/workDirectory/failures.json', '--passfail', '--crs', 'github', '--patchset_id', '1234', '--changelist', '49815', '--cis', 'buildbucket', '--jobid', '8885996262141582672', ], null, ); process.processResults[gitInvocation] = ProcessResult(1234, 0, '1234', ''); process.processResults[goldctlInvocation] = ProcessResult(5678, 0, '5678', ''); process.fallbackProcessResult = ProcessResult(123, 1, 'Fallback failure', 'Fallback failure'); // First call await skiaClient.tryjobInit(); // Remove fake process result. // If the init call is executed again, the fallback process will throw. process.processResults.remove(goldctlInvocation); // Second call await skiaClient.tryjobInit(); }); test('throws for error state from imgtestAdd', () { final File goldenFile = fs.file('/workDirectory/temp/golden_file_test.png') ..createSync(recursive: true); platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'GOLDCTL' : 'goldctl', }, operatingSystem: 'macos' ); skiaClient = SkiaGoldClient( workDirectory, fs: fs, process: process, platform: platform, httpClient: fakeHttpClient, ); const RunInvocation goldctlInvocation = RunInvocation( <String>[ 'goldctl', 'imgtest', 'add', '--work-dir', '/workDirectory/temp', '--test-name', 'golden_file_test', '--png-file', '/workDirectory/temp/golden_file_test.png', '--passfail', ], null, ); process.processResults[goldctlInvocation] = ProcessResult(123, 1, 'Expected failure', 'Expected failure'); process.fallbackProcessResult = ProcessResult(123, 1, 'Fallback failure', 'Fallback failure'); expect( skiaClient.imgtestAdd('golden_file_test', goldenFile), throwsException, ); }); test('correctly inits tryjob for luci', () async { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'GOLDCTL' : 'goldctl', 'SWARMING_TASK_ID' : '4ae997b50dfd4d11', 'LOGDOG_STREAM_PREFIX' : 'buildbucket/cr-buildbucket.appspot.com/8885996262141582672', 'GOLD_TRYJOB' : 'refs/pull/49815/head', }, operatingSystem: 'macos' ); skiaClient = SkiaGoldClient( workDirectory, fs: fs, process: process, platform: platform, httpClient: fakeHttpClient, ); final List<String> ciArguments = skiaClient.getCIArguments(); expect( ciArguments, equals( <String>[ '--changelist', '49815', '--cis', 'buildbucket', '--jobid', '8885996262141582672', ], ), ); }); test('Creates traceID correctly', () async { String traceID; platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'GOLDCTL' : 'goldctl', 'SWARMING_TASK_ID' : '4ae997b50dfd4d11', 'LOGDOG_STREAM_PREFIX' : 'buildbucket/cr-buildbucket.appspot.com/8885996262141582672', 'GOLD_TRYJOB' : 'refs/pull/49815/head', }, operatingSystem: 'linux' ); skiaClient = SkiaGoldClient( workDirectory, fs: fs, process: process, platform: platform, httpClient: fakeHttpClient, ); traceID = skiaClient.getTraceID('flutter.golden.1'); expect( traceID, equals('ae18c7a6aa48e0685525dfe8fdf79003'), ); // Browser platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'GOLDCTL' : 'goldctl', 'SWARMING_TASK_ID' : '4ae997b50dfd4d11', 'LOGDOG_STREAM_PREFIX' : 'buildbucket/cr-buildbucket.appspot.com/8885996262141582672', 'GOLD_TRYJOB' : 'refs/pull/49815/head', 'FLUTTER_TEST_BROWSER' : 'chrome', }, operatingSystem: 'linux' ); skiaClient = SkiaGoldClient( workDirectory, fs: fs, process: process, platform: platform, httpClient: fakeHttpClient, ); traceID = skiaClient.getTraceID('flutter.golden.1'); expect( traceID, equals('e9d5c296c48e7126808520e9cc191243'), ); // Locally - should defer to luci traceID platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, }, operatingSystem: 'macos' ); skiaClient = SkiaGoldClient( workDirectory, fs: fs, process: process, platform: platform, httpClient: fakeHttpClient, ); traceID = skiaClient.getTraceID('flutter.golden.1'); expect( traceID, equals('9968695b9ae78cdb77cbb2be621ca2d6'), ); }); group('Request Handling', () { const String expectation = '55109a4bed52acc780530f7a9aeff6c0'; test('image bytes are processed properly', () async { final Uri imageUrl = Uri.parse( 'https://flutter-gold.skia.org/img/images/$expectation.png' ); final FakeHttpClientRequest fakeImageRequest = FakeHttpClientRequest(); final FakeHttpImageResponse fakeImageResponse = FakeHttpImageResponse( imageResponseTemplate() ); fakeHttpClient.request = fakeImageRequest; fakeImageRequest.response = fakeImageResponse; final List<int> masterBytes = await skiaClient.getImageBytes(expectation); expect(fakeHttpClient.lastUri, imageUrl); expect(masterBytes, equals(_kTestPngBytes)); }); }); }); group('FlutterGoldenFileComparator', () { late FlutterGoldenFileComparator comparator; setUp(() { final Directory basedir = fs.directory('flutter/test/library/') ..createSync(recursive: true); comparator = FlutterPostSubmitFileComparator( basedir.uri, FakeSkiaGoldClient(), fs: fs, platform: platform, ); }); test('calculates the basedir correctly from defaultComparator for local testing', () async { final FakeLocalFileComparator defaultComparator = FakeLocalFileComparator(); final Directory flutterRoot = fs.directory(platform.environment['FLUTTER_ROOT']) ..createSync(recursive: true); defaultComparator.basedir = flutterRoot.childDirectory('baz').uri; final Directory basedir = FlutterGoldenFileComparator.getBaseDirectory( defaultComparator, platform, ); expect( basedir.uri, fs.directory('/flutter/bin/cache/pkg/skia_goldens/baz').uri, ); }); test('ignores version number', () { final Uri key = comparator.getTestUri(Uri.parse('foo.png'), 1); expect(key, Uri.parse('foo.png')); }); test('adds namePrefix', () async { const String libraryName = 'sidedishes'; const String namePrefix = 'tomatosalad'; const String fileName = 'lettuce.png'; final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient(); final Directory basedir = fs.directory('flutter/test/$libraryName/') ..createSync(recursive: true); final FlutterGoldenFileComparator comparator = FlutterPostSubmitFileComparator( basedir.uri, fakeSkiaClient, fs: fs, platform: platform, namePrefix: namePrefix, ); await comparator.compare( Uint8List.fromList(_kTestPngBytes), Uri.parse(fileName), ); expect(fakeSkiaClient.testNames.single, '$namePrefix.$libraryName.$fileName'); }); group('Post-Submit', () { late FakeSkiaGoldClient fakeSkiaClient; setUp(() { fakeSkiaClient = FakeSkiaGoldClient(); final Directory basedir = fs.directory('flutter/test/library/') ..createSync(recursive: true); comparator = FlutterPostSubmitFileComparator( basedir.uri, fakeSkiaClient, fs: fs, platform: platform, ); }); test('asserts .png format', () async { await expectLater( () async { return comparator.compare( Uint8List.fromList(_kTestPngBytes), Uri.parse('flutter.golden_test.1'), ); }, throwsA( isA<AssertionError>().having((AssertionError error) => error.toString(), 'description', contains( 'Golden files in the Flutter framework must end with the file ' 'extension .png.' ), ), ), ); }); test('calls init during compare', () { expect(fakeSkiaClient.initCalls, 0); comparator.compare( Uint8List.fromList(_kTestPngBytes), Uri.parse('flutter.golden_test.1.png'), ); expect(fakeSkiaClient.initCalls, 1); }); test('does not call init in during construction', () { expect(fakeSkiaClient.initCalls, 0); FlutterPostSubmitFileComparator.fromDefaultComparator( platform, goldens: fakeSkiaClient, ); expect(fakeSkiaClient.initCalls, 0); }); group('correctly determines testing environment', () { test('returns true for configured Luci', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'SWARMING_TASK_ID' : '12345678990', 'GOLDCTL' : 'goldctl', }, operatingSystem: 'macos', ); expect( FlutterPostSubmitFileComparator.isAvailableForEnvironment(platform), isTrue, ); }); test('returns false - GOLDCTL not present', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'SWARMING_TASK_ID' : '12345678990', }, operatingSystem: 'macos', ); expect( FlutterPostSubmitFileComparator.isAvailableForEnvironment(platform), isFalse, ); }); test('returns false - GOLD_TRYJOB active', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'SWARMING_TASK_ID' : '12345678990', 'GOLDCTL' : 'goldctl', 'GOLD_TRYJOB' : 'git/ref/12345/head', }, operatingSystem: 'macos', ); expect( FlutterPostSubmitFileComparator.isAvailableForEnvironment(platform), isFalse, ); }); test('returns false - on Cirrus', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'CIRRUS_CI': 'true', 'CIRRUS_PR': '', 'CIRRUS_BRANCH': 'master', 'GOLD_SERVICE_ACCOUNT': 'service account...', }, operatingSystem: 'macos', ); expect( FlutterPostSubmitFileComparator.isAvailableForEnvironment(platform), isFalse, ); }); }); }); group('Pre-Submit', () { late FakeSkiaGoldClient fakeSkiaClient; setUp(() { fakeSkiaClient = FakeSkiaGoldClient(); final Directory basedir = fs.directory('flutter/test/library/') ..createSync(recursive: true); comparator = FlutterPreSubmitFileComparator( basedir.uri, fakeSkiaClient, fs: fs, platform: platform, ); }); test('asserts .png format', () async { await expectLater( () async { return comparator.compare( Uint8List.fromList(_kTestPngBytes), Uri.parse('flutter.golden_test.1'), ); }, throwsA( isA<AssertionError>().having((AssertionError error) => error.toString(), 'description', contains( 'Golden files in the Flutter framework must end with the file ' 'extension .png.' ), ), ), ); }); test('calls init during compare', () { expect(fakeSkiaClient.tryInitCalls, 0); comparator.compare( Uint8List.fromList(_kTestPngBytes), Uri.parse('flutter.golden_test.1.png'), ); expect(fakeSkiaClient.tryInitCalls, 1); }); test('does not call init in during construction', () { expect(fakeSkiaClient.tryInitCalls, 0); FlutterPostSubmitFileComparator.fromDefaultComparator( platform, goldens: fakeSkiaClient, ); expect(fakeSkiaClient.tryInitCalls, 0); }); group('correctly determines testing environment', () { test('returns true for Luci', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'SWARMING_TASK_ID' : '12345678990', 'GOLDCTL' : 'goldctl', 'GOLD_TRYJOB' : 'git/ref/12345/head', }, operatingSystem: 'macos', ); expect( FlutterPreSubmitFileComparator.isAvailableForEnvironment(platform), isTrue, ); }); test('returns false - not on Luci', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, }, operatingSystem: 'macos', ); expect( FlutterPreSubmitFileComparator.isAvailableForEnvironment(platform), isFalse, ); }); test('returns false - GOLDCTL missing', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'SWARMING_TASK_ID' : '12345678990', 'GOLD_TRYJOB' : 'git/ref/12345/head', }, operatingSystem: 'macos', ); expect( FlutterPreSubmitFileComparator.isAvailableForEnvironment(platform), isFalse, ); }); test('returns false - GOLD_TRYJOB missing', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'SWARMING_TASK_ID' : '12345678990', 'GOLDCTL' : 'goldctl', }, operatingSystem: 'macos', ); expect( FlutterPreSubmitFileComparator.isAvailableForEnvironment(platform), isFalse, ); }); test('returns false - on Cirrus', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'CIRRUS_CI': 'true', 'CIRRUS_PR': '', 'CIRRUS_BRANCH': 'master', 'GOLD_SERVICE_ACCOUNT': 'service account...', }, operatingSystem: 'macos', ); expect( FlutterPostSubmitFileComparator.isAvailableForEnvironment(platform), isFalse, ); }); }); }); group('Skipping', () { group('correctly determines testing environment', () { test('returns true on Cirrus builds', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'CIRRUS_CI' : 'yep', }, operatingSystem: 'macos', ); expect( FlutterSkippingFileComparator.isAvailableForEnvironment(platform), isTrue, ); }); test('returns true on irrelevant LUCI builds', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, 'SWARMING_TASK_ID' : '1234567890', }, operatingSystem: 'macos' ); expect( FlutterSkippingFileComparator.isAvailableForEnvironment(platform), isTrue, ); }); test('returns false - no CI', () { platform = FakePlatform( environment: <String, String>{ 'FLUTTER_ROOT': _kFlutterRoot, }, operatingSystem: 'macos', ); expect( FlutterSkippingFileComparator.isAvailableForEnvironment( platform), isFalse, ); }); }); }); group('Local', () { late FlutterLocalFileComparator comparator; final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient(); setUp(() async { final Directory basedir = fs.directory('flutter/test/library/') ..createSync(recursive: true); comparator = FlutterLocalFileComparator( basedir.uri, fakeSkiaClient, fs: fs, platform: FakePlatform( environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot}, operatingSystem: 'macos', ), ); const String hash = '55109a4bed52acc780530f7a9aeff6c0'; fakeSkiaClient.expectationForTestValues['flutter.golden_test.1'] = hash; fakeSkiaClient.imageBytesValues[hash] =_kTestPngBytes; fakeSkiaClient.cleanTestNameValues['library.flutter.golden_test.1.png'] = 'flutter.golden_test.1'; }); test('asserts .png format', () async { await expectLater( () async { return comparator.compare( Uint8List.fromList(_kTestPngBytes), Uri.parse('flutter.golden_test.1'), ); }, throwsA( isA<AssertionError>().having((AssertionError error) => error.toString(), 'description', contains( 'Golden files in the Flutter framework must end with the file ' 'extension .png.' ), ), ), ); }); test('passes when bytes match', () async { expect( await comparator.compare( Uint8List.fromList(_kTestPngBytes), Uri.parse('flutter.golden_test.1.png'), ), isTrue, ); }); test('returns FlutterSkippingGoldenFileComparator when network connection is unavailable', () async { final FakeDirectory fakeDirectory = FakeDirectory(); fakeDirectory.existsSyncValue = true; fakeDirectory.uri = Uri.parse('/flutter'); fakeSkiaClient.getExpectationForTestThrowable = const OSError("Can't reach Gold"); FlutterGoldenFileComparator comparator = await FlutterLocalFileComparator.fromDefaultComparator( platform, goldens: fakeSkiaClient, baseDirectory: fakeDirectory, ); expect(comparator.runtimeType, FlutterSkippingFileComparator); fakeSkiaClient.getExpectationForTestThrowable = const SocketException("Can't reach Gold"); comparator = await FlutterLocalFileComparator.fromDefaultComparator( platform, goldens: fakeSkiaClient, baseDirectory: fakeDirectory, ); expect(comparator.runtimeType, FlutterSkippingFileComparator); // reset property or it will carry on to other tests fakeSkiaClient.getExpectationForTestThrowable = null; }); }); }); } @immutable class RunInvocation { const RunInvocation(this.command, this.workingDirectory); final List<String> command; final String? workingDirectory; @override int get hashCode => Object.hash(Object.hashAll(command), workingDirectory); bool _commandEquals(List<String> other) { if (other == command) { return true; } if (other.length != command.length) { return false; } for (int index = 0; index < other.length; index += 1) { if (other[index] != command[index]) { return false; } } return true; } @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is RunInvocation && _commandEquals(other.command) && other.workingDirectory == workingDirectory; } @override String toString() => '$command ($workingDirectory)'; } class FakeProcessManager extends Fake implements ProcessManager { Map<RunInvocation, ProcessResult> processResults = <RunInvocation, ProcessResult>{}; /// Used if [processResults] does not contain a matching invocation. ProcessResult? fallbackProcessResult; final List<String?> workingDirectories = <String?>[]; @override Future<ProcessResult> run( List<Object> command, { String? workingDirectory, Map<String, String>? environment, bool includeParentEnvironment = true, bool runInShell = false, Encoding? stdoutEncoding = systemEncoding, Encoding? stderrEncoding = systemEncoding, }) async { workingDirectories.add(workingDirectory); final ProcessResult? result = processResults[RunInvocation(command.cast<String>(), workingDirectory)]; if (result == null && fallbackProcessResult == null) { printOnFailure('ProcessManager.run was called with $command ($workingDirectory) unexpectedly - $processResults.'); fail('See above.'); } return result ?? fallbackProcessResult!; } } // See also dev/automated_tests/flutter_test/flutter_gold_test.dart class FakeSkiaGoldClient extends Fake implements SkiaGoldClient { Map<String, String> expectationForTestValues = <String, String>{}; Exception? getExpectationForTestThrowable; @override Future<String> getExpectationForTest(String testName) async { if (getExpectationForTestThrowable != null) { throw getExpectationForTestThrowable!; } return expectationForTestValues[testName] ?? ''; } @override Future<void> auth() async {} final List<String> testNames = <String>[]; int initCalls = 0; @override Future<void> imgtestInit() async => initCalls += 1; @override Future<bool> imgtestAdd(String testName, File goldenFile) async { testNames.add(testName); return true; } int tryInitCalls = 0; @override Future<void> tryjobInit() async => tryInitCalls += 1; @override Future<bool> tryjobAdd(String testName, File goldenFile) async => true; Map<String, List<int>> imageBytesValues = <String, List<int>>{}; @override Future<List<int>> getImageBytes(String imageHash) async => imageBytesValues[imageHash]!; Map<String, String> cleanTestNameValues = <String, String>{}; @override String cleanTestName(String fileName) => cleanTestNameValues[fileName] ?? ''; } class FakeLocalFileComparator extends Fake implements LocalFileComparator { @override late Uri basedir; } class FakeDirectory extends Fake implements Directory { late bool existsSyncValue; @override bool existsSync() => existsSyncValue; @override late Uri uri; } class FakeHttpClient extends Fake implements HttpClient { late Uri lastUri; late FakeHttpClientRequest request; @override Future<HttpClientRequest> getUrl(Uri url) async { lastUri = url; return request; } } class FakeHttpClientRequest extends Fake implements HttpClientRequest { late FakeHttpImageResponse response; @override Future<HttpClientResponse> close() async { return response; } } class FakeHttpImageResponse extends Fake implements HttpClientResponse { FakeHttpImageResponse(this.response); final List<List<int>> response; @override Future<void> forEach(void Function(List<int> element) action) async { response.forEach(action); } }