@@ -12,6 +12,7 @@ import 'package:path/path.dart' as path;
1212import 'package:process/process.dart' ;
1313
1414import 'src/errors.dart' ;
15+ import 'src/release_version.dart' ;
1516
1617export 'src/errors.dart' show SkiaGoldProcessError;
1718
@@ -22,25 +23,89 @@ const String _kLuciEnvName = 'LUCI_CONTEXT';
2223const String _skiaGoldHost = 'https://flutter-engine-gold.skia.org' ;
2324const String _instance = 'flutter-engine' ;
2425
25- /// A client for uploading image tests and making baseline requests to the
26- /// Flutter Gold Dashboard.
26+ /// Uploads images and makes baseline requests to Skia Gold.
27+ ///
28+ /// For an example of how to use this class, see `tool/e2e_test.dart` .
2729interface class SkiaGoldClient {
2830 /// Creates a [SkiaGoldClient] with the given [workDirectory] .
2931 ///
30- /// [dimensions] allows to add attributes about the environment
31- /// used to generate the screenshots.
32- SkiaGoldClient (
32+ /// A set of [dimensions] can be provided to add attributes about the
33+ /// environment used to generate the screenshots, which are treated as keys
34+ /// for the image:
35+ ///
36+ /// ```dart
37+ /// final SkiaGoldClient skiaGoldClient = SkiaGoldClient(
38+ /// someDir,
39+ /// dimensions: <String, String>{
40+ /// 'platform': 'linux',
41+ /// },
42+ /// );
43+ /// ```
44+ ///
45+ /// The [verbose] flag is intended for use in debugging CI issues, and
46+ /// produces more detailed output that some may find useful, but would be too
47+ /// spammy for regular use.
48+ factory SkiaGoldClient (
49+ io.Directory workDirectory, {
50+ Map <String , String >? dimensions,
51+ bool verbose = false ,
52+ }) {
53+ return SkiaGoldClient .forTesting (
54+ workDirectory,
55+ dimensions: dimensions,
56+ verbose: verbose,
57+ );
58+ }
59+
60+ /// Creates a [SkiaGoldClient] for testing.
61+ ///
62+ /// Similar to the default constructor, but allows for dependency injection
63+ /// for testing purposes:
64+ ///
65+ /// - [httpClient] makes requests to Skia Gold to fetch expectations.
66+ /// - [processManager] launches sub-processes.
67+ /// - [stderr] is where output is written for diagnostics.
68+ /// - [environment] is the environment variables for the currently running
69+ /// process, and is used to determine if Skia Gold is available, and whether
70+ /// the current environment is CI, and if so, if it's pre-submit or
71+ /// post-submit.
72+ /// - [engineRoot] is the root of the engine repository, which is used for
73+ /// finding the current commit hash, as well as the location of the
74+ /// `.engine-release.version` file.
75+ @visibleForTesting
76+ SkiaGoldClient .forTesting (
3377 this .workDirectory, {
3478 this .dimensions,
3579 this .verbose = false ,
3680 io.HttpClient ? httpClient,
3781 ProcessManager ? processManager,
3882 StringSink ? stderr,
3983 Map <String , String >? environment,
84+ Engine ? engineRoot,
4085 }) : httpClient = httpClient ?? io.HttpClient (),
4186 process = processManager ?? const LocalProcessManager (),
4287 _stderr = stderr ?? io.stderr,
43- _environment = environment ?? io.Platform .environment;
88+ _environment = environment ?? io.Platform .environment,
89+ _engineRoot = engineRoot ?? Engine .findWithin () {
90+ // Lookup the release version from the engine repository.
91+ final io.File releaseVersionFile = io.File (path.join (
92+ _engineRoot.flutterDir.path,
93+ '.engine-release.version' ,
94+ ));
95+
96+ // If the file is not found or cannot be read, we are in an invalid state.
97+ try {
98+ _releaseVersion = ReleaseVersion .parse (releaseVersionFile.readAsStringSync ());
99+ } on FormatException catch (error) {
100+ throw StateError ('Failed to parse release version file: $error .' );
101+ } on io.FileSystemException catch (error) {
102+ throw StateError ('Failed to read release version file: $error .' );
103+ }
104+ }
105+
106+ /// The root of the engine repository.
107+ final Engine _engineRoot;
108+ ReleaseVersion ? _releaseVersion;
44109
45110 /// Whether the client is available and can be used in this environment.
46111 static bool isAvailable ({
@@ -244,6 +309,14 @@ interface class SkiaGoldClient {
244309 /// determined by the [pixelColorDelta] parameter. It's in the range [0.0,
245310 /// 1.0] and defaults to 0.01. A value of 0.01 means that 1% of the pixels are
246311 /// allowed to be different.
312+ ///
313+ /// ## Release Testing
314+ ///
315+ /// In release branches, we add a unique test suffix to the test name. For
316+ /// example "testName" -> "testName_Release_3_21", based on the version in the
317+ /// `.engine-release.version` file at the root of the engine repository.
318+ ///
319+ /// See <../README.md#release-testing> for more information.
247320 Future <void > addImg (
248321 String testName,
249322 io.File goldenFile, {
@@ -252,6 +325,17 @@ interface class SkiaGoldClient {
252325 required int screenshotSize,
253326 }) async {
254327 assert (_isPresubmit || _isPostsubmit);
328+
329+ // Clean the test name to remove the file extension.
330+ testName = path.basenameWithoutExtension (testName);
331+
332+ // In release branches, we add a unique test suffix to the test name.
333+ // For example "testName" -> "testName_Release_3_21".
334+ // See ../README.md#release-testing for more information.
335+ if (_releaseVersion case final ReleaseVersion v) {
336+ testName = '${testName }_Release_${v .major }_${v .minor }' ;
337+ }
338+
255339 if (_isPresubmit) {
256340 await _tryjobAdd (testName, goldenFile, screenshotSize, pixelColorDelta, differentPixelsRate);
257341 }
@@ -287,7 +371,7 @@ interface class SkiaGoldClient {
287371 '--work-dir' ,
288372 _tempPath,
289373 '--test-name' ,
290- _cleanTestName ( testName) ,
374+ testName,
291375 '--png-file' ,
292376 goldenFile.path,
293377 // Otherwise post submit will not fail.
@@ -391,7 +475,7 @@ interface class SkiaGoldClient {
391475 '--work-dir' ,
392476 _tempPath,
393477 '--test-name' ,
394- _cleanTestName ( testName) ,
478+ testName,
395479 '--png-file' ,
396480 goldenFile.path,
397481 ..._getMatchingArguments (testName, screenshotSize, pixelDeltaThreshold, differentPixelsRate),
@@ -489,7 +573,7 @@ interface class SkiaGoldClient {
489573
490574 /// Returns the current commit hash of the engine repository.
491575 Future <String > _getCurrentCommit () async {
492- final String engineCheckout = Engine . findWithin () .flutterDir.path;
576+ final String engineCheckout = _engineRoot .flutterDir.path;
493577 final io.ProcessResult revParse = await process.run (
494578 < String > ['git' , 'rev-parse' , 'HEAD' ],
495579 workingDirectory: engineCheckout,
@@ -521,12 +605,6 @@ interface class SkiaGoldClient {
521605 return json.encode (_getKeys ());
522606 }
523607
524- /// Removes the file extension from the [fileName] to represent the test name
525- /// properly.
526- static String _cleanTestName (String fileName) {
527- return path.basenameWithoutExtension (fileName);
528- }
529-
530608 /// Returns a list of arguments for initializing a tryjob based on the testing
531609 /// environment.
532610 List <String > _getCIArguments () {
0 commit comments