Add Flutter-repo-specific golden comparator (#17205)
In order to avoid checking binaries into flutter/flutter, this comparator can be used to retrieve golden files from a sibling flutter/goldens repository. https://github.com/flutter/flutter/issues/16859
This commit is contained in:
parent
50afda01e1
commit
65079ad5f2
1
bin/internal/goldens.version
Normal file
1
bin/internal/goldens.version
Normal file
@ -0,0 +1 @@
|
||||
0ea80e6a0147f1a3a59ff57f460a3f038a0d2748
|
@ -64,4 +64,4 @@ dev_dependencies:
|
||||
web_socket_channel: 1.0.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
yaml: 2.1.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
# PUBSPEC CHECKSUM: 2b15
|
||||
# PUBSPEC CHECKSUM: f3fb
|
||||
|
@ -29,6 +29,8 @@ dev_dependencies:
|
||||
sdk: flutter
|
||||
flutter_driver:
|
||||
sdk: flutter
|
||||
flutter_goldens:
|
||||
sdk: flutter
|
||||
|
||||
args: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
@ -209,4 +211,4 @@ flutter:
|
||||
- asset: packages/flutter_gallery_assets/fonts/GoogleSansDisplay-Regular.ttf
|
||||
weight: 400
|
||||
|
||||
# PUBSPEC CHECKSUM: 50c7
|
||||
# PUBSPEC CHECKSUM: 7b1f
|
||||
|
5
examples/flutter_gallery/test/flutter_test_config.dart
Normal file
5
examples/flutter_gallery/test/flutter_test_config.dart
Normal file
@ -0,0 +1,5 @@
|
||||
// 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.
|
||||
|
||||
export 'package:flutter_goldens/flutter_goldens.dart' show main;
|
@ -16,6 +16,8 @@ dependencies:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_goldens:
|
||||
sdk: flutter
|
||||
mockito: 3.0.0-alpha+3
|
||||
|
||||
args: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
@ -67,4 +69,4 @@ dev_dependencies:
|
||||
environment:
|
||||
sdk: '>=1.19.0 <2.0.0'
|
||||
|
||||
# PUBSPEC CHECKSUM: a5bf
|
||||
# PUBSPEC CHECKSUM: 8817
|
||||
|
5
packages/flutter/test/flutter_test_config.dart
Normal file
5
packages/flutter/test/flutter_test_config.dart
Normal file
@ -0,0 +1,5 @@
|
||||
// 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.
|
||||
|
||||
export 'package:flutter_goldens/flutter_goldens.dart' show main;
|
217
packages/flutter_goldens/lib/flutter_goldens.dart
Normal file
217
packages/flutter_goldens/lib/flutter_goldens.dart
Normal file
@ -0,0 +1,217 @@
|
||||
// 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' as io;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/local.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
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
|
||||
/// works for the current test.
|
||||
Future<void> main(FutureOr<void> testMain()) async {
|
||||
goldenFileComparator = await FlutterGoldenFileComparator.fromDefaultComparator();
|
||||
await testMain();
|
||||
}
|
||||
|
||||
/// A golden file comparator specific to the `flutter/flutter` repository.
|
||||
///
|
||||
/// 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`.
|
||||
///
|
||||
/// 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.
|
||||
class FlutterGoldenFileComparator implements GoldenFileComparator {
|
||||
@visibleForTesting
|
||||
FlutterGoldenFileComparator(
|
||||
this.goldens,
|
||||
this.basedir, {
|
||||
this.fs: const LocalFileSystem(),
|
||||
});
|
||||
|
||||
final GoldensClient goldens;
|
||||
final Uri basedir;
|
||||
final FileSystem fs;
|
||||
|
||||
/// 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.
|
||||
static Future<FlutterGoldenFileComparator> fromDefaultComparator() async {
|
||||
final LocalFileComparator defaultComparator = goldenFileComparator;
|
||||
final GoldensClient goldens = new GoldensClient();
|
||||
await goldens.prepare();
|
||||
return new FlutterGoldenFileComparator(goldens, defaultComparator.basedir);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> compare(Uint8List imageBytes, Uri golden) async {
|
||||
final File goldenFile = _getGoldenFile(golden);
|
||||
if (!goldenFile.existsSync()) {
|
||||
throw new 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.
|
||||
return const ListEquality<int>().equals(goldenBytes, imageBytes);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> update(Uri golden, Uint8List imageBytes) async {
|
||||
final File goldenFile = _getGoldenFile(golden);
|
||||
await goldenFile.parent.create(recursive: true);
|
||||
await goldenFile.writeAsBytes(imageBytes, flush: true);
|
||||
}
|
||||
|
||||
File _getGoldenFile(Uri uri) {
|
||||
final File relativeFile = fs.file(uri);
|
||||
final Directory testDirectory = fs.directory(basedir);
|
||||
final String relativeBase = fs.path.relative(testDirectory.path, from: goldens.flutterRoot.path);
|
||||
return goldens.repositoryRoot.childDirectory(relativeBase).childFile(relativeFile.path);
|
||||
}
|
||||
}
|
||||
|
||||
/// A class that represents a clone of the https://github.com/flutter/goldens
|
||||
/// repository, nested within the `bin/cache` directory of the caller's Flutter
|
||||
/// repository.
|
||||
@visibleForTesting
|
||||
class GoldensClient {
|
||||
GoldensClient({
|
||||
this.fs: const LocalFileSystem(),
|
||||
this.platform: const LocalPlatform(),
|
||||
this.process: const LocalProcessManager(),
|
||||
});
|
||||
|
||||
final FileSystem fs;
|
||||
final Platform platform;
|
||||
final ProcessManager process;
|
||||
|
||||
RandomAccessFile _lock;
|
||||
|
||||
Directory get flutterRoot => fs.directory(platform.environment[_kFlutterRootKey]);
|
||||
|
||||
Directory get repositoryRoot => flutterRoot.childDirectory(fs.path.join('bin', 'cache', 'pkg', 'goldens'));
|
||||
|
||||
/// Prepares the local clone of the `flutter/goldens` repository for golden
|
||||
/// file testing.
|
||||
///
|
||||
/// This ensures that the goldens repository has been cloned into its
|
||||
/// expected location within `bin/cache` and that it is synced to the Git
|
||||
/// revision specified in `bin/internal/goldens.version`.
|
||||
///
|
||||
/// While this is preparing the repository, it obtains a file lock such that
|
||||
/// [GoldensClient] instances in other processes or isolates will not
|
||||
/// duplicate the work that this is doing.
|
||||
Future<void> prepare() async {
|
||||
final String goldensCommit = await _getGoldensCommit();
|
||||
String currentCommit = await _getCurrentCommit();
|
||||
if (currentCommit != goldensCommit) {
|
||||
await _obtainLock();
|
||||
try {
|
||||
// Check the current commit again now that we have the lock.
|
||||
currentCommit = await _getCurrentCommit();
|
||||
if (currentCommit != goldensCommit) {
|
||||
if (currentCommit == null) {
|
||||
await _initRepository();
|
||||
}
|
||||
await _syncTo(goldensCommit);
|
||||
}
|
||||
} finally {
|
||||
await _releaseLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _getGoldensCommit() async {
|
||||
final File versionFile = flutterRoot.childFile(fs.path.join('bin', 'internal', 'goldens.version'));
|
||||
return (await versionFile.readAsString()).trim();
|
||||
}
|
||||
|
||||
Future<String> _getCurrentCommit() async {
|
||||
if (!repositoryRoot.existsSync()) {
|
||||
return null;
|
||||
} else {
|
||||
final io.ProcessResult revParse = await process.run(
|
||||
<String>['git', 'rev-parse', 'HEAD'],
|
||||
workingDirectory: repositoryRoot.path,
|
||||
);
|
||||
return revParse.exitCode == 0 ? revParse.stdout.trim() : null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initRepository() async {
|
||||
await repositoryRoot.create(recursive: true);
|
||||
await _runCommands(
|
||||
<String>[
|
||||
'git init',
|
||||
'git remote add upstream https://github.com/flutter/goldens.git',
|
||||
],
|
||||
workingDirectory: repositoryRoot,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _syncTo(String commit) async {
|
||||
await _runCommands(
|
||||
<String>[
|
||||
'git pull upstream master',
|
||||
'git fetch upstream $commit',
|
||||
'git reset --hard FETCH_HEAD',
|
||||
],
|
||||
workingDirectory: repositoryRoot,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runCommands(
|
||||
List<String> commands, {
|
||||
Directory workingDirectory,
|
||||
}) async {
|
||||
for (String command in commands) {
|
||||
final List<String> parts = command.split(' ');
|
||||
final io.ProcessResult result = await process.run(
|
||||
parts,
|
||||
workingDirectory: workingDirectory?.path,
|
||||
);
|
||||
if (result.exitCode != 0) {
|
||||
throw new NonZeroExitCode(result.exitCode, result.stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _obtainLock() async {
|
||||
final File lockFile = flutterRoot.childFile(fs.path.join('bin', 'cache', 'goldens.lockfile'));
|
||||
await lockFile.create(recursive: true);
|
||||
_lock = await lockFile.open(mode: io.FileMode.WRITE);
|
||||
await _lock.lock(io.FileLock.BLOCKING_EXCLUSIVE);
|
||||
}
|
||||
|
||||
Future<void> _releaseLock() async {
|
||||
await _lock.close();
|
||||
_lock = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Exception that signals a process' exit with a non-zero exit code.
|
||||
class NonZeroExitCode implements Exception {
|
||||
const NonZeroExitCode(this.exitCode, this.stderr) : assert(exitCode != 0);
|
||||
|
||||
final int exitCode;
|
||||
final String stderr;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Exit code $exitCode: $stderr';
|
||||
}
|
||||
}
|
66
packages/flutter_goldens/pubspec.yaml
Normal file
66
packages/flutter_goldens/pubspec.yaml
Normal file
@ -0,0 +1,66 @@
|
||||
name: flutter_goldens
|
||||
|
||||
dependencies:
|
||||
# To update these, use "flutter update-packages --force-upgrade".
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
collection: 1.14.6
|
||||
file: 5.0.0
|
||||
meta: 1.1.2
|
||||
platform: 2.1.2
|
||||
process: 3.0.1
|
||||
|
||||
args: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
convert: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
crypto: 2.0.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
csslib: 0.14.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
glob: 1.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
html: 0.13.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http: 0.11.3+16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http_multi_server: 2.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http_parser: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
intl: 0.15.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
io: 0.3.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
js: 0.6.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
logging: 0.11.3+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
matcher: 0.12.1+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
mime: 0.9.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
multi_server_socket: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
node_preamble: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
package_config: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
package_resolver: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
path: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
plugin: 0.2.0+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
pool: 1.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
pub_semver: 1.3.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
quiver: 0.29.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf: 0.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_packages_handler: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_static: 0.2.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_web_socket: 0.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_map_stack_trace: 1.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_maps: 0.10.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_span: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
stack_trace: 1.9.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
stream_channel: 1.6.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
string_scanner: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
term_glyph: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
test: 0.12.34 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
typed_data: 1.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
utf: 0.9.0+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
vector_math: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
watcher: 0.9.7+7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
web_socket_channel: 1.0.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
yaml: 2.1.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
dev_dependencies:
|
||||
mockito: 3.0.0-alpha+3
|
||||
|
||||
environment:
|
||||
sdk: '>=1.19.0 <2.0.0'
|
||||
|
||||
# PUBSPEC CHECKSUM: 4bf9
|
127
packages/flutter_goldens/test/flutter_goldens_test.dart
Normal file
127
packages/flutter_goldens/test/flutter_goldens_test.dart
Normal file
@ -0,0 +1,127 @@
|
||||
// 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:io' as io;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_goldens/flutter_goldens.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
const String _kFlutterRoot = '/flutter';
|
||||
const String _kRepositoryRoot = '$_kFlutterRoot/bin/cache/pkg/goldens';
|
||||
const String _kVersionFile = '$_kFlutterRoot/bin/internal/goldens.version';
|
||||
const String _kGoldensVersion = '123456abcdef';
|
||||
|
||||
void main() {
|
||||
MemoryFileSystem fs;
|
||||
FakePlatform platform;
|
||||
MockProcessManager process;
|
||||
|
||||
setUp(() {
|
||||
fs = new MemoryFileSystem();
|
||||
platform = new FakePlatform(environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot});
|
||||
process = new MockProcessManager();
|
||||
fs.directory(_kFlutterRoot).createSync(recursive: true);
|
||||
fs.directory(_kRepositoryRoot).createSync(recursive: true);
|
||||
fs.file(_kVersionFile).createSync(recursive: true);
|
||||
fs.file(_kVersionFile).writeAsStringSync(_kGoldensVersion);
|
||||
});
|
||||
|
||||
group('GoldensClient', () {
|
||||
GoldensClient goldens;
|
||||
|
||||
setUp(() {
|
||||
goldens = new GoldensClient(
|
||||
fs: fs,
|
||||
platform: platform,
|
||||
process: process,
|
||||
);
|
||||
});
|
||||
|
||||
group('prepare', () {
|
||||
test('performs minimal work if versions match', () async {
|
||||
when(process.run(typed(captureAny), workingDirectory: typed(captureAny, named: 'workingDirectory')))
|
||||
.thenAnswer((_) => new Future<io.ProcessResult>.value(io.ProcessResult(123, 0, _kGoldensVersion, '')));
|
||||
await goldens.prepare();
|
||||
|
||||
// Verify that we only spawned `git rev-parse HEAD`
|
||||
final VerificationResult verifyProcessRun =
|
||||
verify(process.run(typed(captureAny), workingDirectory: typed(captureAny, named: 'workingDirectory')));
|
||||
verifyProcessRun.called(1);
|
||||
expect(verifyProcessRun.captured.first, <String>['git', 'rev-parse', 'HEAD']);
|
||||
expect(verifyProcessRun.captured.last, _kRepositoryRoot);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('FlutterGoldenFileComparator', () {
|
||||
GoldensClient goldens;
|
||||
MemoryFileSystem fs;
|
||||
FlutterGoldenFileComparator comparator;
|
||||
|
||||
setUp(() {
|
||||
goldens = new MockGoldensClient();
|
||||
fs = new MemoryFileSystem();
|
||||
final Directory flutterRoot = fs.directory('/path/to/flutter')..createSync(recursive: true);
|
||||
final Directory goldensRoot = flutterRoot.childDirectory('bin/cache/goldens')..createSync(recursive: true);
|
||||
final Directory testDirectory = flutterRoot.childDirectory('test/foo/bar')..createSync(recursive: true);
|
||||
comparator = new FlutterGoldenFileComparator(goldens, new Uri.directory(testDirectory.path), fs: fs);
|
||||
when(goldens.flutterRoot).thenReturn(flutterRoot);
|
||||
when(goldens.repositoryRoot).thenReturn(goldensRoot);
|
||||
});
|
||||
|
||||
group('compare', () {
|
||||
test('throws if golden file is not found', () async {
|
||||
try {
|
||||
await comparator.compare(new Uint8List.fromList(<int>[1, 2, 3]), Uri.parse('test.png'));
|
||||
fail('TestFailure expected but not thrown');
|
||||
} on TestFailure catch (error) {
|
||||
expect(error.message, contains('Could not be compared against non-existent file'));
|
||||
}
|
||||
});
|
||||
|
||||
test('returns false if golden bytes do not match', () async {
|
||||
final File goldenFile = fs.file('/path/to/flutter/bin/cache/goldens/test/foo/bar/test.png')
|
||||
..createSync(recursive: true);
|
||||
goldenFile.writeAsBytesSync(<int>[4, 5, 6], flush: true);
|
||||
final bool result = await comparator.compare(new Uint8List.fromList(<int>[1, 2, 3]), Uri.parse('test.png'));
|
||||
expect(result, isFalse);
|
||||
});
|
||||
|
||||
test('returns true if golden bytes match', () async {
|
||||
final File goldenFile = fs.file('/path/to/flutter/bin/cache/goldens/test/foo/bar/test.png')
|
||||
..createSync(recursive: true);
|
||||
goldenFile.writeAsBytesSync(<int>[1, 2, 3], flush: true);
|
||||
final bool result = await comparator.compare(new Uint8List.fromList(<int>[1, 2, 3]), Uri.parse('test.png'));
|
||||
expect(result, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('update', () {
|
||||
test('creates golden file if it does not already exist', () async {
|
||||
final File goldenFile = fs.file('/path/to/flutter/bin/cache/goldens/test/foo/bar/test.png');
|
||||
expect(goldenFile.existsSync(), isFalse);
|
||||
await comparator.update(Uri.parse('test.png'), new Uint8List.fromList(<int>[1, 2, 3]));
|
||||
expect(goldenFile.existsSync(), isTrue);
|
||||
expect(goldenFile.readAsBytesSync(), <int>[1, 2, 3]);
|
||||
});
|
||||
|
||||
test('overwrites golden bytes if golden file already exist', () async {
|
||||
final File goldenFile = fs.file('/path/to/flutter/bin/cache/goldens/test/foo/bar/test.png')
|
||||
..createSync(recursive: true);
|
||||
goldenFile.writeAsBytesSync(<int>[4, 5, 6], flush: true);
|
||||
await comparator.update(Uri.parse('test.png'), new Uint8List.fromList(<int>[1, 2, 3]));
|
||||
expect(goldenFile.readAsBytesSync(), <int>[1, 2, 3]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MockProcessManager extends Mock implements ProcessManager {}
|
||||
class MockGoldensClient extends Mock implements GoldensClient {}
|
@ -66,7 +66,11 @@ abstract class GoldenFileComparator {
|
||||
///
|
||||
/// * [flutter_test] for more information about how to configure tests at the
|
||||
/// directory-level.
|
||||
GoldenFileComparator goldenFileComparator = const _UninitializedComparator();
|
||||
GoldenFileComparator _goldenFileComparator = const _UninitializedComparator();
|
||||
GoldenFileComparator get goldenFileComparator => _goldenFileComparator;
|
||||
set goldenFileComparator(GoldenFileComparator comparator) {
|
||||
_goldenFileComparator = comparator ?? const _UninitializedComparator();
|
||||
}
|
||||
|
||||
/// Whether golden files should be automatically updated during tests rather
|
||||
/// than compared to the image bytes recorded by the tests.
|
||||
@ -120,15 +124,19 @@ class LocalFileComparator implements GoldenFileComparator {
|
||||
/// directory in which [testFile] resides.
|
||||
///
|
||||
/// The [testFile] URI must represent a file.
|
||||
LocalFileComparator(Uri testFile)
|
||||
LocalFileComparator(Uri testFile, {path.Style pathStyle})
|
||||
: assert(testFile.scheme == 'file'),
|
||||
basedir = new Uri.directory(_path.dirname(_path.fromUri(testFile)));
|
||||
basedir = _getBasedir(testFile, pathStyle),
|
||||
_path = _getPath(pathStyle);
|
||||
|
||||
// Due to https://github.com/flutter/flutter/issues/17118, we need to
|
||||
// explicitly set the path style.
|
||||
static final path.Context _path = new path.Context(style: Platform.isWindows
|
||||
? path.Style.windows
|
||||
: path.Style.posix);
|
||||
static path.Context _getPath(path.Style style) {
|
||||
return new path.Context(style: style ?? path.Style.platform);
|
||||
}
|
||||
|
||||
static Uri _getBasedir(Uri testFile, path.Style pathStyle) {
|
||||
final path.Context context = _getPath(pathStyle);
|
||||
return context.toUri(context.dirname(context.fromUri(testFile)) + context.separator);
|
||||
}
|
||||
|
||||
/// The directory in which the test was loaded.
|
||||
///
|
||||
@ -136,6 +144,11 @@ class LocalFileComparator implements GoldenFileComparator {
|
||||
/// 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);
|
||||
@ -149,11 +162,12 @@ class LocalFileComparator implements GoldenFileComparator {
|
||||
@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 new File(_path.join(_path.fromUri(basedir), golden.path));
|
||||
return new File(_path.join(_path.fromUri(basedir), _path.fromUri(golden.path)));
|
||||
}
|
||||
|
||||
static bool _areListsEqual<T>(List<T> list1, List<T> list2) {
|
||||
|
@ -24,6 +24,16 @@ void main() {
|
||||
fs = new 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> body()) {
|
||||
test_package.test(description, () {
|
||||
return io.IOOverrides.runZoned(
|
||||
@ -59,18 +69,18 @@ void main() {
|
||||
LocalFileComparator comparator;
|
||||
|
||||
setUp(() {
|
||||
comparator = new LocalFileComparator(new Uri.file('/golden_test.dart'));
|
||||
comparator = new LocalFileComparator(fs.file(fix('/golden_test.dart')).uri, pathStyle: fs.path.style);
|
||||
});
|
||||
|
||||
test('calculates basedir correctly', () {
|
||||
expect(comparator.basedir, new Uri.file('/'));
|
||||
comparator = new LocalFileComparator(new Uri.file('/foo/bar/golden_test.dart'));
|
||||
expect(comparator.basedir, new Uri.file('/foo/bar/'));
|
||||
expect(comparator.basedir, fs.file(fix('/')).uri);
|
||||
comparator = new LocalFileComparator(fs.file(fix('/foo/bar/golden_test.dart')).uri, pathStyle: fs.path.style);
|
||||
expect(comparator.basedir, fs.directory(fix('/foo/bar/')).uri);
|
||||
});
|
||||
|
||||
group('compare', () {
|
||||
Future<bool> doComparison([String golden = 'golden.png']) {
|
||||
final Uri uri = new Uri.file(golden);
|
||||
final Uri uri = fs.file(fix(golden)).uri;
|
||||
return comparator.compare(
|
||||
new Uint8List.fromList(_kExpectedBytes),
|
||||
uri,
|
||||
@ -79,13 +89,13 @@ void main() {
|
||||
|
||||
group('succeeds', () {
|
||||
test('when golden file is in same folder as test', () async {
|
||||
fs.file('/golden.png').writeAsBytesSync(_kExpectedBytes);
|
||||
fs.file(fix('/golden.png')).writeAsBytesSync(_kExpectedBytes);
|
||||
final bool success = await doComparison();
|
||||
expect(success, isTrue);
|
||||
});
|
||||
|
||||
test('when golden file is in subfolder of test', () async {
|
||||
fs.file('/sub/foo.png')
|
||||
fs.file(fix('/sub/foo.png'))
|
||||
..createSync(recursive: true)
|
||||
..writeAsBytesSync(_kExpectedBytes);
|
||||
final bool success = await doComparison('sub/foo.png');
|
||||
@ -100,32 +110,32 @@ void main() {
|
||||
});
|
||||
|
||||
test('when golden bytes are leading subset of image bytes', () async {
|
||||
fs.file('/golden.png').writeAsBytesSync(<int>[1, 2]);
|
||||
fs.file(fix('/golden.png')).writeAsBytesSync(<int>[1, 2]);
|
||||
expect(await doComparison(), isFalse);
|
||||
});
|
||||
|
||||
test('when golden bytes are leading superset of image bytes', () async {
|
||||
fs.file('/golden.png').writeAsBytesSync(<int>[1, 2, 3, 4]);
|
||||
fs.file(fix('/golden.png')).writeAsBytesSync(<int>[1, 2, 3, 4]);
|
||||
expect(await doComparison(), isFalse);
|
||||
});
|
||||
|
||||
test('when golden bytes are trailing subset of image bytes', () async {
|
||||
fs.file('/golden.png').writeAsBytesSync(<int>[2, 3]);
|
||||
fs.file(fix('/golden.png')).writeAsBytesSync(<int>[2, 3]);
|
||||
expect(await doComparison(), isFalse);
|
||||
});
|
||||
|
||||
test('when golden bytes are trailing superset of image bytes', () async {
|
||||
fs.file('/golden.png').writeAsBytesSync(<int>[0, 1, 2, 3]);
|
||||
fs.file(fix('/golden.png')).writeAsBytesSync(<int>[0, 1, 2, 3]);
|
||||
expect(await doComparison(), isFalse);
|
||||
});
|
||||
|
||||
test('when golden bytes are disjoint from image bytes', () async {
|
||||
fs.file('/golden.png').writeAsBytesSync(<int>[4, 5, 6]);
|
||||
fs.file(fix('/golden.png')).writeAsBytesSync(<int>[4, 5, 6]);
|
||||
expect(await doComparison(), isFalse);
|
||||
});
|
||||
|
||||
test('when golden bytes are empty', () async {
|
||||
fs.file('/golden.png').writeAsBytesSync(<int>[]);
|
||||
fs.file(fix('/golden.png')).writeAsBytesSync(<int>[]);
|
||||
expect(await doComparison(), isFalse);
|
||||
});
|
||||
});
|
||||
@ -133,18 +143,18 @@ void main() {
|
||||
|
||||
group('update', () {
|
||||
test('updates existing file', () async {
|
||||
fs.file('/golden.png').writeAsBytesSync(_kExpectedBytes);
|
||||
fs.file(fix('/golden.png')).writeAsBytesSync(_kExpectedBytes);
|
||||
const List<int> newBytes = const <int>[11, 12, 13];
|
||||
await comparator.update(new Uri.file('golden.png'), new Uint8List.fromList(newBytes));
|
||||
expect(fs.file('/golden.png').readAsBytesSync(), newBytes);
|
||||
await comparator.update(fs.file('golden.png').uri, new Uint8List.fromList(newBytes));
|
||||
expect(fs.file(fix('/golden.png')).readAsBytesSync(), newBytes);
|
||||
});
|
||||
|
||||
test('creates non-existent file', () async {
|
||||
expect(fs.file('/foo.png').existsSync(), isFalse);
|
||||
expect(fs.file(fix('/foo.png')).existsSync(), isFalse);
|
||||
const List<int> newBytes = const <int>[11, 12, 13];
|
||||
await comparator.update(new Uri.file('foo.png'), new Uint8List.fromList(newBytes));
|
||||
expect(fs.file('/foo.png').existsSync(), isTrue);
|
||||
expect(fs.file('/foo.png').readAsBytesSync(), newBytes);
|
||||
await comparator.update(fs.file('foo.png').uri, new Uint8List.fromList(newBytes));
|
||||
expect(fs.file(fix('/foo.png')).existsSync(), isTrue);
|
||||
expect(fs.file(fix('/foo.png')).readAsBytesSync(), newBytes);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -685,9 +685,7 @@ void main() {
|
||||
);
|
||||
if (testConfigFile != null) {
|
||||
buffer.write('''
|
||||
return () {
|
||||
test_config.main(test.main);
|
||||
};
|
||||
return () => test_config.main(test.main);
|
||||
''');
|
||||
} else {
|
||||
buffer.write('''
|
||||
|
Loading…
x
Reference in New Issue
Block a user