[Impeller] Skia gold for flutter_tester dart tests. (#47066)

This removes skips for the golden tests in `//testing/dart/canvas_test.dart` and instead passes them up to Skia gold.

Adds a utility class for dealing with Skia gold from these tests, as well as the existing fuzzy identical image comparison for tests that just want to do in memory comparison of images generated from the same test.

Removes the old golden files that were in tree.

Part of https://github.com/flutter/flutter/issues/53784
diff --git a/.ci.yaml b/.ci.yaml
index d84d5a7..2c396e5 100644
--- a/.ci.yaml
+++ b/.ci.yaml
@@ -293,6 +293,10 @@
       add_recipes_cq: "true"
       release_build: "true"
       config_name: linux_host_engine
+      dependencies: >-
+        [
+          {"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"}
+        ]
     drone_dimensions:
       - os=Linux
 
diff --git a/ci/builders/linux_host_engine.json b/ci/builders/linux_host_engine.json
index aae0ac2..8c1f46f 100644
--- a/ci/builders/linux_host_engine.json
+++ b/ci/builders/linux_host_engine.json
@@ -185,6 +185,12 @@
                 "device_type=none",
                 "os=Linux"
             ],
+            "dependencies": [
+                {
+                    "dependency": "goldctl",
+                    "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"
+                }
+            ],
             "gclient_variables": {
                 "download_android_deps": false
             },
diff --git a/impeller/golden_tests_harvester/bin/golden_tests_harvester.dart b/impeller/golden_tests_harvester/bin/golden_tests_harvester.dart
index 21524bf..62a5f45 100644
--- a/impeller/golden_tests_harvester/bin/golden_tests_harvester.dart
+++ b/impeller/golden_tests_harvester/bin/golden_tests_harvester.dart
@@ -16,7 +16,7 @@
 
 /// Fake SkiaGoldClient that is used if the harvester is run outside of Luci.
 class FakeSkiaGoldClient implements SkiaGoldClient {
-  FakeSkiaGoldClient(this._workingDirectory, {this.dimensions});
+  FakeSkiaGoldClient(this._workingDirectory, {this.dimensions, this.verbose = false});
 
   final Directory _workingDirectory;
 
@@ -24,6 +24,9 @@
   final Map<String, String>? dimensions;
 
   @override
+  final bool verbose;
+
+  @override
   Future<void> addImg(String testName, File goldenFile,
       {double differentPixelsRate = 0.01,
       int pixelColorDelta = 0,
diff --git a/impeller/golden_tests_harvester/pubspec.yaml b/impeller/golden_tests_harvester/pubspec.yaml
index d733c8c..02d10c2 100644
--- a/impeller/golden_tests_harvester/pubspec.yaml
+++ b/impeller/golden_tests_harvester/pubspec.yaml
@@ -30,6 +30,8 @@
     path: ../../../third_party/dart/third_party/pkg/file/packages/file
   meta:
     path: ../../../third_party/dart/pkg/meta
+  engine_repo_tools:
+    path: ../../tools/pkg/engine_repo_tools
   path:
     path: ../../../third_party/dart/third_party/pkg/path
   platform:
diff --git a/lib/web_ui/pubspec.yaml b/lib/web_ui/pubspec.yaml
index 1b53ead..e777c0e 100644
--- a/lib/web_ui/pubspec.yaml
+++ b/lib/web_ui/pubspec.yaml
@@ -54,3 +54,7 @@
     path: ../../web_sdk/web_engine_tester
   skia_gold_client:
     path: ../../testing/skia_gold_client
+
+dependency_overrides:
+  engine_repo_tools:
+    path: ../../tools/pkg/engine_repo_tools
diff --git a/testing/dart/BUILD.gn b/testing/dart/BUILD.gn
index d9bd962..8d590a8 100644
--- a/testing/dart/BUILD.gn
+++ b/testing/dart/BUILD.gn
@@ -48,9 +48,11 @@
 ]
 
 foreach(test, tests) {
+  skia_gold_work_dir = rebase_path("$root_gen_dir/skia_gold_$test")
   flutter_frontend_server("compile_$test") {
     main_dart = test
     kernel_output = "$root_gen_dir/$test.dill"
+    extra_args = [ "-DkSkiaGoldWorkDirectory=$skia_gold_work_dir" ]
     package_config = ".dart_tool/package_config.json"
   }
 }
diff --git a/testing/dart/canvas_test.dart b/testing/dart/canvas_test.dart
index d317e2f..8c4937b 100644
--- a/testing/dart/canvas_test.dart
+++ b/testing/dart/canvas_test.dart
@@ -12,6 +12,7 @@
 import 'package:path/path.dart' as path;
 import 'package:vector_math/vector_math_64.dart';
 
+import 'goldens.dart';
 import 'impeller_enabled.dart';
 
 typedef CanvasCallback = void Function(Canvas canvas);
@@ -123,59 +124,9 @@
   });
 }
 
-/// @returns true When the images are reasonably similar.
-/// @todo Make the search actually fuzzy to a certain degree.
-Future<bool> fuzzyCompareImages(Image golden, Image img) async {
-  if (golden.width != img.width || golden.height != img.height) {
-    return false;
-  }
-  int getPixel(ByteData data, int x, int y) => data.getUint32((x + y * golden.width) * 4);
-  final ByteData goldenData = (await golden.toByteData())!;
-  final ByteData imgData = (await img.toByteData())!;
-  for (int y = 0; y < golden.height; y++) {
-    for (int x = 0; x < golden.width; x++) {
-      if (getPixel(goldenData, x, y) != getPixel(imgData, x, y)) {
-        return false;
-      }
-    }
-  }
-  return true;
-}
+void main() async {
+  final ImageComparer comparer = await ImageComparer.create();
 
-Future<void> saveTestImage(Image image, String filename) async {
-  final String imagesPath = path.join('flutter', 'testing', 'resources');
-  final ByteData pngData = (await image.toByteData(format: ImageByteFormat.png))!;
-  final String outPath = path.join(imagesPath, filename);
-  File(outPath).writeAsBytesSync(pngData.buffer.asUint8List());
-  print('wrote: $outPath');
-}
-
-/// @returns true When the images are reasonably similar.
-Future<bool> fuzzyGoldenImageCompare(
-    Image image, String goldenImageName) async {
-  final String imagesPath = path.join('flutter', 'testing', 'resources');
-  final File file = File(path.join(imagesPath, goldenImageName));
-
-  bool areEqual = false;
-
-  if (file.existsSync()) {
-    final Uint8List goldenData = await file.readAsBytes();
-
-    final Codec codec = await instantiateImageCodec(goldenData);
-    final FrameInfo frame = await codec.getNextFrame();
-    expect(frame.image.height, equals(image.height));
-    expect(frame.image.width, equals(image.width));
-
-    areEqual = await fuzzyCompareImages(frame.image, image);
-  }
-
-  if (!areEqual) {
-    saveTestImage(image, 'found_$goldenImageName');
-  }
-  return areEqual;
-}
-
-void main() {
   testNoCrashes();
 
   test('Simple .toImage', () async {
@@ -190,11 +141,8 @@
     }, 100, 100);
     expect(image.width, equals(100));
     expect(image.height, equals(100));
-
-    final bool areEqual =
-        await fuzzyGoldenImageCompare(image, 'canvas_test_toImage.png');
-    expect(areEqual, true);
-  }, skip: impellerEnabled);
+    await comparer.addGoldenImage(image, 'canvas_test_toImage.png');
+  });
 
   Gradient makeGradient() {
     return Gradient.linear(
@@ -212,10 +160,8 @@
     expect(image.width, equals(100));
     expect(image.height, equals(100));
 
-    final bool areEqual =
-        await fuzzyGoldenImageCompare(image, 'canvas_test_dithered_gradient.png');
-    expect(areEqual, true);
-  }, skip: !Platform.isLinux || impellerEnabled); // https://github.com/flutter/flutter/issues/53784
+    await comparer.addGoldenImage(image, 'canvas_test_dithered_gradient.png');
+  });
 
   test('Null values allowed for drawAtlas methods', () async {
     final Image image = await createImage(100, 100);
@@ -302,12 +248,8 @@
       });
     }, width, height);
 
-    final bool areEqual = await fuzzyCompareImages(incrementalMatrixImage, combinedMatrixImage);
+    final bool areEqual = await comparer.fuzzyCompareImages(incrementalMatrixImage, combinedMatrixImage);
 
-    if (!areEqual) {
-      saveTestImage(incrementalMatrixImage, 'incremental_3D_transform_test_image.png');
-      saveTestImage(combinedMatrixImage, 'combined_3D_transform_test_image.png');
-    }
     expect(areEqual, true);
   });
 
@@ -348,10 +290,8 @@
     expect(image.width, equals(200));
     expect(image.height, equals(250));
 
-    final bool areEqual =
-        await fuzzyGoldenImageCompare(image, 'dotted_path_effect_mixed_with_stroked_geometry.png');
-    expect(areEqual, true);
-  }, skip: !Platform.isLinux || impellerEnabled); // https://github.com/flutter/flutter/issues/53784
+    await comparer.addGoldenImage(image, 'dotted_path_effect_mixed_with_stroked_geometry.png');
+  });
 
   test('Gradients with matrices in Paragraphs render correctly', () async {
     final Image image = await toImage((Canvas canvas) {
@@ -400,10 +340,8 @@
     expect(image.width, equals(600));
     expect(image.height, equals(400));
 
-    final bool areEqual =
-    await fuzzyGoldenImageCompare(image, 'text_with_gradient_with_matrix.png');
-    expect(areEqual, true);
-  }, skip: !Platform.isLinux || impellerEnabled); // https://github.com/flutter/flutter/issues/53784
+    await comparer.addGoldenImage(image, 'text_with_gradient_with_matrix.png');
+  });
 
   test('toImageSync - too big', () async {
     PictureRecorder recorder = PictureRecorder();
@@ -602,8 +540,8 @@
     final Image tofuImage = await drawText('>\b<');
 
     // The tab's image should be identical to the space's image but not the tofu's image.
-    final bool tabToSpaceComparison = await fuzzyCompareImages(tabImage, spaceImage);
-    final bool tabToTofuComparison = await fuzzyCompareImages(tabImage, tofuImage);
+    final bool tabToSpaceComparison = await comparer.fuzzyCompareImages(tabImage, spaceImage);
+    final bool tabToTofuComparison = await comparer.fuzzyCompareImages(tabImage, tofuImage);
 
     expect(tabToSpaceComparison, isTrue);
     expect(tabToTofuComparison, isFalse);
diff --git a/testing/dart/goldens.dart b/testing/dart/goldens.dart
new file mode 100644
index 0000000..5a70ecf
--- /dev/null
+++ b/testing/dart/goldens.dart
@@ -0,0 +1,129 @@
+// 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 'dart:typed_data';
+import 'dart:ui';
+
+import 'package:path/path.dart' as path;
+import 'package:skia_gold_client/skia_gold_client.dart';
+
+import 'impeller_enabled.dart';
+
+const String _kSkiaGoldWorkDirectoryKey = 'kSkiaGoldWorkDirectory';
+
+/// A helper for doing image comparison (golden) tests.
+///
+/// Contains utilities for comparing two images in memory that are expected to
+/// be identical, or for adding images to Skia gold for comparison.
+class ImageComparer {
+  ImageComparer._({
+    required SkiaGoldClient client,
+  }) : _client = client;
+
+  // Avoid talking to Skia gold for the force-multithreading variants.
+  static bool get _useSkiaGold =>
+      !Platform.executableArguments.contains('--force-multithreading');
+
+  /// Creates an image comparer and authorizes.
+  static Future<ImageComparer> create({
+    bool verbose = false,
+  }) async {
+    const String workDirectoryPath =
+        String.fromEnvironment(_kSkiaGoldWorkDirectoryKey);
+    if (workDirectoryPath.isEmpty) {
+      throw UnsupportedError(
+          'Using ImageComparer requries defining kSkiaGoldWorkDirectoryKey.');
+    }
+
+    final Directory workDirectory = Directory(
+      impellerEnabled ? '${workDirectoryPath}_iplr' : workDirectoryPath,
+    )..createSync();
+    final Map<String, String> dimensions = <String, String>{
+      'impeller_enabled': impellerEnabled.toString(),
+    };
+    final SkiaGoldClient client = isSkiaGoldClientAvailable && _useSkiaGold
+        ? SkiaGoldClient(workDirectory,
+            dimensions: dimensions, verbose: verbose)
+        : _FakeSkiaGoldClient(workDirectory, dimensions, verbose: verbose);
+
+    await client.auth();
+    return ImageComparer._(client: client);
+  }
+
+  final SkiaGoldClient _client;
+
+  /// Adds an [Image] to Skia Gold for comparison.
+  ///
+  /// The [fileName] must be unique.
+  Future<void> addGoldenImage(Image image, String fileName) async {
+    final ByteData data =
+        (await image.toByteData(format: ImageByteFormat.png))!;
+
+    final File file = File(path.join(_client.workDirectory.path, fileName))
+      ..writeAsBytesSync(data.buffer.asUint8List());
+    await _client.addImg(
+      fileName,
+      file,
+      screenshotSize: image.width * image.height,
+    ).catchError((dynamic error) {
+      print('Skia gold comparison failed: $error');
+      throw Exception('Failed comparison: $fileName');
+    });
+  }
+
+  Future<bool> fuzzyCompareImages(Image golden, Image testImage) async {
+    if (golden.width != testImage.width || golden.height != testImage.height) {
+      return false;
+    }
+    int getPixel(ByteData data, int x, int y) =>
+        data.getUint32((x + y * golden.width) * 4);
+    final ByteData goldenData = (await golden.toByteData())!;
+    final ByteData testImageData = (await testImage.toByteData())!;
+    for (int y = 0; y < golden.height; y++) {
+      for (int x = 0; x < golden.width; x++) {
+        if (getPixel(goldenData, x, y) != getPixel(testImageData, x, y)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+}
+
+// TODO(dnfield): add local comparison against baseline,
+// https://github.com/flutter/flutter/issues/136831
+class _FakeSkiaGoldClient implements SkiaGoldClient {
+  _FakeSkiaGoldClient(
+    this.workDirectory,
+    this.dimensions, {
+    this.verbose = false,
+  });
+
+  @override
+  final Directory workDirectory;
+
+  @override
+  final Map<String, String> dimensions;
+
+  @override
+  final bool verbose;
+
+  @override
+  Future<void> auth() async {}
+
+  @override
+  Future<void> addImg(
+    String testName,
+    File goldenFile, {
+    double differentPixelsRate = 0.01,
+    int pixelColorDelta = 0,
+    required int screenshotSize,
+  }) async {}
+
+  @override
+  dynamic noSuchMethod(Invocation invocation) {
+    throw UnimplementedError(invocation.memberName.toString().split('"')[1]);
+  }
+}
diff --git a/testing/dart/pubspec.yaml b/testing/dart/pubspec.yaml
index 771026f..e13765a 100644
--- a/testing/dart/pubspec.yaml
+++ b/testing/dart/pubspec.yaml
@@ -19,6 +19,7 @@
 dependencies:
   litetest: any
   path: any
+  skia_gold_client: any
   sky_engine: any
   vector_math: any
   vm_service: any
@@ -29,8 +30,14 @@
     path: ../../../third_party/dart/pkg/async_helper
   collection:
     path: ../../../third_party/dart/third_party/pkg/collection
+  crypto:
+    path: ../../../third_party/dart/third_party/pkg/crypto
+  engine_repo_tools:
+    path: ../../tools/pkg/engine_repo_tools
   expect:
     path: ../../../third_party/dart/pkg/expect
+  file:
+    path: ../../../third_party/dart/third_party/pkg/file/packages/file
   fixnum:
     path: ../../../third_party/dart/third_party/pkg/fixnum
   litetest:
@@ -39,12 +46,20 @@
     path: ../../../third_party/dart/pkg/meta
   path:
     path: ../../../third_party/dart/third_party/pkg/path
+  platform:
+    path: ../../../third_party/pkg/platform
+  process:
+    path: ../../../third_party/pkg/process
   protobuf:
     path: ../../../third_party/dart/third_party/pkg/protobuf/protobuf
   smith:
     path: ../../../third_party/dart/pkg/smith
+  skia_gold_client:
+    path: ../skia_gold_client
   sky_engine:
     path: ../../sky/packages/sky_engine
+  typed_data:
+    path: ../../../third_party/dart/third_party/pkg/typed_data
   vector_math:
     path: ../../../third_party/pkg/vector_math
   vm_service:
diff --git a/testing/resources/canvas_test_dithered_gradient.png b/testing/resources/canvas_test_dithered_gradient.png
deleted file mode 100644
index d8062f2..0000000
--- a/testing/resources/canvas_test_dithered_gradient.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/canvas_test_gradient.png b/testing/resources/canvas_test_gradient.png
deleted file mode 100644
index 89f3f63..0000000
--- a/testing/resources/canvas_test_gradient.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/canvas_test_toImage.png b/testing/resources/canvas_test_toImage.png
deleted file mode 100644
index 75d02c2..0000000
--- a/testing/resources/canvas_test_toImage.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/dotted_path_effect_mixed_with_stroked_geometry.png b/testing/resources/dotted_path_effect_mixed_with_stroked_geometry.png
deleted file mode 100644
index a8aef6b..0000000
--- a/testing/resources/dotted_path_effect_mixed_with_stroked_geometry.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/text_with_gradient_with_matrix.png b/testing/resources/text_with_gradient_with_matrix.png
deleted file mode 100644
index b940c1e..0000000
--- a/testing/resources/text_with_gradient_with_matrix.png
+++ /dev/null
Binary files differ
diff --git a/testing/run_tests.py b/testing/run_tests.py
index 419460f..8074d81 100755
--- a/testing/run_tests.py
+++ b/testing/run_tests.py
@@ -606,6 +606,11 @@
       return 'multithreaded'
     return 'single-threaded'
 
+  def impeller_enabled(self):
+    if self.enable_impeller:
+      return 'impeller swiftshader'
+    return 'skia software'
+
 
 def gather_dart_test(build_dir, dart_file, options):
   kernel_file_name = os.path.basename(dart_file) + '.dill'
@@ -637,8 +642,8 @@
 
   tester_name = 'flutter_tester'
   logger.info(
-      "Running test '%s' using '%s' (%s)", kernel_file_name, tester_name,
-      options.threading_description()
+      "Running test '%s' using '%s' (%s, %s)", kernel_file_name, tester_name,
+      options.threading_description(), options.impeller_enabled()
   )
   forbidden_output = [] if 'unopt' in build_dir or options.expect_failure else [
       '[ERROR'
diff --git a/testing/scenario_app/pubspec.yaml b/testing/scenario_app/pubspec.yaml
index 4c579aa..fb13f58 100644
--- a/testing/scenario_app/pubspec.yaml
+++ b/testing/scenario_app/pubspec.yaml
@@ -29,6 +29,8 @@
     path: ../../../third_party/dart/third_party/pkg/collection
   crypto:
     path: ../../../third_party/dart/third_party/pkg/crypto
+  engine_repo_tools:
+    path: ../../tools/pkg/engine_repo_tools
   file:
     path: ../../../third_party/dart/third_party/pkg/file/packages/file
   meta:
diff --git a/testing/skia_gold_client/README.md b/testing/skia_gold_client/README.md
index c9d6a8c..3e7f6ec 100644
--- a/testing/skia_gold_client/README.md
+++ b/testing/skia_gold_client/README.md
@@ -15,15 +15,29 @@
   dependencies: [{"dependency": "goldctl"}]
 ```
 
-2. Add dependency in `pubspec.yaml`:
+2. In the builder `.json` file, ensure the drone has a dependency on `goldctl`:
+
+```yaml
+            "dependencies": [
+                {
+                    "dependency": "goldctl",
+                    "version": "git_revision:<sha>"
+                }
+            ],
+```
+
+3. Add dependency in `pubspec.yaml`:
 
 ```yaml
 dependencies:
+  # needed for skia_gold_client to avoid a cache miss.
+  engine_repo_tools:
+    path: <relative-path>/tools/pkg/engine_repo_tools
   skia_gold_client:
     path: <relative-path>/testing/skia_gold_client
 ```
 
-3. Use the client:
+4. Use the client:
 
 ```dart
 import 'package:skia_gold_client/skia_gold_client.dart';
diff --git a/testing/skia_gold_client/lib/skia_gold_client.dart b/testing/skia_gold_client/lib/skia_gold_client.dart
index b258de0..471aec0 100644
--- a/testing/skia_gold_client/lib/skia_gold_client.dart
+++ b/testing/skia_gold_client/lib/skia_gold_client.dart
@@ -6,6 +6,7 @@
 import 'dart:io';
 
 import 'package:crypto/crypto.dart';
+import 'package:engine_repo_tools/engine_repo_tools.dart';
 import 'package:path/path.dart' as path;
 import 'package:process/process.dart';
 
@@ -36,7 +37,13 @@
   ///
   /// [dimensions] allows to add attributes about the environment
   /// used to generate the screenshots.
-  SkiaGoldClient(this.workDirectory, { this.dimensions });
+  SkiaGoldClient(this.workDirectory, { this.dimensions, this.verbose = false});
+
+  /// Whether to print verbose output from goldctl.
+  ///
+  /// This flag is intended for use in debugging CI issues, and should not
+  /// ordinarily be set to true.
+  final bool verbose;
 
   /// Allows to add attributes about the environment used to generate the screenshots.
   final Map<String, String>? dimensions;
@@ -95,6 +102,7 @@
     final List<String> authCommand = <String>[
       _goldctl,
       'auth',
+      if (verbose) '--verbose',
       '--work-dir', _tempPath,
       '--luci',
     ];
@@ -111,6 +119,9 @@
         ..writeln('stdout: ${result.stdout}')
         ..writeln('stderr: ${result.stderr}');
       throw Exception(buf.toString());
+    } else if (verbose) {
+      print('stdout:\n${result.stdout}');
+      print('stderr:\n${result.stderr}');
     }
   }
 
@@ -134,6 +145,7 @@
     final List<String> imgtestInitCommand = <String>[
       _goldctl,
       'imgtest', 'init',
+      if (verbose) '--verbose',
       '--instance', _instance,
       '--work-dir', _tempPath,
       '--commit', commitHash,
@@ -163,7 +175,11 @@
         ..writeln('stdout: ${result.stdout}')
         ..writeln('stderr: ${result.stderr}');
       throw Exception(buf.toString());
+    } else if (verbose) {
+      print('stdout:\n${result.stdout}');
+      print('stderr:\n${result.stderr}');
     }
+
   }
 
   /// Executes the `imgtest add` command in the `goldctl` tool.
@@ -229,6 +245,7 @@
     final List<String> imgtestCommand = <String>[
       _goldctl,
       'imgtest', 'add',
+      if (verbose) '--verbose',
       '--work-dir', _tempPath,
       '--test-name', cleanTestName(testName),
       '--png-file', goldenFile.path,
@@ -243,6 +260,9 @@
       // is meant to inform when an unexpected result occurs.
       print('goldctl imgtest add stdout: ${result.stdout}');
       print('goldctl imgtest add stderr: ${result.stderr}');
+    } else if (verbose) {
+      print('stdout:\n${result.stdout}');
+      print('stderr:\n${result.stderr}');
     }
   }
 
@@ -261,6 +281,7 @@
     final List<String> tryjobInitCommand = <String>[
       _goldctl,
       'imgtest', 'init',
+      if (verbose) '--verbose',
       '--instance', _instance,
       '--work-dir', _tempPath,
       '--commit', commitHash,
@@ -293,6 +314,9 @@
         ..writeln('stdout: ${result.stdout}')
         ..writeln('stderr: ${result.stderr}');
       throw Exception(buf.toString());
+    } else if (verbose) {
+      print('stdout:\n${result.stdout}');
+      print('stderr:\n${result.stderr}');
     }
   }
 
@@ -317,6 +341,7 @@
     final List<String> tryjobCommand = <String>[
       _goldctl,
       'imgtest', 'add',
+      if (verbose) '--verbose',
       '--work-dir', _tempPath,
       '--test-name', cleanTestName(testName),
       '--png-file', goldenFile.path,
@@ -338,6 +363,9 @@
         ..writeln('stderr: ${result.stderr}')
         ..writeln();
       throw Exception(buf.toString());
+    } else if (verbose) {
+      print('stdout:\n${result.stdout}');
+      print('stderr:\n${result.stderr}');
     }
   }
 
@@ -421,13 +449,13 @@
 
   /// Returns the current commit hash of the engine repository.
   Future<String> _getCurrentCommit() async {
-    final File currentScript = File.fromUri(Platform.script);
+    final String engineCheckout = Engine.findWithin().flutterDir.path;
     final ProcessResult revParse = await process.run(
       <String>['git', 'rev-parse', 'HEAD'],
-      workingDirectory: currentScript.parent.absolute.path,
+      workingDirectory: engineCheckout,
     );
     if (revParse.exitCode != 0) {
-      throw Exception('Current commit of the engine can not be found from path ${currentScript.path}.');
+      throw Exception('Current commit of the engine can not be found from path $engineCheckout.');
     }
     return (revParse.stdout as String).trim();
   }
diff --git a/testing/skia_gold_client/pubspec.yaml b/testing/skia_gold_client/pubspec.yaml
index 8c7bc0a..693424d 100644
--- a/testing/skia_gold_client/pubspec.yaml
+++ b/testing/skia_gold_client/pubspec.yaml
@@ -17,6 +17,7 @@
 dependencies:
   crypto: any
   path: any
+  engine_repo_tools: any
   process: any
 
 dependency_overrides:
@@ -24,6 +25,8 @@
     path: ../../../third_party/dart/third_party/pkg/collection
   crypto:
     path: ../../../third_party/dart/third_party/pkg/crypto
+  engine_repo_tools:
+    path: ../../tools/pkg/engine_repo_tools
   file:
     path: ../../../third_party/dart/third_party/pkg/file/packages/file
   meta:
diff --git a/web_sdk/web_test_utils/pubspec.yaml b/web_sdk/web_test_utils/pubspec.yaml
index ca7efd3..fbdd1be 100644
--- a/web_sdk/web_test_utils/pubspec.yaml
+++ b/web_sdk/web_test_utils/pubspec.yaml
@@ -16,3 +16,7 @@
     path: ../../testing/skia_gold_client
   typed_data: 1.3.0
   yaml: 3.0.0
+
+dependency_overrides:
+  engine_repo_tools:
+    path: ../../tools/pkg/engine_repo_tools