flutter/packages/flutter_goldens/test/flutter_goldens_test.dart
Kate Lovett 01d1e74af5
Remove abi key permanently from golden file testing (#149858)
We have fiddled with this a bunch, and there is definitively no way to reliably include this, so I am removing it.

Since CI does not cover all possible values, we can't consistently look up images for local testing. We thought removing it from the look up would work fine, and it did for most tests, but there were still some that could not find an image without it.

A brief history on the abi key
- added in https://github.com/flutter/flutter/pull/143621
- disabled in https://github.com/flutter/flutter/pull/148023
- added back in https://github.com/flutter/flutter/pull/148072
  - we thought there was only an issue with alphabetizing keys
- removed from local image look up in https://github.com/flutter/flutter/pull/149696

I updated the docs page to also discuss what makes a good key and what does not based on what we learned here.
2024-06-06 23:05:52 +00:00

1240 lines
46 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.
// 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:path/path.dart' as path;
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import 'json_templates.dart';
// TODO(ianh): make sure all constructors order their arguments in a manner consistent with the defined parameter order
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() {
group('SkiaGoldClient', () {
test('web HTML test', () async {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{
'GOLDCTL': 'goldctl',
'FLUTTER_ROOT': _kFlutterRoot,
'FLUTTER_TEST_BROWSER': 'Chrome',
'FLUTTER_WEB_RENDERER': 'html',
},
operatingSystem: 'macos'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
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 {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{
'GOLDCTL': 'goldctl',
'FLUTTER_ROOT': _kFlutterRoot,
'FLUTTER_TEST_BROWSER': 'Chrome',
'FLUTTER_WEB_RENDERER': 'canvaskit',
},
operatingSystem: 'macos'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
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 MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
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 MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
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 {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{
'FLUTTER_ROOT': _kFlutterRoot,
'GOLD_SERVICE_ACCOUNT' : 'Service Account',
'GOLDCTL' : 'goldctl',
},
operatingSystem: 'macos'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
process.fallbackProcessResult = ProcessResult(123, 1, 'Fallback failure', 'Fallback failure');
expect(
skiaClient.auth(),
throwsException,
);
});
test('throws for error state from init', () {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{
'FLUTTER_ROOT': _kFlutterRoot,
'GOLDCTL' : 'goldctl',
},
operatingSystem: 'macos'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
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 {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{
'FLUTTER_ROOT': _kFlutterRoot,
'GOLDCTL' : 'goldctl',
},
operatingSystem: 'macos'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
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 {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform 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'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
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 MemoryFileSystem fs = MemoryFileSystem();
final File goldenFile = fs.file('/workDirectory/temp/golden_file_test.png')
..createSync(recursive: true);
final FakePlatform platform = FakePlatform(
environment: <String, String>{
'FLUTTER_ROOT': _kFlutterRoot,
'GOLDCTL' : 'goldctl',
},
operatingSystem: 'macos'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
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 {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform 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'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
final List<String> ciArguments = skiaClient.getCIArguments();
expect(
ciArguments,
equals(
<String>[
'--changelist', '49815',
'--cis', 'buildbucket',
'--jobid', '8885996262141582672',
],
),
);
});
test('Creates traceID correctly', () async {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform 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'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
expect(
skiaClient.getTraceID('flutter.golden.1'),
equals('ae18c7a6aa48e0685525dfe8fdf79003'),
);
});
test('Creates traceID correctly - Browser', () async {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform 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'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
expect(
skiaClient.getTraceID('flutter.golden.1'),
equals('e9d5c296c48e7126808520e9cc191243'),
);
});
test('Creates traceID correctly - locally - should defer to luci traceID', () async {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{
'FLUTTER_ROOT': _kFlutterRoot,
},
operatingSystem: 'macos'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
expect(
skiaClient.getTraceID('flutter.golden.1'),
equals('9968695b9ae78cdb77cbb2be621ca2d6'),
);
});
test('throws for error state from imgtestAdd', () {
final MemoryFileSystem fs = MemoryFileSystem();
final File goldenFile = fs.file('/workDirectory/temp/golden_file_test.png')
..createSync(recursive: true);
final FakePlatform platform = FakePlatform(
environment: <String, String>{
'FLUTTER_ROOT': _kFlutterRoot,
'GOLDCTL' : 'goldctl',
},
operatingSystem: 'macos'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
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),
throwsA(
isA<SkiaException>().having((SkiaException error) => error.message,
'message',
contains('result-state.json'),
),
),
);
});
test('throws for error state from tryjobAdd', () {
final MemoryFileSystem fs = MemoryFileSystem();
final File goldenFile = fs.file('/workDirectory/temp/golden_file_test.png')
..createSync(recursive: true);
final FakePlatform platform = FakePlatform(
environment: <String, String>{
'FLUTTER_ROOT': _kFlutterRoot,
'GOLDCTL' : 'goldctl',
},
operatingSystem: 'macos'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
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.tryjobAdd('golden_file_test', goldenFile),
throwsA(
isA<SkiaException>().having((SkiaException error) => error.message,
'message',
contains('result-state.json'),
),
),
);
});
group('Request Handling', () {
test('image bytes are processed properly', () async {
const String expectation = '55109a4bed52acc780530f7a9aeff6c0';
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
final FakeProcessManager process = FakeProcessManager();
final FakeHttpClient fakeHttpClient = FakeHttpClient();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory workDirectory = fs.directory('/workDirectory')
..createSync(recursive: true);
final SkiaGoldClient skiaClient = SkiaGoldClient(
workDirectory,
fs: fs,
process: process,
platform: platform,
httpClient: fakeHttpClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
);
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', () {
test('calculates the basedir correctly from defaultComparator for local testing', () async {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
fs.directory(_kFlutterRoot).createSync(recursive: true);
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: platform,
fs: fs,
);
expect(
basedir.uri,
fs.directory('/flutter/bin/cache/pkg/skia_goldens/baz').uri,
);
});
test('ignores version number', () {
final List<String> log = <String>[];
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory basedir = fs.directory('flutter/test/library/')
..createSync(recursive: true);
final FlutterGoldenFileComparator comparator = FlutterPostSubmitFileComparator(
basedir.uri,
FakeSkiaGoldClient(),
fs: fs,
platform: platform,
log: log.add,
);
final Uri key = comparator.getTestUri(Uri.parse('foo.png'), 1);
expect(key, Uri.parse('foo.png'));
expect(log, isEmpty);
});
test('adds namePrefix', () async {
final List<String> log = <String>[];
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
fs.directory(_kFlutterRoot).createSync(recursive: true);
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,
log: log.add,
);
await comparator.compare(
Uint8List.fromList(_kTestPngBytes),
Uri.parse(fileName),
);
expect(fakeSkiaClient.testNames.single, '$namePrefix.$libraryName.$fileName');
expect(log, isEmpty);
});
group('Post-Submit', () {
test('asserts .png format', () async {
final List<String> log = <String>[];
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory basedir = fs.directory('flutter/test/library/')
..createSync(recursive: true);
final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient();
final FlutterGoldenFileComparator comparator = FlutterPostSubmitFileComparator(
basedir.uri,
fakeSkiaClient,
fs: fs,
platform: platform,
log: log.add,
);
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.'
),
),
),
);
expect(log, isEmpty);
});
test('calls init during compare', () {
final List<String> log = <String>[];
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory basedir = fs.directory('flutter/test/library/')
..createSync(recursive: true);
final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient();
final FlutterGoldenFileComparator comparator = FlutterPostSubmitFileComparator(
basedir.uri,
fakeSkiaClient,
fs: fs,
platform: platform,
log: log.add,
);
expect(fakeSkiaClient.initCalls, 0);
comparator.compare(
Uint8List.fromList(_kTestPngBytes),
Uri.parse('flutter.golden_test.1.png'),
);
expect(fakeSkiaClient.initCalls, 1);
expect(log, isEmpty);
});
test('does not call init in during construction', () {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
fs.directory(_kFlutterRoot).createSync(recursive: true);
final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient();
expect(fakeSkiaClient.initCalls, 0);
FlutterPostSubmitFileComparator.fromLocalFileComparator(
localFileComparator: LocalFileComparator(Uri.parse('/test'), pathStyle: path.Style.posix),
platform: platform,
goldens: fakeSkiaClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
fs: fs,
process: FakeProcessManager(),
httpClient: FakeHttpClient(),
);
expect(fakeSkiaClient.initCalls, 0);
});
});
group('Pre-Submit', () {
test('asserts .png format', () async {
final List<String> log = <String>[];
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory basedir = fs.directory('flutter/test/library/')
..createSync(recursive: true);
final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient();
final FlutterGoldenFileComparator comparator = FlutterPreSubmitFileComparator(
basedir.uri,
fakeSkiaClient,
fs: fs,
platform: platform,
log: log.add,
);
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.'
),
),
),
);
expect(log, isEmpty);
});
test('calls init during compare', () {
final List<String> log = <String>[];
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory basedir = fs.directory('flutter/test/library/')
..createSync(recursive: true);
final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient();
final FlutterGoldenFileComparator comparator = FlutterPreSubmitFileComparator(
basedir.uri,
fakeSkiaClient,
fs: fs,
platform: platform,
log: log.add,
);
expect(fakeSkiaClient.tryInitCalls, 0);
comparator.compare(
Uint8List.fromList(_kTestPngBytes),
Uri.parse('flutter.golden_test.1.png'),
);
expect(fakeSkiaClient.tryInitCalls, 1);
expect(log, isEmpty);
});
test('does not call init in during construction', () {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
fs.directory(_kFlutterRoot).createSync(recursive: true);
final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient();
expect(fakeSkiaClient.tryInitCalls, 0);
FlutterPostSubmitFileComparator.fromLocalFileComparator(
localFileComparator: LocalFileComparator(Uri.parse('/test'), pathStyle: path.Style.posix),
platform: platform,
goldens: fakeSkiaClient,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
fs: fs,
process: FakeProcessManager(),
httpClient: FakeHttpClient(),
);
expect(fakeSkiaClient.tryInitCalls, 0);
});
});
group('Local', () {
test('asserts .png format', () async {
final List<String> log = <String>[];
final MemoryFileSystem fs = MemoryFileSystem();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory basedir = fs.directory('flutter/test/library/')
..createSync(recursive: true);
final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient();
final FlutterGoldenFileComparator comparator = FlutterLocalFileComparator(
basedir.uri,
fakeSkiaClient,
fs: fs,
platform: FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos',
),
log: log.add,
);
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';
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.'
),
),
),
);
expect(log, isEmpty);
});
test('passes when bytes match', () async {
final List<String> log = <String>[];
final MemoryFileSystem fs = MemoryFileSystem();
fs.directory(_kFlutterRoot).createSync(recursive: true);
final Directory basedir = fs.directory('flutter/test/library/')
..createSync(recursive: true);
final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient();
final FlutterGoldenFileComparator comparator = FlutterLocalFileComparator(
basedir.uri,
fakeSkiaClient,
fs: fs,
platform: FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos',
),
log: log.add,
);
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';
expect(
await comparator.compare(
Uint8List.fromList(_kTestPngBytes),
Uri.parse('flutter.golden_test.1.png'),
),
isTrue,
);
expect(log, isEmpty);
});
test('returns FlutterSkippingGoldenFileComparator when network connection is unavailable', () async {
final MemoryFileSystem fs = MemoryFileSystem();
final FakePlatform platform = FakePlatform(
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
operatingSystem: 'macos'
);
fs.directory(_kFlutterRoot).createSync(recursive: true);
final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient();
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';
final FakeDirectory fakeDirectory = FakeDirectory();
fakeDirectory.existsSyncValue = true;
fakeDirectory.uri = Uri.parse('/flutter');
fakeSkiaClient.getExpectationForTestThrowable = const OSError("Can't reach Gold");
final FlutterGoldenFileComparator comparator1 = await FlutterLocalFileComparator.fromLocalFileComparator(
localFileComparator: LocalFileComparator(Uri.parse('/test'), pathStyle: path.Style.posix),
platform: platform,
goldens: fakeSkiaClient,
baseDirectory: fakeDirectory,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
fs: fs,
process: FakeProcessManager(),
httpClient: FakeHttpClient(),
);
expect(comparator1.runtimeType, FlutterSkippingFileComparator);
fakeSkiaClient.getExpectationForTestThrowable = const SocketException("Can't reach Gold");
final FlutterGoldenFileComparator comparator2 = await FlutterLocalFileComparator.fromLocalFileComparator(
localFileComparator: LocalFileComparator(Uri.parse('/test'), pathStyle: path.Style.posix),
platform: platform,
goldens: fakeSkiaClient,
baseDirectory: fakeDirectory,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
fs: fs,
process: FakeProcessManager(),
httpClient: FakeHttpClient(),
);
expect(comparator2.runtimeType, FlutterSkippingFileComparator);
fakeSkiaClient.getExpectationForTestThrowable = const FormatException("Can't reach Gold");
final FlutterGoldenFileComparator comparator3 = await FlutterLocalFileComparator.fromLocalFileComparator(
localFileComparator: LocalFileComparator(Uri.parse('/test'), pathStyle: path.Style.posix),
platform: platform,
goldens: fakeSkiaClient,
baseDirectory: fakeDirectory,
log: (String message) => fail('skia gold client printed unexpected output: "$message"'),
fs: fs,
process: FakeProcessManager(),
httpClient: FakeHttpClient(),
);
expect(comparator3.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);
}
}