@@ -9,35 +9,37 @@ import 'package:crypto/crypto.dart';
99import 'package:path/path.dart' as path;
1010import 'package:process/process.dart' ;
1111
12- import 'environment.dart' ;
13-
1412const String _kGoldctlKey = 'GOLDCTL' ;
13+ const String _kPresubmitEnvName = 'GOLD_TRYJOB' ;
14+ const String _kLuciEnvName = 'LUCI_CONTEXT' ;
1515
1616const String _skiaGoldHost = 'https://flutter-engine-gold.skia.org' ;
1717const String _instance = 'flutter-engine' ;
1818
19- /// The percentage of accepted pixels to be wrong.
20- ///
21- /// This should be a double between 0.0 and 1.0. A value of 0.0 means we don't
22- /// accept any pixel to be different. A value of 1.0 means we accept 100% of
23- /// pixels to be different.
24- const double kMaxDifferentPixelsRate = 0.1 ;
19+ /// Whether the Skia Gold client is available and can be used in this
20+ /// environment.
21+ bool get isSkiaGoldClientAvailable => Platform .environment.containsKey (_kGoldctlKey);
22+
23+ /// Returns true if the current environment is a LUCI builder.
24+ bool get isLuciEnv => Platform .environment.containsKey (_kLuciEnvName);
25+
26+ /// Whether the current task is run during a presubmit check.
27+ bool get _isPresubmit => isLuciEnv && isSkiaGoldClientAvailable && Platform .environment.containsKey (_kPresubmitEnvName);
28+
29+ /// Whether the current task is run during a postsubmit check.
30+ bool get _isPostsubmit => isLuciEnv && isSkiaGoldClientAvailable && ! Platform .environment.containsKey (_kPresubmitEnvName);
2531
2632/// A client for uploading image tests and making baseline requests to the
2733/// Flutter Gold Dashboard.
2834class SkiaGoldClient {
2935 /// Creates a [SkiaGoldClient] with the given [workDirectory] .
3036 ///
31- /// The [browserName] parameter is the name of the browser that generated the
32- /// screenshots.
33- SkiaGoldClient (this .workDirectory, { required this .browserName });
37+ /// [dimensions] allows to add attributes about the environment
38+ /// used to generate the screenshots.
39+ SkiaGoldClient (this .workDirectory, { this .dimensions });
3440
35- /// Whether the Skia Gold client is available and can be used in this
36- /// environment.
37- static bool get isAvailable => Platform .environment.containsKey (_kGoldctlKey);
38-
39- /// The name of the browser running the tests.
40- final String browserName;
41+ /// Allows to add attributes about the environment used to generate the screenshots.
42+ final Map <String , String >? dimensions;
4143
4244 /// A controller for launching sub-processes.
4345 final ProcessManager process = const LocalProcessManager ();
@@ -73,7 +75,7 @@ class SkiaGoldClient {
7375 /// The path to the local [Directory] where the `goldctl` tool is hosted.
7476 String get _goldctl {
7577 assert (
76- isAvailable ,
78+ isSkiaGoldClientAvailable ,
7779 'Trying to use `goldctl` in an environment where it is not available' ,
7880 );
7981 return Platform .environment[_kGoldctlKey]! ;
@@ -165,6 +167,42 @@ class SkiaGoldClient {
165167 _isInitialized = true ;
166168 }
167169
170+ /// Executes the `imgtest add` command in the `goldctl` tool.
171+ ///
172+ /// The `imgtest` command collects and uploads test results to the Skia Gold
173+ /// backend, the `add` argument uploads the current image test.
174+ ///
175+ /// Throws an exception for try jobs that failed to pass the pixel comparison.
176+ ///
177+ /// The [testName] and [goldenFile] parameters reference the current
178+ /// comparison being evaluated.
179+ ///
180+ /// [pixelColorDelta] defines maximum acceptable difference in RGB channels of each pixel,
181+ /// such that:
182+ ///
183+ /// ```
184+ /// abs(r(image) - r(golden)) + abs(g(image) - g(golden)) + abs(b(image) - b(golden)) <= pixelDeltaThreshold
185+ /// ```
186+ ///
187+ /// [differentPixelsRate] is the fraction of accepted pixels to be wrong in the range [0.0, 1.0] .
188+ /// Defaults to 0.1. A value of 0.1 means that 10% of the pixels are allowed to change.
189+ Future <void > addImg (
190+ String testName,
191+ File goldenFile, {
192+ double differentPixelsRate = 0.1 ,
193+ int pixelColorDelta = 0 ,
194+ required int screenshotSize,
195+ }) async {
196+ assert (_isPresubmit || _isPostsubmit);
197+
198+ if (_isPresubmit) {
199+ await _tryjobAdd (testName, goldenFile, screenshotSize, pixelColorDelta, differentPixelsRate);
200+ }
201+ if (_isPostsubmit) {
202+ await _imgtestAdd (testName, goldenFile, screenshotSize, pixelColorDelta, differentPixelsRate);
203+ }
204+ }
205+
168206 /// Executes the `imgtest add` command in the `goldctl` tool.
169207 ///
170208 /// The `imgtest` command collects and uploads test results to the Skia Gold
@@ -174,11 +212,12 @@ class SkiaGoldClient {
174212 ///
175213 /// The [testName] and [goldenFile] parameters reference the current
176214 /// comparison being evaluated.
177- Future <bool > imgtestAdd (
215+ Future <void > _imgtestAdd (
178216 String testName,
179217 File goldenFile,
180218 int screenshotSize,
181- bool isCanvaskitTest,
219+ int pixelDeltaThreshold,
220+ double maxDifferentPixelsRate,
182221 ) async {
183222 await _imgtestInit ();
184223
@@ -188,7 +227,7 @@ class SkiaGoldClient {
188227 '--work-dir' , _tempPath,
189228 '--test-name' , cleanTestName (testName),
190229 '--png-file' , goldenFile.path,
191- ..._getMatchingArguments (testName, screenshotSize, isCanvaskitTest ),
230+ ..._getMatchingArguments (testName, screenshotSize, pixelDeltaThreshold, maxDifferentPixelsRate ),
192231 ];
193232
194233 final ProcessResult result = await _runCommand (imgtestCommand);
@@ -200,8 +239,6 @@ class SkiaGoldClient {
200239 print ('goldctl imgtest add stdout: ${result .stdout }' );
201240 print ('goldctl imgtest add stderr: ${result .stderr }' );
202241 }
203-
204- return true ;
205242 }
206243
207244 /// Executes the `imgtest init` command in the `goldctl` tool for tryjobs.
@@ -268,11 +305,12 @@ class SkiaGoldClient {
268305 ///
269306 /// The [testName] and [goldenFile] parameters reference the current
270307 /// comparison being evaluated.
271- Future <void > tryjobAdd (
308+ Future <void > _tryjobAdd (
272309 String testName,
273310 File goldenFile,
274311 int screenshotSize,
275- bool isCanvaskitTest,
312+ int pixelDeltaThreshold,
313+ double differentPixelsRate,
276314 ) async {
277315 await _tryjobInit ();
278316
@@ -282,7 +320,7 @@ class SkiaGoldClient {
282320 '--work-dir' , _tempPath,
283321 '--test-name' , cleanTestName (testName),
284322 '--png-file' , goldenFile.path,
285- ..._getMatchingArguments (testName, screenshotSize, isCanvaskitTest ),
323+ ..._getMatchingArguments (testName, screenshotSize, pixelDeltaThreshold, differentPixelsRate ),
286324 ];
287325
288326 final ProcessResult result = await _runCommand (tryjobCommand);
@@ -306,7 +344,8 @@ class SkiaGoldClient {
306344 List <String > _getMatchingArguments (
307345 String testName,
308346 int screenshotSize,
309- bool isCanvaskitTest,
347+ int pixelDeltaThreshold,
348+ double differentPixelsRate,
310349 ) {
311350 // The algorithm to be used when matching images. The available options are:
312351 // - "fuzzy": Allows for customizing the thresholds of pixel differences.
@@ -318,22 +357,7 @@ class SkiaGoldClient {
318357 // baseline. It's okay for this to be a slightly high number like 10% of the
319358 // image size because those wrong pixels are constrained by
320359 // `pixelDeltaThreshold` below.
321- final int maxDifferentPixels = (screenshotSize * kMaxDifferentPixelsRate).toInt ();
322-
323- // The maximum acceptable difference in RGB channels of each pixel.
324- //
325- // ```
326- // abs(r(image) - r(golden)) + abs(g(image) - g(golden)) + abs(b(image) - b(golden)) <= pixelDeltaThreshold
327- // ```
328- final String pixelDeltaThreshold;
329- if (isCanvaskitTest) {
330- pixelDeltaThreshold = '21' ;
331- } else if (browserName == 'ios-safari' ) {
332- pixelDeltaThreshold = '15' ;
333- } else {
334- pixelDeltaThreshold = '3' ;
335- }
336-
360+ final int maxDifferentPixels = (screenshotSize * differentPixelsRate).toInt ();
337361 return < String > [
338362 '--add-test-optional-key' , 'image_matching_algorithm:$algorithm ' ,
339363 '--add-test-optional-key' , 'fuzzy_max_different_pixels:$maxDifferentPixels ' ,
@@ -396,19 +420,15 @@ class SkiaGoldClient {
396420
397421 /// Returns the current commit hash of the engine repository.
398422 Future <String > _getCurrentCommit () async {
399- final Directory webUiRoot = environment.webUiRootDir;
400- if (! webUiRoot.existsSync ()) {
401- throw Exception ('Web Engine root could not be found: $webUiRoot \n ' );
402- } else {
403- final ProcessResult revParse = await process.run (
404- < String > ['git' , 'rev-parse' , 'HEAD' ],
405- workingDirectory: webUiRoot.path,
406- );
407- if (revParse.exitCode != 0 ) {
408- throw Exception ('Current commit of Web Engine can not be found.' );
409- }
410- return (revParse.stdout as String ).trim ();
423+ final File currentScript = File .fromUri (Platform .script);
424+ final ProcessResult revParse = await process.run (
425+ < String > ['git' , 'rev-parse' , 'HEAD' ],
426+ workingDirectory: currentScript.path,
427+ );
428+ if (revParse.exitCode != 0 ) {
429+ throw Exception ('Current commit of the engine can not be found from path ${currentScript .path }.' );
411430 }
431+ return (revParse.stdout as String ).trim ();
412432 }
413433
414434 /// Returns a Map of key value pairs used to uniquely identify the
@@ -417,11 +437,14 @@ class SkiaGoldClient {
417437 /// Currently, the only key value pairs being tracked are the platform and
418438 /// browser the image was rendered on.
419439 Map <String , dynamic > _getKeys () {
420- return < String , dynamic > {
421- 'Browser' : browserName,
440+ final Map <String , dynamic > initialKeys = < String , dynamic > {
422441 'CI' : 'luci' ,
423442 'Platform' : Platform .operatingSystem,
424443 };
444+ if (dimensions != null ) {
445+ initialKeys.addAll (dimensions! );
446+ }
447+ return initialKeys;
425448 }
426449
427450 /// Same as [_getKeys] but encodes it in a JSON string.
0 commit comments