| // Copyright 2013 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 'package:golden_tests_harvester/golden_tests_harvester.dart'; |
| import 'package:litetest/litetest.dart'; |
| import 'package:path/path.dart' as p; |
| |
| void main() async { |
| Future<void> withTempDirectory( |
| FutureOr<void> Function(io.Directory) callback) async { |
| final io.Directory tempDirectory = await io.Directory.systemTemp |
| .createTemp('golden_tests_harvester_test.'); |
| try { |
| await callback(tempDirectory); |
| } finally { |
| await tempDirectory.delete(recursive: true); |
| } |
| } |
| |
| test('should fail on a missing directory', () async { |
| await withTempDirectory((io.Directory tempDirectory) async { |
| final StringSink stderr = StringBuffer(); |
| final ArgumentError error = await _expectThrow<ArgumentError>(() async { |
| await Harvester.create( |
| io.Directory(p.join(tempDirectory.path, 'non_existent')), |
| stderr, |
| addImageToSkiaGold: _alwaysThrowsAddImg); |
| }); |
| expect(error.message, contains('non_existent')); |
| expect(stderr.toString(), isEmpty); |
| }); |
| }); |
| |
| test('should require a file named "digest.json" in the working directory', |
| () async { |
| await withTempDirectory((io.Directory tempDirectory) async { |
| final StringSink stderr = StringBuffer(); |
| |
| final StateError error = await _expectThrow<StateError>(() async { |
| await Harvester.create( |
| tempDirectory, |
| stderr, |
| addImageToSkiaGold: _alwaysThrowsAddImg); |
| }); |
| expect(error.toString(), contains('digest.json')); |
| expect(stderr.toString(), isEmpty); |
| }); |
| }); |
| |
| test('should throw if "digest.json" is in an unexpected format', () async { |
| await withTempDirectory((io.Directory tempDirectory) async { |
| final StringSink stderr = StringBuffer(); |
| final io.File digestsFile = |
| io.File(p.join(tempDirectory.path, 'digest.json')); |
| await digestsFile |
| .writeAsString('{"dimensions": "not a map", "entries": []}'); |
| |
| final FormatException error = |
| await _expectThrow<FormatException>(() async { |
| await Harvester.create( |
| tempDirectory, |
| stderr, |
| addImageToSkiaGold: _alwaysThrowsAddImg); |
| }); |
| expect(error.message, contains('dimensions')); |
| expect(stderr.toString(), isEmpty); |
| }); |
| }); |
| |
| test('should fail eagerly if addImg fails', () async { |
| await withTempDirectory((io.Directory tempDirectory) async { |
| final io.File digestsFile = |
| io.File(p.join(tempDirectory.path, 'digest.json')); |
| final StringSink stderr = StringBuffer(); |
| await digestsFile.writeAsString(''' |
| { |
| "dimensions": {}, |
| "entries": [ |
| { |
| "filename": "test_name_1.png", |
| "width": 100, |
| "height": 100, |
| "maxDiffPixelsPercent": 0.01, |
| "maxColorDelta": 0 |
| } |
| ] |
| } |
| '''); |
| |
| final FailedComparisonException error = |
| await _expectThrow<FailedComparisonException>(() async { |
| final Harvester harvester = await Harvester.create( |
| tempDirectory, |
| stderr, |
| addImageToSkiaGold: _alwaysThrowsAddImg); |
| await harvest(harvester); |
| }); |
| expect(error.testName, 'test_name_1.png'); |
| expect(stderr.toString(), contains('IntentionalError')); |
| }); |
| }); |
| |
| test('should invoke addImg per test', () async { |
| await withTempDirectory((io.Directory tempDirectory) async { |
| final io.File digestsFile = |
| io.File(p.join(tempDirectory.path, 'digest.json')); |
| await digestsFile.writeAsString(''' |
| { |
| "dimensions": {}, |
| "entries": [ |
| { |
| "filename": "test_name_1.png", |
| "width": 100, |
| "height": 100, |
| "maxDiffPixelsPercent": 0.01, |
| "maxColorDelta": 0 |
| }, |
| { |
| "filename": "test_name_2.png", |
| "width": 200, |
| "height": 200, |
| "maxDiffPixelsPercent": 0.02, |
| "maxColorDelta": 1 |
| } |
| ] |
| } |
| '''); |
| final List<String> addImgCalls = <String>[]; |
| final StringSink stderr = StringBuffer(); |
| |
| final Harvester harvester = |
| await Harvester.create(tempDirectory, stderr, addImageToSkiaGold: ( |
| String testName, |
| io.File goldenFile, { |
| required int screenshotSize, |
| double differentPixelsRate = 0.01, |
| int pixelColorDelta = 0, |
| }) async { |
| addImgCalls.add( |
| '$testName $screenshotSize $differentPixelsRate $pixelColorDelta'); |
| }); |
| await harvest(harvester); |
| expect(addImgCalls, <String>[ |
| 'test_name_1.png 10000 0.01 0', |
| 'test_name_2.png 40000 0.02 1', |
| ]); |
| }); |
| }); |
| |
| test('client has dimensions', () async { |
| await withTempDirectory((io.Directory tempDirectory) async { |
| final StringSink stderr = StringBuffer(); |
| final io.File digestsFile = |
| io.File(p.join(tempDirectory.path, 'digest.json')); |
| await digestsFile |
| .writeAsString('{"dimensions": {"key":"value"}, "entries": []}'); |
| final Harvester harvester = await Harvester.create(tempDirectory, stderr); |
| expect(harvester is SkiaGoldHarvester, true); |
| final SkiaGoldHarvester skiaGoldHarvester = |
| harvester as SkiaGoldHarvester; |
| expect(skiaGoldHarvester.client.dimensions, |
| <String, String>{'key': 'value'}); |
| }); |
| }); |
| |
| test('throws without GOLDCTL', () async { |
| await withTempDirectory((io.Directory tempDirectory) async { |
| final StringSink stderr = StringBuffer(); |
| final io.File digestsFile = |
| io.File(p.join(tempDirectory.path, 'digest.json')); |
| await digestsFile.writeAsString(''' |
| { |
| "dimensions": {"key":"value"}, |
| "entries": [ |
| { |
| "filename": "foo.png", |
| "width": 100, |
| "height": 100, |
| "maxDiffPixelsPercent": 0.01, |
| "maxColorDelta": 0 |
| } |
| ]} |
| '''); |
| final Harvester harvester = await Harvester.create(tempDirectory, stderr); |
| final StateError error = await _expectThrow<StateError>(() async { |
| await harvest(harvester); |
| }); |
| expect(error.message, contains('GOLDCTL')); |
| expect(stderr.toString(), isEmpty); |
| }); |
| }); |
| } |
| |
| |
| FutureOr<T> _expectThrow<T extends Object>( |
| FutureOr<void> Function() callback) async { |
| try { |
| await callback(); |
| fail('Expected an exception of type $T'); |
| } on T catch (e) { |
| return e; |
| } catch (e) { |
| fail('Expected an exception of type $T, but got $e'); |
| } |
| // fail(...) unfortunately does not return Never, but it does always throw. |
| throw UnsupportedError('Unreachable'); |
| } |
| |
| final class _IntentionalError extends Error {} |
| |
| Future<void> _alwaysThrowsAddImg( |
| String testName, |
| io.File goldenFile, { |
| required int screenshotSize, |
| double differentPixelsRate = 0.01, |
| int pixelColorDelta = 0, |
| }) async { |
| throw _IntentionalError(); |
| } |