flutter/packages/flutter_test/test/goldens_test.dart
Michael Goderbauer 5491c8c146
Auto-format Framework (#160545)
This auto-formats all *.dart files in the repository outside of the
`engine` subdirectory and enforces that these files stay formatted with
a presubmit check.

**Reviewers:** Please carefully review all the commits except for the
one titled "formatted". The "formatted" commit was auto-generated by
running `dev/tools/format.sh -a -f`. The other commits were hand-crafted
to prepare the repo for the formatting change. I recommend reviewing the
commits one-by-one via the "Commits" tab and avoiding Github's "Files
changed" tab as it will likely slow down your browser because of the
size of this PR.

---------

Co-authored-by: Kate Lovett <katelovett@google.com>
Co-authored-by: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com>
2024-12-19 20:06:21 +00:00

588 lines
16 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io' as io;
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:file/memory.dart';
import 'package:flutter/foundation.dart'
show DiagnosticLevel, DiagnosticPropertiesBuilder, DiagnosticsNode, FlutterError;
import 'package:flutter_test/flutter_test.dart' as test_package;
import 'package:flutter_test/flutter_test.dart' hide test;
// 1x1 transparent pixel
const List<int> _kExpectedPngBytes = <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,
];
// 1x1 colored pixel
const List<int> _kColorFailurePngBytes = <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,
13,
73,
68,
65,
84,
120,
1,
99,
249,
207,
240,
255,
63,
0,
7,
18,
3,
2,
164,
147,
160,
197,
0,
0,
0,
0,
73,
69,
78,
68,
174,
66,
96,
130,
];
// 1x2 transparent pixel
const List<int> _kSizeFailurePngBytes = <int>[
137,
80,
78,
71,
13,
10,
26,
10,
0,
0,
0,
13,
73,
72,
68,
82,
0,
0,
0,
1,
0,
0,
0,
2,
8,
6,
0,
0,
0,
153,
129,
182,
39,
0,
0,
0,
14,
73,
68,
65,
84,
120,
1,
99,
97,
0,
2,
22,
16,
1,
0,
0,
70,
0,
9,
112,
117,
150,
160,
0,
0,
0,
0,
73,
69,
78,
68,
174,
66,
96,
130,
];
void main() {
late MemoryFileSystem fs;
setUp(() {
final FileSystemStyle style =
io.Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix;
fs = MemoryFileSystem(style: style);
});
/// Converts posix-style paths to the style associated with [fs].
///
/// This allows us to deal in posix-style paths in the tests.
String fix(String path) {
if (path.startsWith('/')) {
path = '${fs.style.drive}$path';
}
return path.replaceAll('/', fs.path.separator);
}
void test(String description, FutureOr<void> Function() body) {
test_package.test(description, () async {
await io.IOOverrides.runZoned<FutureOr<void>>(
body,
createDirectory: (String path) => fs.directory(path),
createFile: (String path) => fs.file(path),
createLink: (String path) => fs.link(path),
getCurrentDirectory: () => fs.currentDirectory,
setCurrentDirectory: (String path) => fs.currentDirectory = path,
getSystemTempDirectory: () => fs.systemTempDirectory,
stat: (String path) => fs.stat(path),
statSync: (String path) => fs.statSync(path),
fseIdentical: (String p1, String p2) => fs.identical(p1, p2),
fseIdenticalSync: (String p1, String p2) => fs.identicalSync(p1, p2),
fseGetType: (String path, bool followLinks) => fs.type(path, followLinks: followLinks),
fseGetTypeSync:
(String path, bool followLinks) => fs.typeSync(path, followLinks: followLinks),
fsWatch: (String a, int b, bool c) => throw UnsupportedError('unsupported'),
fsWatchIsSupported: () => fs.isWatchSupported,
);
});
}
group('goldenFileComparator', () {
test('is initialized by test framework', () {
expect(goldenFileComparator, isNotNull);
expect(goldenFileComparator, isA<LocalFileComparator>());
final LocalFileComparator comparator = goldenFileComparator as LocalFileComparator;
expect(comparator.basedir.path, contains('flutter_test'));
});
test('image comparison should not loop over all pixels when the data is the same', () async {
final List<int> invalidImageData1 = Uint8List.fromList(<int>[127]);
final List<int> invalidImageData2 = Uint8List.fromList(<int>[127]);
// This will fail if the comparison algorithm tries to generate the images
// to loop over every pixel which is not necessary when test and master
// is exactly the same (for performance reasons).
await GoldenFileComparator.compareLists(invalidImageData1, invalidImageData2);
});
});
group('LocalFileComparator', () {
late LocalFileComparator comparator;
setUp(() {
comparator = LocalFileComparator(
fs.file(fix('/golden_test.dart')).uri,
pathStyle: fs.path.style,
);
});
test('calculates basedir correctly', () {
expect(comparator.basedir, fs.file(fix('/')).uri);
comparator = LocalFileComparator(
fs.file(fix('/foo/bar/golden_test.dart')).uri,
pathStyle: fs.path.style,
);
expect(comparator.basedir, fs.directory(fix('/foo/bar/')).uri);
});
test('can be instantiated with uri that represents file in same folder', () {
comparator = LocalFileComparator(Uri.parse('foo_test.dart'), pathStyle: fs.path.style);
expect(comparator.basedir, Uri.parse('./'));
});
test('throws if local output is not awaited', () {
try {
comparator.generateFailureOutput(
ComparisonResult(passed: false, diffPercent: 1.0),
Uri.parse('foo_test.dart'),
Uri.parse('/foo/bar/'),
);
TestAsyncUtils.verifyAllScopesClosed();
fail('unexpectedly did not throw');
} on FlutterError catch (e) {
final List<String> lines = e.message.split('\n');
expectSync(lines[0], 'Asynchronous call to guarded function leaked.');
expectSync(lines[1], 'You must use "await" with all Future-returning test APIs.');
expectSync(
lines[2],
matches(
r'^The guarded method "generateFailureOutput" from class '
r'LocalComparisonOutput was called from .*goldens_test.dart on line '
r'[0-9]+, but never completed before its parent scope closed\.$',
),
);
expectSync(lines.length, 3);
final DiagnosticPropertiesBuilder propertiesBuilder = DiagnosticPropertiesBuilder();
e.debugFillProperties(propertiesBuilder);
final List<DiagnosticsNode> information = propertiesBuilder.properties;
expectSync(information.length, 3);
expectSync(information[0].level, DiagnosticLevel.summary);
expectSync(information[1].level, DiagnosticLevel.hint);
expectSync(information[2].level, DiagnosticLevel.info);
}
});
group('compare', () {
Future<bool> doComparison([String golden = 'golden.png']) {
final Uri uri = fs.file(fix(golden)).uri;
return comparator.compare(Uint8List.fromList(_kExpectedPngBytes), uri);
}
group('succeeds', () {
test('when golden file is in same folder as test', () async {
fs.file(fix('/golden.png')).writeAsBytesSync(_kExpectedPngBytes);
final bool success = await doComparison();
expect(success, isTrue);
});
test('when golden file is in subfolder of test', () async {
fs.file(fix('/sub/foo.png'))
..createSync(recursive: true)
..writeAsBytesSync(_kExpectedPngBytes);
final bool success = await doComparison('sub/foo.png');
expect(success, isTrue);
});
group('when comparator instantiated with uri that represents file in same folder', () {
test('and golden file is in same folder as test', () async {
fs.file(fix('/foo/bar/golden.png'))
..createSync(recursive: true)
..writeAsBytesSync(_kExpectedPngBytes);
fs.currentDirectory = fix('/foo/bar');
comparator = LocalFileComparator(
Uri.parse('local_test.dart'),
pathStyle: fs.path.style,
);
final bool success = await doComparison();
expect(success, isTrue);
});
test('and golden file is in subfolder of test', () async {
fs.file(fix('/foo/bar/baz/golden.png'))
..createSync(recursive: true)
..writeAsBytesSync(_kExpectedPngBytes);
fs.currentDirectory = fix('/foo/bar');
comparator = LocalFileComparator(
Uri.parse('local_test.dart'),
pathStyle: fs.path.style,
);
final bool success = await doComparison('baz/golden.png');
expect(success, isTrue);
});
});
});
group('fails', () {
test('and generates correct output in the correct base location', () async {
comparator = LocalFileComparator(Uri.parse('local_test.dart'), pathStyle: fs.path.style);
await fs.file(fix('/golden.png')).writeAsBytes(_kColorFailurePngBytes);
await expectLater(
() => doComparison(),
throwsA(
isFlutterError.having(
(FlutterError error) => error.message,
'message',
contains('100.00%, 1px diff detected'),
),
),
);
final io.File master = fs.file(fix('/failures/golden_masterImage.png'));
final io.File test = fs.file(fix('/failures/golden_testImage.png'));
final io.File isolated = fs.file(fix('/failures/golden_isolatedDiff.png'));
final io.File masked = fs.file(fix('/failures/golden_maskedDiff.png'));
expect(master.existsSync(), isTrue);
expect(test.existsSync(), isTrue);
expect(isolated.existsSync(), isTrue);
expect(masked.existsSync(), isTrue);
});
test('and generates correct output when files are in a subdirectory', () async {
comparator = LocalFileComparator(Uri.parse('local_test.dart'), pathStyle: fs.path.style);
fs.file(fix('subdir/golden.png'))
..createSync(recursive: true)
..writeAsBytesSync(_kColorFailurePngBytes);
await expectLater(
() => doComparison('subdir/golden.png'),
throwsA(
isFlutterError.having(
(FlutterError error) => error.message,
'message',
contains('100.00%, 1px diff detected'),
),
),
);
final io.File master = fs.file(fix('/failures/golden_masterImage.png'));
final io.File test = fs.file(fix('/failures/golden_testImage.png'));
final io.File isolated = fs.file(fix('/failures/golden_isolatedDiff.png'));
final io.File masked = fs.file(fix('/failures/golden_maskedDiff.png'));
expect(master.existsSync(), isTrue);
expect(test.existsSync(), isTrue);
expect(isolated.existsSync(), isTrue);
expect(masked.existsSync(), isTrue);
});
test('and generates correct output when images are not the same size', () async {
await fs.file(fix('/golden.png')).writeAsBytes(_kSizeFailurePngBytes);
await expectLater(
() => doComparison(),
throwsA(
isFlutterError.having(
(FlutterError error) => error.message,
'message',
contains('image sizes do not match'),
),
),
);
final io.File master = fs.file(fix('/failures/golden_masterImage.png'));
final io.File test = fs.file(fix('/failures/golden_testImage.png'));
final io.File isolated = fs.file(fix('/failures/golden_isolatedDiff.png'));
final io.File masked = fs.file(fix('/failures/golden_maskedDiff.png'));
expect(master.existsSync(), isTrue);
expect(test.existsSync(), isTrue);
expect(isolated.existsSync(), isFalse);
expect(masked.existsSync(), isFalse);
});
test('when golden file does not exist', () async {
await expectLater(
() => doComparison(),
throwsA(
isA<TestFailure>().having(
(TestFailure error) => error.message,
'message',
contains('Could not be compared against non-existent file'),
),
),
);
});
test('when images are not the same size', () async {
await fs.file(fix('/golden.png')).writeAsBytes(_kSizeFailurePngBytes);
await expectLater(
() => doComparison(),
throwsA(
isFlutterError.having(
(FlutterError error) => error.message,
'message',
contains('image sizes do not match'),
),
),
);
});
test('when pixels do not match', () async {
await fs.file(fix('/golden.png')).writeAsBytes(_kColorFailurePngBytes);
await expectLater(
() => doComparison(),
throwsA(
isFlutterError.having(
(FlutterError error) => error.message,
'message',
contains('100.00%, 1px diff detected'),
),
),
);
});
test('when golden bytes are empty', () async {
await fs.file(fix('/golden.png')).writeAsBytes(<int>[]);
await expectLater(
() => doComparison(),
throwsA(
isFlutterError.having(
(FlutterError error) => error.message,
'message',
contains('null image provided'),
),
),
);
});
});
});
group('update', () {
test('updates existing file', () async {
fs.file(fix('/golden.png')).writeAsBytesSync(_kExpectedPngBytes);
const List<int> newBytes = <int>[11, 12, 13];
await comparator.update(fs.file('golden.png').uri, Uint8List.fromList(newBytes));
expect(fs.file(fix('/golden.png')).readAsBytesSync(), newBytes);
});
test('creates non-existent file', () async {
expect(fs.file(fix('/foo.png')).existsSync(), isFalse);
const List<int> newBytes = <int>[11, 12, 13];
await comparator.update(fs.file('foo.png').uri, Uint8List.fromList(newBytes));
expect(fs.file(fix('/foo.png')).existsSync(), isTrue);
expect(fs.file(fix('/foo.png')).readAsBytesSync(), newBytes);
});
});
group('getTestUri', () {
test('updates file name with version number', () {
final Uri key = Uri.parse('foo.png');
final Uri key1 = comparator.getTestUri(key, 1);
expect(key1, Uri.parse('foo.1.png'));
});
test('does nothing for null version number', () {
final Uri key = Uri.parse('foo.png');
final Uri keyNull = comparator.getTestUri(key, null);
expect(keyNull, Uri.parse('foo.png'));
});
});
});
group('ComparisonResult', () {
group('dispose', () {
test('disposes diffs images', () async {
final ui.Image image1 = await createTestImage(width: 10, height: 10, cache: false);
final ui.Image image2 = await createTestImage(width: 15, height: 5, cache: false);
final ui.Image image3 = await createTestImage(width: 5, height: 10, cache: false);
final ComparisonResult result = ComparisonResult(
passed: false,
diffPercent: 1.0,
diffs: <String, ui.Image>{'image1': image1, 'image2': image2, 'image3': image3},
);
expect(image1.debugDisposed, isFalse);
expect(image2.debugDisposed, isFalse);
expect(image3.debugDisposed, isFalse);
result.dispose();
expect(image1.debugDisposed, isTrue);
expect(image2.debugDisposed, isTrue);
expect(image3.debugDisposed, isTrue);
});
});
});
}