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. // Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:file/file.dart'; import 'package:file/file.dart';
...@@ -12,6 +12,8 @@ import 'package:meta/meta.dart'; ...@@ -12,6 +12,8 @@ import 'package:meta/meta.dart';
import 'package:flutter_goldens_client/client.dart'; import 'package:flutter_goldens_client/client.dart';
export '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 /// Main method that can be used in a `flutter_test_config.dart` file to set
/// [goldenFileComparator] to an instance of [FlutterGoldenFileComparator] that /// [goldenFileComparator] to an instance of [FlutterGoldenFileComparator] that
...@@ -25,12 +27,14 @@ Future<void> main(FutureOr<void> testMain()) async { ...@@ -25,12 +27,14 @@ Future<void> main(FutureOr<void> testMain()) async {
/// ///
/// Within the https://github.com/flutter/flutter repository, it's important /// 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 /// 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 /// minimum. To satisfy this requirement, this comparator uses the
/// files from a sibling repository, `flutter/goldens`. /// [SkiaGoldClient] to upload widgets for golden tests and process results.
/// ///
/// This comparator will locally clone the `flutter/goldens` repository into /// This comparator will locally clone the `flutter/goldens` repository into
/// the `$FLUTTER_ROOT/bin/cache/pkg/goldens` folder, then perform the comparison against /// the `$FLUTTER_ROOT/bin/cache/pkg/goldens` folder, then perform the comparison against
/// the files therein. /// the files therein.
/// This comparator will instantiate the [SkiaGoldClient] and process the
/// results of the test.
class FlutterGoldenFileComparator implements GoldenFileComparator { class FlutterGoldenFileComparator implements GoldenFileComparator {
/// Creates a [FlutterGoldenFileComparator] that will resolve golden file /// Creates a [FlutterGoldenFileComparator] that will resolve golden file
/// URIs relative to the specified [basedir]. /// URIs relative to the specified [basedir].
...@@ -49,48 +53,36 @@ class FlutterGoldenFileComparator implements GoldenFileComparator { ...@@ -49,48 +53,36 @@ class FlutterGoldenFileComparator implements GoldenFileComparator {
@visibleForTesting @visibleForTesting
final FileSystem fs; final FileSystem fs;
/// Instance of the [SkiaGoldClient] for executing tests.
final SkiaGoldClient _skiaClient = SkiaGoldClient();
/// Creates a new [FlutterGoldenFileComparator] that mirrors the relative /// Creates a new [FlutterGoldenFileComparator] that mirrors the relative
/// path resolution of the default [goldenFileComparator]. /// path resolution of the default [goldenFileComparator].
/// ///
/// By the time the future completes, the clone of the `flutter/goldens` /// The [defaultComparator] parameter is visible for testing
/// repository is guaranteed to be ready use.
///
/// The [goldens] and [defaultComparator] parameters are visible for testing
/// purposes only. /// purposes only.
static Future<FlutterGoldenFileComparator> fromDefaultComparator({ static Future<FlutterGoldenFileComparator> fromDefaultComparator({
GoldensClient goldens,
LocalFileComparator defaultComparator, LocalFileComparator defaultComparator,
}) async { }) async {
defaultComparator ??= goldenFileComparator; defaultComparator ??= goldenFileComparator;
// Prepare the goldens repo.
goldens ??= GoldensClient();
await goldens.prepare();
// Calculate the appropriate basedir for the current test context. // 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 Directory testDirectory = fs.directory(defaultComparator.basedir);
final String testDirectoryRelativePath = fs.path.relative(testDirectory.path, from: goldens.flutterRoot.path); final Directory flutterRoot = fs.directory(Platform.environment[_kFlutterRootKey]);
return FlutterGoldenFileComparator(goldens.repositoryRoot.childDirectory(testDirectoryRelativePath).uri); 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 @override
Future<bool> compare(Uint8List imageBytes, Uri golden) async { Future<bool> compare(Uint8List imageBytes, Uri golden) async {
final File goldenFile = _getGoldenFile(golden); final File goldenFile = _getGoldenFile(golden);
if (!goldenFile.existsSync()) {
throw TestFailure('Could not be compared against non-existent file: "$golden"'); bool authorized = await _skiaClient.auth(fs.directory(basedir));
} bool tested = await _skiaClient.imgtest(golden.path, goldenFile);
final List<int> goldenBytes = await goldenFile.readAsBytes(); return true; // TEMP
// TODO(tvolkert): Improve the intelligence of this comparison. //TODO(katelovett): Process results
if (goldenBytes.length != imageBytes.length) {
return false;
}
for (int i = 0; i < goldenBytes.length; i++) {
if (goldenBytes[i] != imageBytes[i]) {
return false;
}
}
return true;
} }
@override @override
...@@ -103,4 +95,5 @@ class FlutterGoldenFileComparator implements GoldenFileComparator { ...@@ -103,4 +95,5 @@ class FlutterGoldenFileComparator implements GoldenFileComparator {
File _getGoldenFile(Uri uri) { File _getGoldenFile(Uri uri) {
return fs.directory(basedir).childFile(fs.file(uri).path); return fs.directory(basedir).childFile(fs.file(uri).path);
} }
} }
...@@ -296,6 +296,16 @@ AsyncMatcher matchesGoldenFile(dynamic key) { ...@@ -296,6 +296,16 @@ AsyncMatcher matchesGoldenFile(dynamic key) {
throw ArgumentError('Unexpected type for golden file: ${key.runtimeType}'); 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 /// Asserts that a [Finder], [Future<ui.Image>], or [ui.Image] matches a
/// reference image identified by [image]. /// reference image identified by [image].
/// ///
...@@ -1688,6 +1698,53 @@ class _MatchesGoldenFile extends AsyncMatcher { ...@@ -1688,6 +1698,53 @@ class _MatchesGoldenFile extends AsyncMatcher {
description.add('one widget whose rasterized image matches golden image "$key"'); 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 { class _MatchesSemanticsData extends Matcher {
_MatchesSemanticsData({ _MatchesSemanticsData({
this.label, 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