Commit b6a2efb7 authored by Kate Lovett's avatar Kate Lovett

Committing progress. Documentation and testing incomplete/in progress. Code cleanup needed as well.

parent efe744a3
// 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:file/file.dart';
......@@ -12,6 +12,8 @@ import 'package:meta/meta.dart';
import 'package:flutter_goldens_client/client.dart';
export 'package:flutter_goldens_client/client.dart';
//TODO(katelovett): Tests [flutter_goldens_test.dart] and inline documentation
const String _kFlutterRootKey = 'FLUTTER_ROOT';
/// Main method that can be used in a `flutter_test_config.dart` file to set
/// [goldenFileComparator] to an instance of [FlutterGoldenFileComparator] that
......@@ -25,12 +27,14 @@ Future<void> main(FutureOr<void> testMain()) async {
///
/// Within the https://github.com/flutter/flutter repository, it's important
/// not to check-in binaries in order to keep the size of the repository to a
/// minimum. To satisfy this requirement, this comparator retrieves the golden
/// files from a sibling repository, `flutter/goldens`.
/// minimum. To satisfy this requirement, this comparator uses the
/// [SkiaGoldClient] to upload widgets for golden tests and process results.
///
/// This comparator will locally clone the `flutter/goldens` repository into
/// the `$FLUTTER_ROOT/bin/cache/pkg/goldens` folder, then perform the comparison against
/// the files therein.
/// This comparator will instantiate the [SkiaGoldClient] and process the
/// results of the test.
class FlutterGoldenFileComparator implements GoldenFileComparator {
/// Creates a [FlutterGoldenFileComparator] that will resolve golden file
/// URIs relative to the specified [basedir].
......@@ -49,48 +53,36 @@ class FlutterGoldenFileComparator implements GoldenFileComparator {
@visibleForTesting
final FileSystem fs;
/// Instance of the [SkiaGoldClient] for executing tests.
final SkiaGoldClient _skiaClient = SkiaGoldClient();
/// Creates a new [FlutterGoldenFileComparator] that mirrors the relative
/// path resolution of the default [goldenFileComparator].
///
/// By the time the future completes, the clone of the `flutter/goldens`
/// repository is guaranteed to be ready use.
///
/// The [goldens] and [defaultComparator] parameters are visible for testing
/// The [defaultComparator] parameter is visible for testing
/// purposes only.
static Future<FlutterGoldenFileComparator> fromDefaultComparator({
GoldensClient goldens,
LocalFileComparator defaultComparator,
}) async {
defaultComparator ??= goldenFileComparator;
// Prepare the goldens repo.
goldens ??= GoldensClient();
await goldens.prepare();
// Calculate the appropriate basedir for the current test context.
final FileSystem fs = goldens.fs;
const FileSystem fs = LocalFileSystem();
final Directory testDirectory = fs.directory(defaultComparator.basedir);
final String testDirectoryRelativePath = fs.path.relative(testDirectory.path, from: goldens.flutterRoot.path);
return FlutterGoldenFileComparator(goldens.repositoryRoot.childDirectory(testDirectoryRelativePath).uri);
final Directory flutterRoot = fs.directory(Platform.environment[_kFlutterRootKey]);
final Directory goldenRoot = flutterRoot.childDirectory(fs.path.join('bin', 'cache', 'pkg', 'goldens'));
final String testDirectoryRelativePath = fs.path.relative(testDirectory.path, from: flutterRoot.path);
return FlutterGoldenFileComparator(goldenRoot.childDirectory(testDirectoryRelativePath).uri);
}
@override
Future<bool> compare(Uint8List imageBytes, Uri golden) async {
final File goldenFile = _getGoldenFile(golden);
if (!goldenFile.existsSync()) {
throw TestFailure('Could not be compared against non-existent file: "$golden"');
}
final List<int> goldenBytes = await goldenFile.readAsBytes();
// TODO(tvolkert): Improve the intelligence of this comparison.
if (goldenBytes.length != imageBytes.length) {
return false;
}
for (int i = 0; i < goldenBytes.length; i++) {
if (goldenBytes[i] != imageBytes[i]) {
return false;
}
}
return true;
bool authorized = await _skiaClient.auth(fs.directory(basedir));
bool tested = await _skiaClient.imgtest(golden.path, goldenFile);
return true; // TEMP
//TODO(katelovett): Process results
}
@override
......@@ -103,4 +95,5 @@ class FlutterGoldenFileComparator implements GoldenFileComparator {
File _getGoldenFile(Uri uri) {
return fs.directory(basedir).childFile(fs.file(uri).path);
}
}
......@@ -296,6 +296,16 @@ AsyncMatcher matchesGoldenFile(dynamic key) {
throw ArgumentError('Unexpected type for golden file: ${key.runtimeType}');
}
/// TODO(katelovett): Documentation
AsyncMatcher matchesSkiaGoldFile(dynamic key) {
if (key is Uri) {
return _MatchesSkiaGoldFile(key);
} else if (key is String) {
return _MatchesSkiaGoldFile.forStringPath(key);
}
throw ArgumentError('Unexpected type for Skia Gold file: ${key.runtimeType}');
}
/// Asserts that a [Finder], [Future<ui.Image>], or [ui.Image] matches a
/// reference image identified by [image].
///
......@@ -1688,6 +1698,53 @@ class _MatchesGoldenFile extends AsyncMatcher {
description.add('one widget whose rasterized image matches golden image "$key"');
}
class _MatchesSkiaGoldFile extends AsyncMatcher {
const _MatchesSkiaGoldFile(this.key);
_MatchesSkiaGoldFile.forStringPath(String path) : key = Uri.parse(path);
final Uri key;
@override
Future<String> matchAsync(dynamic item) async {
Future<ui.Image> imageFuture;
if (item is Future<ui.Image>) {
imageFuture = item;
}else if (item is ui.Image) {
imageFuture = Future<ui.Image>.value(item);
} else {
final Finder finder = item;
final Iterable<Element> elements = finder.evaluate();
if (elements.isEmpty) {
return 'could not be rendered because no widget was found.';
} else if (elements.length > 1) {
return 'matched too many widgets.';
}
imageFuture = _captureImage(elements.single);
}
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
return binding.runAsync<String>(() async {
final ui.Image image = await imageFuture;
final ByteData bytes = await image.toByteData(format: ui.ImageByteFormat.png)
.timeout(const Duration(seconds: 10), onTimeout: () => null);
if (bytes == null)
return 'Failed to generate screenshot from engine within the 10,000ms timeout';
await goldenFileComparator.update(key, bytes.buffer.asUint8List());
try {
final bool success = await goldenFileComparator.compare(null, key);
return success ? null : 'Skia Gold test fail.';
} on TestFailure catch (ex) {
return ex.message;
}
}, additionalTime: const Duration(seconds: 11));
}
@override
Description describe(Description description) =>
description.add('one widget whose rasterized images matches Skia Gold image $key');
}
class _MatchesSemanticsData extends Matcher {
_MatchesSemanticsData({
this.label,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment