import 'dart:async';
import 'dart:typed_data';
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:flutter_goldens_client/client.dart';
import 'package:flutter_goldens_client/skia_client.dart';
export 'package:flutter_goldens_client/client.dart';
export 'package:flutter_goldens_client/skia_client.dart';
Future<void> main(FutureOr<void> testMain()) async {
const Platform platform = LocalPlatform();
if (FlutterSkiaGoldFileComparator.isAvailableOnPlatform(platform)) {
goldenFileComparator = await FlutterSkiaGoldFileComparator.fromDefaultComparator();
} else if (FlutterGoldensRepositoryFileComparator.isAvailableOnPlatform(platform)) {
goldenFileComparator = await FlutterGoldensRepositoryFileComparator.fromDefaultComparator();
} else {
goldenFileComparator = FlutterSkippingGoldenFileComparator.fromDefaultComparator();
}
await testMain();
}
abstract class FlutterGoldenFileComparator extends GoldenFileComparator {
@visibleForTesting
FlutterGoldenFileComparator(
this.basedir, {
this.fs = const LocalFileSystem(),
this.platform = const LocalPlatform(),
}) : assert(basedir != null),
assert(fs != null),
assert(platform != null);
final Uri basedir;
@visibleForTesting
final FileSystem fs;
@visibleForTesting
final Platform platform;
@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);
}
@protected
@visibleForTesting
static Directory getBaseDirectory(GoldensClient goldens, LocalFileComparator defaultComparator) {
final FileSystem fs = goldens.fs;
final Directory testDirectory = fs.directory(defaultComparator.basedir);
final String testDirectoryRelativePath = fs.path.relative(testDirectory.path, from: goldens.flutterRoot.path);
return goldens.comparisonRoot.childDirectory(testDirectoryRelativePath);
}
@protected
File getGoldenFile(Uri uri) {
assert(basedir.scheme == 'file');
final File goldenFile = fs.directory(basedir).childFile(fs.file(uri).path);
assert(goldenFile.uri.scheme == 'file');
return goldenFile;
}
}
class FlutterGoldensRepositoryFileComparator extends FlutterGoldenFileComparator {
FlutterGoldensRepositoryFileComparator(
Uri basedir, {
FileSystem fs = const LocalFileSystem(),
Platform platform = const LocalPlatform(),
}) : super(
basedir,
fs: fs,
platform: platform,
);
static Future<FlutterGoldensRepositoryFileComparator> fromDefaultComparator({
GoldensRepositoryClient goldens,
LocalFileComparator defaultComparator,
}) async {
defaultComparator ??= goldenFileComparator;
goldens ??= GoldensRepositoryClient();
await goldens.prepare();
final Directory baseDirectory = FlutterGoldenFileComparator.getBaseDirectory(goldens, defaultComparator);
return FlutterGoldensRepositoryFileComparator(baseDirectory.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();
if (goldenBytes.length != imageBytes.length) {
return false;
}
for (int i = 0; i < goldenBytes.length; i++) {
if (goldenBytes[i] != imageBytes[i]) {
return false;
}
}
return true;
}
static bool isAvailableOnPlatform(Platform platform) => platform.isLinux;
}
class FlutterSkiaGoldFileComparator extends FlutterGoldenFileComparator {
FlutterSkiaGoldFileComparator(
final Uri basedir,
this.skiaClient, {
FileSystem fs = const LocalFileSystem(),
Platform platform = const LocalPlatform(),
}) : super(
basedir,
fs: fs,
platform: platform,
);
final SkiaGoldClient skiaClient;
static Future<FlutterSkiaGoldFileComparator> fromDefaultComparator({
SkiaGoldClient goldens,
LocalFileComparator defaultComparator,
}) async {
defaultComparator ??= goldenFileComparator;
goldens ??= SkiaGoldClient();
final Directory baseDirectory = FlutterGoldenFileComparator.getBaseDirectory(goldens, defaultComparator);
if (!baseDirectory.existsSync())
baseDirectory.createSync(recursive: true);
await goldens.auth(baseDirectory);
await goldens.imgtestInit();
return FlutterSkiaGoldFileComparator(baseDirectory.uri, goldens);
}
@override
Future<bool> compare(Uint8List imageBytes, Uri golden) async {
golden = _addPrefix(golden);
await update(golden, imageBytes);
final File goldenFile = getGoldenFile(golden);
if (!goldenFile.existsSync()) {
throw TestFailure('Could not be compared against non-existent file: "$golden"');
}
return await skiaClient.imgtestAdd(golden.path, goldenFile);
}
@override
Uri getTestUri(Uri key, int version) => key;
static bool isAvailableOnPlatform(Platform platform) {
final String cirrusCI = platform.environment['CIRRUS_CI'] ?? '';
final String cirrusPR = platform.environment['CIRRUS_PR'] ?? '';
final String cirrusBranch = platform.environment['CIRRUS_BRANCH'] ?? '';
final String goldServiceAccount = platform.environment['GOLD_SERVICE_ACCOUNT'] ?? '';
return cirrusCI.isNotEmpty
&& cirrusPR.isEmpty
&& cirrusBranch == 'master'
&& goldServiceAccount.isNotEmpty;
}
Uri _addPrefix(Uri golden) {
final String prefix = basedir.pathSegments[basedir.pathSegments.length - 2];
return Uri.parse(prefix + '.' + golden.toString());
}
}
class FlutterSkippingGoldenFileComparator extends FlutterGoldenFileComparator {
FlutterSkippingGoldenFileComparator(Uri basedir) : super(basedir);
static FlutterSkippingGoldenFileComparator fromDefaultComparator({
LocalFileComparator defaultComparator,
}) {
defaultComparator ??= goldenFileComparator;
return FlutterSkippingGoldenFileComparator(defaultComparator.basedir);
}
@override
Future<bool> compare(Uint8List imageBytes, Uri golden) async {
print('Skipping "$golden" test : Skia Gold is not available in this testing '
'environment and flutter/goldens repository comparison is only available '
'on Linux machines.'
);
return true;
}
@override
Future<void> update(Uri golden, Uint8List imageBytes) => null;
}