mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Previously benchmark_test.dart would break if run with --update-goldens. Now the entire test suite can be run with --update-goldens.
216 lines
8.7 KiB
Dart
216 lines
8.7 KiB
Dart
// Copyright 2018 The Chromium 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';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:test_api/test_api.dart' as test_package show TestFailure;
|
|
|
|
/// Compares rasterized image bytes against a golden image file.
|
|
///
|
|
/// Instances of this comparator will be used as the backend for
|
|
/// [matchesGoldenFile].
|
|
///
|
|
/// Instances of this comparator will be invoked by the test framework in the
|
|
/// [TestWidgetsFlutterBinding.runAsync] zone and are thus not subject to the
|
|
/// fake async constraints that are normally imposed on widget tests (i.e. the
|
|
/// need or the ability to call [WidgetTester.pump] to advance the microtask
|
|
/// queue).
|
|
abstract class GoldenFileComparator {
|
|
/// Compares [imageBytes] against the golden file identified by [golden].
|
|
///
|
|
/// The returned future completes with a boolean value that indicates whether
|
|
/// [imageBytes] matches the golden file's bytes within the tolerance defined
|
|
/// by the comparator.
|
|
///
|
|
/// In the case of comparison mismatch, the comparator may choose to throw a
|
|
/// [TestFailure] if it wants to control the failure message.
|
|
///
|
|
/// The method by which [golden] is located and by which its bytes are loaded
|
|
/// is left up to the implementation class. For instance, some implementations
|
|
/// may load files from the local file system, whereas others may load files
|
|
/// over the network or from a remote repository.
|
|
Future<bool> compare(Uint8List imageBytes, Uri golden);
|
|
|
|
/// Updates the golden file identified by [golden] with [imageBytes].
|
|
///
|
|
/// This will be invoked in lieu of [compare] when [autoUpdateGoldenFiles]
|
|
/// is `true` (which gets set automatically by the test framework when the
|
|
/// user runs `flutter test --update-goldens`).
|
|
///
|
|
/// The method by which [golden] is located and by which its bytes are written
|
|
/// is left up to the implementation class.
|
|
Future<void> update(Uri golden, Uint8List imageBytes);
|
|
}
|
|
|
|
/// Compares rasterized image bytes against a golden image file.
|
|
///
|
|
/// This comparator is used as the backend for [matchesGoldenFile].
|
|
///
|
|
/// When using `flutter test`, a comparator implemented by [LocalFileComparator]
|
|
/// is used if no other comparator is specified. It treats the golden key as
|
|
/// a relative path from the test file's directory. It will then load the
|
|
/// golden file's bytes from disk and perform a byte-for-byte comparison of the
|
|
/// encoded PNGs, returning true only if there's an exact match.
|
|
///
|
|
/// When using `flutter test --update-goldens`, the [LocalFileComparator]
|
|
/// updates the files on disk to match the rendering.
|
|
///
|
|
/// When using `flutter run`, the default comparator ([TrivialComparator])
|
|
/// is used. It prints a message to the console but otherwise does nothing. This
|
|
/// allows tests to be developed visually on a real device.
|
|
///
|
|
/// Callers may choose to override the default comparator by setting this to a
|
|
/// custom comparator during test set-up (or using directory-level test
|
|
/// configuration). For example, some projects may wish to install a more
|
|
/// intelligent comparator that knows how to decode the PNG images to raw
|
|
/// pixels and compare pixel vales, reporting specific differences between the
|
|
/// images.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [flutter_test] for more information about how to configure tests at the
|
|
/// directory-level.
|
|
GoldenFileComparator get goldenFileComparator => _goldenFileComparator;
|
|
GoldenFileComparator _goldenFileComparator = const TrivialComparator._();
|
|
set goldenFileComparator(GoldenFileComparator value) {
|
|
assert(value != null);
|
|
_goldenFileComparator = value;
|
|
}
|
|
|
|
/// Whether golden files should be automatically updated during tests rather
|
|
/// than compared to the image bytes recorded by the tests.
|
|
///
|
|
/// When this is `true`, [matchesGoldenFile] will always report a successful
|
|
/// match, because the bytes being tested implicitly become the new golden.
|
|
///
|
|
/// The Flutter tool will automatically set this to `true` when the user runs
|
|
/// `flutter test --update-goldens`, so callers should generally never have to
|
|
/// explicitly modify this value.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [goldenFileComparator]
|
|
bool autoUpdateGoldenFiles = false;
|
|
|
|
/// Placeholder comparator that is set as the value of [goldenFileComparator]
|
|
/// when the initialization that happens in the test bootstrap either has not
|
|
/// yet happened or has been bypassed.
|
|
///
|
|
/// The test bootstrap file that gets generated by the Flutter tool when the
|
|
/// user runs `flutter test` is expected to set [goldenFileComparator] to
|
|
/// a comparator that resolves golden file references relative to the test
|
|
/// directory. From there, the caller may choose to override the comparator by
|
|
/// setting it to another value during test initialization. The only case
|
|
/// where we expect it to remain uninitialized is when the user runs a test
|
|
/// via `flutter run`. In this case, the [compare] method will just print a
|
|
/// message that it would have otherwise run a real comparison, and it will
|
|
/// return trivial success.
|
|
///
|
|
/// This class can't be constructed. It represents the default value of
|
|
/// [goldenFileComparator].
|
|
class TrivialComparator implements GoldenFileComparator {
|
|
const TrivialComparator._();
|
|
|
|
@override
|
|
Future<bool> compare(Uint8List imageBytes, Uri golden) {
|
|
debugPrint('Golden file comparison requested for "$golden"; skipping...');
|
|
return Future<bool>.value(true);
|
|
}
|
|
|
|
@override
|
|
Future<void> update(Uri golden, Uint8List imageBytes) {
|
|
throw StateError('goldenFileComparator has not been initialized');
|
|
}
|
|
}
|
|
|
|
/// The default [GoldenFileComparator] implementation for `flutter test`.
|
|
///
|
|
/// This comparator loads golden files from the local file system, treating the
|
|
/// golden key as a relative path from the test file's directory.
|
|
///
|
|
/// This comparator performs a very simplistic comparison, doing a byte-for-byte
|
|
/// comparison of the encoded PNGs, returning true only if there's an exact
|
|
/// match. This means it will fail the test if two PNGs represent the same
|
|
/// pixels but are encoded differently.
|
|
///
|
|
/// When using `flutter test --update-goldens`, [LocalFileComparator]
|
|
/// updates the files on disk to match the rendering.
|
|
class LocalFileComparator implements GoldenFileComparator {
|
|
/// Creates a new [LocalFileComparator] for the specified [testFile].
|
|
///
|
|
/// Golden file keys will be interpreted as file paths relative to the
|
|
/// directory in which [testFile] resides.
|
|
///
|
|
/// The [testFile] URL must represent a file.
|
|
LocalFileComparator(Uri testFile, {path.Style pathStyle})
|
|
: basedir = _getBasedir(testFile, pathStyle),
|
|
_path = _getPath(pathStyle);
|
|
|
|
static path.Context _getPath(path.Style style) {
|
|
return path.Context(style: style ?? path.Style.platform);
|
|
}
|
|
|
|
static Uri _getBasedir(Uri testFile, path.Style pathStyle) {
|
|
final path.Context context = _getPath(pathStyle);
|
|
final String testFilePath = context.fromUri(testFile);
|
|
final String testDirectoryPath = context.dirname(testFilePath);
|
|
return context.toUri(testDirectoryPath + context.separator);
|
|
}
|
|
|
|
/// The directory in which the test was loaded.
|
|
///
|
|
/// Golden file keys will be interpreted as file paths relative to this
|
|
/// directory.
|
|
final Uri basedir;
|
|
|
|
/// Path context exists as an instance variable rather than just using the
|
|
/// system path context in order to support testing, where we can spoof the
|
|
/// platform to test behaviors with arbitrary path styles.
|
|
final path.Context _path;
|
|
|
|
@override
|
|
Future<bool> compare(Uint8List imageBytes, Uri golden) async {
|
|
final File goldenFile = _getFile(golden);
|
|
if (!goldenFile.existsSync()) {
|
|
throw test_package.TestFailure('Could not be compared against non-existent file: "$golden"');
|
|
}
|
|
final List<int> goldenBytes = await goldenFile.readAsBytes();
|
|
return _areListsEqual<int>(imageBytes, goldenBytes);
|
|
}
|
|
|
|
@override
|
|
Future<void> update(Uri golden, Uint8List imageBytes) async {
|
|
final File goldenFile = _getFile(golden);
|
|
await goldenFile.parent.create(recursive: true);
|
|
await goldenFile.writeAsBytes(imageBytes, flush: true);
|
|
}
|
|
|
|
File _getFile(Uri golden) {
|
|
return File(_path.join(_path.fromUri(basedir), _path.fromUri(golden.path)));
|
|
}
|
|
|
|
static bool _areListsEqual<T>(List<T> list1, List<T> list2) {
|
|
if (identical(list1, list2)) {
|
|
return true;
|
|
}
|
|
if (list1 == null || list2 == null) {
|
|
return false;
|
|
}
|
|
final int length = list1.length;
|
|
if (length != list2.length) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < length; i++) {
|
|
if (list1[i] != list2[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|