| // 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:io'; |
| |
| import 'package:engine_repo_tools/engine_repo_tools.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:skia_gold_client/skia_gold_client.dart'; |
| |
| /// An E2E test for the Skia Gold client. |
| /// |
| /// Attempts to answer the question: "Does the Skia Gold client, and our custom |
| /// integration with it (GitHub, LUCI, etc), work as expected?" in an automated |
| /// way. |
| /// |
| /// In a sibling directory, `e2e_fixtures`, there are static (checked-in) files |
| /// that represent (fake) generated output to be uploaded and verified by the |
| /// Skia Gold client. This test, when run on CI, will use these fixtures to |
| /// simulate the process of uploading and verifying real output. |
| /// |
| /// For example, try changing the contents of a fixture file, and then uploading |
| /// a PR with this change. The CI will run this test, and it should fail (on |
| /// pre-submit), since the fixture file no longer matches the expected output. |
| /// |
| /// Next, after the PR is merged, the CI will run this test again, and it should |
| /// pass (on post-submit), since the fixture file now matches the expected |
| /// output. |
| /// |
| /// There are also tests for the "dimensions" feature of the Skia Gold client, |
| /// which live in `e2e_fixtures/dimensions`. These tests are similar to the |
| /// regular tests, but experiment with different fake dimensions to ensure that |
| /// our CI environment is correctly handling this feature. |
| void main() async { |
| // If the client is not available, we can't run the test. |
| if (!SkiaGoldClient.isAvailable()) { |
| stderr.writeln('Skia gold is unavailable in this environment.'); |
| exitCode = 1; |
| return; |
| } |
| |
| // If we're not in an engine repo, we can't run the test. |
| final Engine? engine = Engine.tryFindWithin(); |
| if (engine == null) { |
| stderr.writeln('Must run within the engine repo.'); |
| exitCode = 1; |
| return; |
| } |
| |
| // Create a client. |
| final SkiaGoldClient skiaGoldClient = SkiaGoldClient( |
| engine.flutterDir, |
| ); |
| |
| // Authenticate the client. |
| await skiaGoldClient.auth(); |
| |
| const String prefix = 'SkiaGoldClientE2ETest'; |
| const List<_Digest> digests = <_Digest>[ |
| _Digest( |
| name: '${prefix}_SolidBlueSquare', |
| source: 'e2e_fixtures/solid_blue_square.png', |
| pixelCount: 512 * 512, |
| ), |
| _Digest( |
| name: '${prefix}_SolidRedSquare', |
| source: 'e2e_fixtures/solid_red_square.png', |
| pixelCount: 768 * 768, |
| ), |
| _Digest( |
| name: '${prefix}_SolidGreenSquare', |
| source: 'e2e_fixtures/solid_green_square.png', |
| pixelCount: 1200 * 1200, |
| ), |
| ]; |
| |
| // Upload the digests to Skia Gold. |
| final Set<_Digest> comparisonsFailed = <_Digest>{}; |
| for (final _Digest digest in digests) { |
| final String digestPath = digest.source; |
| final String digestName = digest.name; |
| final File digestFile = File(path.join( |
| engine.flutterDir.path, |
| 'testing', |
| 'skia_gold_client', |
| 'tool', |
| digestPath, |
| )); |
| if (!digestFile.existsSync()) { |
| stderr.writeln('The digest file "$digestPath" does not exist.'); |
| exitCode = 1; |
| return; |
| } |
| |
| print('Uploading digest: $digestName ($digestPath): ${digest.pixelCount} pixels...'); |
| |
| try { |
| await skiaGoldClient.addImg( |
| digestName, |
| digestFile, |
| screenshotSize: 0, |
| ); |
| stderr.writeln('Comparison success: $digestName'); |
| } on Exception catch (e) { |
| stderr.writeln('Comparison failure: $digestName: $e'); |
| comparisonsFailed.add(digest); |
| } |
| } |
| |
| if (comparisonsFailed.isNotEmpty) { |
| stdout.writeln('${comparisonsFailed.length} digest(s) failed.'); |
| for (final _Digest digest in comparisonsFailed) { |
| stderr.writeln(' ${digest.name} (${digest.source})'); |
| } |
| exitCode = 1; |
| return; |
| } |
| } |
| |
| final class _Digest { |
| const _Digest({ |
| required this.name, |
| required this.source, |
| required this.pixelCount, |
| }); |
| |
| /// The name of the digest/test. |
| final String name; |
| |
| /// The source of the digest (e.g. the path to the image file). |
| final String source; |
| |
| /// The number of pixels in the image. |
| final int pixelCount; |
| |
| @override |
| int get hashCode => source.hashCode; |
| |
| @override |
| bool operator ==(Object other) { |
| return other is _Digest && other.source == source; |
| } |
| } |