Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
a84e369b
Unverified
Commit
a84e369b
authored
Nov 08, 2022
by
Zachary Anderson
Committed by
GitHub
Nov 08, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revert "Allow Flutter golden file tests to be flaky (#114450)" (#114902)
This reverts commit
53e68762
.
parent
a1432a9c
Changes
33
Show whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
1351 additions
and
2063 deletions
+1351
-2063
flutter_gold_test.dart
dev/automated_tests/flutter_test/flutter_gold_test.dart
+1
-1
analyze.dart
dev/bots/analyze.dart
+3
-12
golden_class.dart
...st/analyze-test-input/root/packages/foo/golden_class.dart
+0
-4
golden_doc.dart
...test/analyze-test-input/root/packages/foo/golden_doc.dart
+0
-3
analyze_test.dart
dev/bots/test/analyze_test.dart
+0
-2
technical_debt__cost.dart
dev/devicelab/bin/tasks/technical_debt__cost.dart
+0
-4
flutter_test_config.dart
examples/api/test/flutter_test_config.dart
+1
-1
goldens_io.dart
examples/api/test/goldens_io.dart
+1
-7
goldens_web.dart
examples/api/test/goldens_web.dart
+8
-0
_goldens_io.dart
packages/flutter/test/_goldens_io.dart
+5
-0
_goldens_web.dart
packages/flutter/test/_goldens_web.dart
+8
-0
date_picker_test.dart
packages/flutter/test/cupertino/date_picker_test.dart
+7
-41
flutter_test_config.dart
packages/flutter/test/flutter_test_config.dart
+3
-5
flutter_web_test_config.dart
packages/flutter/test/flutter_web_test_config.dart
+0
-25
flutter_goldens.dart
packages/flutter_goldens/lib/flutter_goldens.dart
+539
-5
flaky_goldens.dart
packages/flutter_goldens/lib/src/flaky_goldens.dart
+0
-98
flutter_goldens_io.dart
packages/flutter_goldens/lib/src/flutter_goldens_io.dart
+0
-611
flutter_goldens_web.dart
packages/flutter_goldens/lib/src/flutter_goldens_web.dart
+0
-71
flaky_goldens_test.dart
packages/flutter_goldens/test/flaky_goldens_test.dart
+0
-52
flutter_goldens_test.dart
packages/flutter_goldens/test/flutter_goldens_test.dart
+733
-64
json_templates.dart
packages/flutter_goldens/test/json_templates.dart
+0
-0
skia_client_test.dart
packages/flutter_goldens/test/skia_client_test.dart
+0
-716
fakes.dart
packages/flutter_goldens/test/utils/fakes.dart
+0
-205
skia_client.dart
packages/flutter_goldens_client/lib/skia_client.dart
+13
-50
_goldens_web.dart
packages/flutter_test/lib/src/_goldens_web.dart
+3
-3
goldens.dart
packages/flutter_test/lib/src/goldens.dart
+0
-8
flutter_web_goldens.dart
packages/flutter_tools/lib/src/test/flutter_web_goldens.dart
+3
-33
flutter_web_platform.dart
...ages/flutter_tools/lib/src/test/flutter_web_platform.dart
+5
-7
test_config.dart
packages/flutter_tools/lib/src/test/test_config.dart
+3
-16
bootstrap.dart
packages/flutter_tools/lib/src/web/bootstrap.dart
+1
-2
golden_comparator_process_test.dart
...est/general.shard/web/golden_comparator_process_test.dart
+6
-9
golden_comparator_test.dart
..._tools/test/general.shard/web/golden_comparator_test.dart
+7
-7
forbidden_imports_test.dart
..._tools/test/integration.shard/forbidden_imports_test.dart
+1
-1
No files found.
dev/automated_tests/flutter_test/flutter_gold_test.dart
View file @
a84e369b
...
...
@@ -8,7 +8,7 @@ import 'dart:typed_data';
import
'package:file/file.dart'
;
import
'package:file/memory.dart'
;
import
'package:flutter_goldens/
src/flutter_goldens_io
.dart'
;
import
'package:flutter_goldens/
flutter_goldens
.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:platform/platform.dart'
;
...
...
dev/bots/analyze.dart
View file @
a84e369b
...
...
@@ -418,8 +418,8 @@ Future<void> verifyNoSyncAsyncStar(String workingDirectory, {int minimumMatches
}
}
final
RegExp
_findGoldenTestPattern
=
RegExp
(
r'
(matchesGoldenFile|expectFlakyGolden)
\('
);
final
RegExp
_findGoldenDefinitionPattern
=
RegExp
(
r'
(matchesGoldenFile|expectFlakyGolden)
\(Object'
);
final
RegExp
_findGoldenTestPattern
=
RegExp
(
r'
matchesGoldenFile
\('
);
final
RegExp
_findGoldenDefinitionPattern
=
RegExp
(
r'
matchesGoldenFile
\(Object'
);
final
RegExp
_leadingComment
=
RegExp
(
r'//'
);
final
RegExp
_goldenTagPattern1
=
RegExp
(
r'@Tags\('
);
final
RegExp
_goldenTagPattern2
=
RegExp
(
r"'reduced-test-set'"
);
...
...
@@ -431,17 +431,8 @@ const String _ignoreGoldenTag = '// flutter_ignore: golden_tag (see analyze.dart
const
String
_ignoreGoldenTagForFile
=
'// flutter_ignore_for_file: golden_tag (see analyze.dart)'
;
Future
<
void
>
verifyGoldenTags
(
String
workingDirectory
,
{
int
minimumMatches
=
2000
})
async
{
// Skip flutter_goldens/lib because this library uses `matchesGoldenFile`
// but is not itself a test that needs tags.
final
String
flutterGoldensPackageLib
=
path
.
join
(
flutterPackages
,
'flutter_goldens'
,
'lib'
);
bool
isWithinFlutterGoldenLib
(
File
file
)
{
return
path
.
isWithin
(
flutterGoldensPackageLib
,
file
.
path
);
}
final
List
<
String
>
errors
=
<
String
>[];
final
Stream
<
File
>
allTestFiles
=
_allFiles
(
workingDirectory
,
'dart'
,
minimumMatches:
minimumMatches
)
.
where
((
File
file
)
=>
!
isWithinFlutterGoldenLib
(
file
));
await
for
(
final
File
file
in
allTestFiles
)
{
await
for
(
final
File
file
in
_allFiles
(
workingDirectory
,
'dart'
,
minimumMatches:
minimumMatches
))
{
bool
needsTag
=
false
;
bool
hasTagNotation
=
false
;
bool
hasReducedTag
=
false
;
...
...
dev/bots/test/analyze-test-input/root/packages/foo/golden_class.dart
View file @
a84e369b
...
...
@@ -7,7 +7,3 @@
void
matchesGoldenFile
(
Object
key
)
{
return
;
}
void
expectFlakyGolden
(
Object
key
,
String
string
){
return
;
}
dev/bots/test/analyze-test-input/root/packages/foo/golden_doc.dart
View file @
a84e369b
...
...
@@ -35,11 +35,8 @@
/// ```
/// {@end-tool}
///
/// expectFlakyGolden(a, b)
// Other comments
// matchesGoldenFile('comment.png');
// expectFlakyGolden(a, b);
String
literal
=
'matchesGoldenFile()'
;
// flutter_ignore: golden_tag (see analyze.dart)
String
flakyLiteral
=
'expectFlakyGolden'
;
dev/bots/test/analyze_test.dart
View file @
a84e369b
...
...
@@ -79,9 +79,7 @@ void main() {
'at the top of the file before import statements.'
;
const
String
missingTag
=
"Files containing golden tests must be tagged with 'reduced-test-set'."
;
final
List
<
String
>
lines
=
<
String
>[
'║ test/analyze-test-input/root/packages/foo/flaky_golden_no_tag.dart:
$noTag
'
,
'║ test/analyze-test-input/root/packages/foo/golden_missing_tag.dart:
$missingTag
'
,
'║ test/analyze-test-input/root/packages/foo/flaky_golden_missing_tag.dart:
$missingTag
'
,
'║ test/analyze-test-input/root/packages/foo/golden_no_tag.dart:
$noTag
'
,
]
.
map
((
String
line
)
=>
line
.
replaceAll
(
'/'
,
Platform
.
isWindows
?
r'\'
:
'/'
))
...
...
dev/devicelab/bin/tasks/technical_debt__cost.dart
View file @
a84e369b
...
...
@@ -17,7 +17,6 @@ const double todoCost = 1009.0; // about two average SWE days, in dollars
const
double
ignoreCost
=
2003.0
;
// four average SWE days, in dollars
const
double
pythonCost
=
3001.0
;
// six average SWE days, in dollars
const
double
skipCost
=
2473.0
;
// 20 hours: 5 to fix the issue we're ignoring, 15 to fix the bugs we missed because the test was off
const
double
flakyGoldenCost
=
2467.0
;
// Similar to skip cost
const
double
ignoreForFileCost
=
2477.0
;
// similar thinking as skipCost
const
double
asDynamicCost
=
2011.0
;
// a few days to refactor the code.
const
double
deprecationCost
=
233.0
;
// a few hours to remove the old code.
...
...
@@ -70,9 +69,6 @@ Future<double> findCostsForFile(File file) async {
if
(
isTest
&&
line
.
contains
(
'skip:'
)
&&
!
line
.
contains
(
'[intended]'
))
{
total
+=
skipCost
;
}
if
(
isTest
&&
line
.
contains
(
'expectFlakyGolden('
))
{
total
+=
flakyGoldenCost
;
}
if
(
isDart
&&
isOptingOutOfNullSafety
(
line
))
{
total
+=
fileNullSafetyMigrationCost
;
}
...
...
examples/api/test/flutter_test_config.dart
View file @
a84e369b
...
...
@@ -5,7 +5,7 @@
import
'dart:async'
;
import
'
package:flutter_goldens/flutter_goldens
.dart'
as
flutter_goldens
;
import
'
goldens_io.dart'
if
(
dart
.
library
.
html
)
'goldens_web
.dart'
as
flutter_goldens
;
Future
<
void
>
testExecutable
(
FutureOr
<
void
>
Function
()
testMain
)
{
// Enable golden file testing using Skia Gold.
...
...
dev/bots/test/analyze-test-input/root/packages/foo/flaky_golden_no_tag
.dart
→
examples/api/test/goldens_io
.dart
View file @
a84e369b
...
...
@@ -2,10 +2,4 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// The tag is missing. This should fail analysis.
import
'golden_class.dart'
;
void
main
(
)
{
expectFlakyGolden
(
'key'
,
'missing_tag.png'
);
}
export
'package:flutter_goldens/flutter_goldens.dart'
show
testExecutable
;
dev/bots/test/analyze-test-input/root/packages/foo/flaky_golden_missing_tag
.dart
→
examples/api/test/goldens_web
.dart
View file @
a84e369b
...
...
@@ -2,13 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// The reduced test set tag is missing. This should fail analysis.
@Tags
(<
String
>[
'some-other-tag'
])
import
'dart:async'
;
import
'package:test/test.dart'
;
import
'golden_class.dart'
;
void
main
(
)
{
expectFlakyGolden
(
'finder'
,
'missing_tag.png'
);
}
// package:flutter_goldens is not used as part of the test process for web.
Future
<
void
>
testExecutable
(
FutureOr
<
void
>
Function
()
testMain
)
async
=>
testMain
();
packages/flutter/test/_goldens_io.dart
0 → 100644
View file @
a84e369b
// Copyright 2014 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.
export
'package:flutter_goldens/flutter_goldens.dart'
show
testExecutable
;
dev/bots/test/analyze-test-input/root/packages/foo/flaky_golden_ignore
.dart
→
packages/flutter/test/_goldens_web
.dart
View file @
a84e369b
...
...
@@ -2,15 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This would fail analysis, but it is ignored
// flutter_ignore_for_file: golden_tag (see analyze.dart)
import
'dart:async'
;
@Tags
(<
String
>[
'some-other-tag'
])
import
'package:test/test.dart'
;
import
'golden_class.dart'
;
void
main
(
)
{
expectFlakyGolden
(
'key'
,
'String'
);
}
// package:flutter_goldens is not used as part of the test process for web.
Future
<
void
>
testExecutable
(
FutureOr
<
void
>
Function
()
testMain
)
async
=>
testMain
();
packages/flutter/test/cupertino/date_picker_test.dart
View file @
a84e369b
...
...
@@ -17,11 +17,10 @@ import 'dart:ui';
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_goldens/flutter_goldens.dart'
show
expectFlakyGolden
;
import
'package:flutter_test/flutter_test.dart'
;
// TODO(yjbanov): on the web text rendered with perspective produces flaky goldens: https://github.com/flutter/flutter/issues/110785
const
bool
perspectiveTestIsFlaky
=
isBrowser
;
const
bool
skipPerspectiveTextGoldens
=
isBrowser
;
// A number of the hit tests below say "warnIfMissed: false". This is because
// the way the CupertinoPicker works, the hits don't actually reach the labels,
...
...
@@ -1198,13 +1197,7 @@ void main() {
}
await
tester
.
pumpWidget
(
buildApp
(
CupertinoDatePickerMode
.
time
));
if
(
perspectiveTestIsFlaky
)
{
await
expectFlakyGolden
(
find
.
byType
(
CupertinoDatePicker
),
'date_picker_test.time.initial.png'
,
);
}
else
{
if
(!
skipPerspectiveTextGoldens
)
{
await
expectLater
(
find
.
byType
(
CupertinoDatePicker
),
matchesGoldenFile
(
'date_picker_test.time.initial.png'
),
...
...
@@ -1212,13 +1205,7 @@ void main() {
}
await
tester
.
pumpWidget
(
buildApp
(
CupertinoDatePickerMode
.
date
));
if
(
perspectiveTestIsFlaky
)
{
await
expectFlakyGolden
(
find
.
byType
(
CupertinoDatePicker
),
'date_picker_test.date.initial.png'
,
);
}
else
{
if
(!
skipPerspectiveTextGoldens
)
{
await
expectLater
(
find
.
byType
(
CupertinoDatePicker
),
matchesGoldenFile
(
'date_picker_test.date.initial.png'
),
...
...
@@ -1226,13 +1213,7 @@ void main() {
}
await
tester
.
pumpWidget
(
buildApp
(
CupertinoDatePickerMode
.
dateAndTime
));
if
(
perspectiveTestIsFlaky
)
{
await
expectFlakyGolden
(
find
.
byType
(
CupertinoDatePicker
),
'date_picker_test.datetime.initial.png'
,
);
}
else
{
if
(!
skipPerspectiveTextGoldens
)
{
await
expectLater
(
find
.
byType
(
CupertinoDatePicker
),
matchesGoldenFile
(
'date_picker_test.datetime.initial.png'
),
...
...
@@ -1243,12 +1224,7 @@ void main() {
await
tester
.
drag
(
find
.
text
(
'4'
),
Offset
(
0
,
_kRowOffset
.
dy
/
2
),
warnIfMissed:
false
);
// see top of file
await
tester
.
pump
();
if
(
perspectiveTestIsFlaky
)
{
await
expectFlakyGolden
(
find
.
byType
(
CupertinoDatePicker
),
'date_picker_test.datetime.drag.png'
,
);
}
else
{
if
(!
skipPerspectiveTextGoldens
)
{
await
expectLater
(
find
.
byType
(
CupertinoDatePicker
),
matchesGoldenFile
(
'date_picker_test.datetime.drag.png'
),
...
...
@@ -1338,12 +1314,7 @@ void main() {
),
);
if
(
perspectiveTestIsFlaky
)
{
await
expectFlakyGolden
(
find
.
byType
(
CupertinoTimerPicker
),
'timer_picker_test.datetime.initial.png'
,
);
}
else
{
if
(!
skipPerspectiveTextGoldens
)
{
await
expectLater
(
find
.
byType
(
CupertinoTimerPicker
),
matchesGoldenFile
(
'timer_picker_test.datetime.initial.png'
),
...
...
@@ -1354,12 +1325,7 @@ void main() {
await
tester
.
drag
(
find
.
text
(
'59'
),
Offset
(
0
,
_kRowOffset
.
dy
/
2
),
warnIfMissed:
false
);
// see top of file
await
tester
.
pump
();
if
(
perspectiveTestIsFlaky
)
{
await
expectFlakyGolden
(
find
.
byType
(
CupertinoTimerPicker
),
'timer_picker_test.datetime.drag.png'
,
);
}
else
{
if
(!
skipPerspectiveTextGoldens
)
{
await
expectLater
(
find
.
byType
(
CupertinoTimerPicker
),
matchesGoldenFile
(
'timer_picker_test.datetime.drag.png'
),
...
...
packages/flutter/test/flutter_test_config.dart
View file @
a84e369b
...
...
@@ -5,9 +5,11 @@
import
'dart:async'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_goldens/flutter_goldens.dart'
as
flutter_goldens
;
import
'package:flutter_test/flutter_test.dart'
;
import
'_goldens_io.dart'
if
(
dart
.
library
.
html
)
'_goldens_web.dart'
as
flutter_goldens
;
Future
<
void
>
testExecutable
(
FutureOr
<
void
>
Function
()
testMain
)
{
// Enable checks because there are many implementations of [RenderBox] in this
// package can benefit from the additional validations.
...
...
@@ -20,7 +22,3 @@ Future<void> testExecutable(FutureOr<void> Function() testMain) {
// Enable golden file testing using Skia Gold.
return
flutter_goldens
.
testExecutable
(
testMain
);
}
Future
<
void
>
processBrowserCommand
(
dynamic
command
)
{
return
flutter_goldens
.
processBrowserCommand
(
command
);
}
packages/flutter/test/flutter_web_test_config.dart
deleted
100644 → 0
View file @
a1432a9c
// Copyright 2014 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:async'
;
import
'dart:convert'
;
import
'dart:io'
;
import
'flutter_test_config.dart'
;
/// A custom host configuration for browser tests that supports flaky golden
/// checks.
///
/// See also [processBrowserCommand].
Future
<
void
>
startWebTestHostConfiguration
(
String
testUri
)
async
{
testExecutable
(()
async
{
final
Stream
<
dynamic
>
commands
=
stdin
.
transform
<
String
>(
utf8
.
decoder
)
.
transform
<
String
>(
const
LineSplitter
())
.
map
<
dynamic
>(
jsonDecode
);
await
for
(
final
dynamic
command
in
commands
)
{
await
processBrowserCommand
(
command
);
}
});
}
packages/flutter_goldens/lib/flutter_goldens.dart
View file @
a84e369b
...
...
@@ -2,10 +2,544 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// This library exposes functions that enhance the test with custom golden
/// configuration for the Flutter repository.
import
'dart:async'
show
FutureOr
;
import
'dart:io'
as
io
show
OSError
,
SocketException
;
import
'package:file/file.dart'
;
import
'package:file/local.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter_goldens_client/skia_client.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:platform/platform.dart'
;
export
'package:flutter_goldens_client/skia_client.dart'
;
export
'src/flaky_goldens.dart'
show
expectFlakyGolden
;
export
'src/flutter_goldens_io.dart'
if
(
dart
.
library
.
js_util
)
'src/flutter_goldens_web.dart'
show
processBrowserCommand
,
testExecutable
;
// If you are here trying to figure out how to use golden files in the Flutter
// repo itself, consider reading this wiki page:
// https://github.com/flutter/flutter/wiki/Writing-a-golden-file-test-for-package%3Aflutter
const
String
_kFlutterRootKey
=
'FLUTTER_ROOT'
;
/// Main method that can be used in a `flutter_test_config.dart` file to set
/// [goldenFileComparator] to an instance of [FlutterGoldenFileComparator] that
/// works for the current test. _Which_ FlutterGoldenFileComparator is
/// instantiated is based on the current testing environment.
///
/// When set, the `namePrefix` is prepended to the names of all gold images.
Future
<
void
>
testExecutable
(
FutureOr
<
void
>
Function
()
testMain
,
{
String
?
namePrefix
})
async
{
const
Platform
platform
=
LocalPlatform
();
if
(
FlutterPostSubmitFileComparator
.
isAvailableForEnvironment
(
platform
))
{
goldenFileComparator
=
await
FlutterPostSubmitFileComparator
.
fromDefaultComparator
(
platform
,
namePrefix:
namePrefix
);
}
else
if
(
FlutterPreSubmitFileComparator
.
isAvailableForEnvironment
(
platform
))
{
goldenFileComparator
=
await
FlutterPreSubmitFileComparator
.
fromDefaultComparator
(
platform
,
namePrefix:
namePrefix
);
}
else
if
(
FlutterSkippingFileComparator
.
isAvailableForEnvironment
(
platform
))
{
goldenFileComparator
=
FlutterSkippingFileComparator
.
fromDefaultComparator
(
'Golden file testing is not executed on Cirrus, or LUCI environments outside of flutter/flutter.'
,
namePrefix:
namePrefix
);
}
else
{
goldenFileComparator
=
await
FlutterLocalFileComparator
.
fromDefaultComparator
(
platform
);
}
await
testMain
();
}
/// Abstract base class golden file comparator specific to the `flutter/flutter`
/// repository.
///
/// Golden file testing for the `flutter/flutter` repository is handled by three
/// different [FlutterGoldenFileComparator]s, depending on the current testing
/// environment.
///
/// * The [FlutterPostSubmitFileComparator] is utilized during post-submit
/// testing, after a pull request has landed on the master branch. This
/// comparator uses the [SkiaGoldClient] and the `goldctl` tool to upload
/// tests to the [Flutter Gold dashboard](https://flutter-gold.skia.org).
/// Flutter Gold manages the master golden files for the `flutter/flutter`
/// repository.
///
/// * The [FlutterPreSubmitFileComparator] is utilized in pre-submit testing,
/// before a pull request lands on the master branch. This
/// comparator uses the [SkiaGoldClient] to execute tryjobs, allowing
/// contributors to view and check in visual differences before landing the
/// change.
///
/// * The [FlutterLocalFileComparator] is used for local development testing.
/// This comparator will use the [SkiaGoldClient] to request baseline images
/// from [Flutter Gold](https://flutter-gold.skia.org) and manually compare
/// pixels. If a difference is detected, this comparator will
/// generate failure output illustrating the found difference. If a baseline
/// is not found for a given test image, it will consider it a new test and
/// output the new image for verification.
///
/// The [FlutterSkippingFileComparator] is utilized to skip tests outside
/// of the appropriate environments described above. Currently, some Luci
/// environments do not execute golden file testing, and as such do not require
/// a comparator. This comparator is also used when an internet connection is
/// unavailable.
abstract
class
FlutterGoldenFileComparator
extends
GoldenFileComparator
{
/// Creates a [FlutterGoldenFileComparator] that will resolve golden file
/// URIs relative to the specified [basedir], and retrieve golden baselines
/// using the [skiaClient]. The [basedir] is used for writing and accessing
/// information and files for interacting with the [skiaClient]. When testing
/// locally, the [basedir] will also contain any diffs from failed tests, or
/// goldens generated from newly introduced tests.
///
/// The [fs] and [platform] parameters are useful in tests, where the default
/// file system and platform can be replaced by mock instances.
@visibleForTesting
FlutterGoldenFileComparator
(
this
.
basedir
,
this
.
skiaClient
,
{
this
.
fs
=
const
LocalFileSystem
(),
this
.
platform
=
const
LocalPlatform
(),
this
.
namePrefix
,
});
/// The directory to which golden file URIs will be resolved in [compare] and
/// [update], cannot be null.
final
Uri
basedir
;
/// A client for uploading image tests and making baseline requests to the
/// Flutter Gold Dashboard, cannot be null.
final
SkiaGoldClient
skiaClient
;
/// The file system used to perform file access.
@visibleForTesting
final
FileSystem
fs
;
/// A wrapper for the [dart:io.Platform] API.
@visibleForTesting
final
Platform
platform
;
/// The prefix that is added to all golden names.
final
String
?
namePrefix
;
@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
);
}
@override
Uri
getTestUri
(
Uri
key
,
int
?
version
)
=>
key
;
/// Calculate the appropriate basedir for the current test context.
///
/// The optional [suffix] argument is used by the
/// [FlutterPostSubmitFileComparator] and the [FlutterPreSubmitFileComparator].
/// These [FlutterGoldenFileComparators] randomize their base directories to
/// maintain thread safety while using the `goldctl` tool.
@protected
@visibleForTesting
static
Directory
getBaseDirectory
(
LocalFileComparator
defaultComparator
,
Platform
platform
,
{
String
?
suffix
,
})
{
const
FileSystem
fs
=
LocalFileSystem
();
final
Directory
flutterRoot
=
fs
.
directory
(
platform
.
environment
[
_kFlutterRootKey
]);
Directory
comparisonRoot
;
if
(
suffix
!=
null
)
{
comparisonRoot
=
fs
.
systemTempDirectory
.
createTempSync
(
suffix
);
}
else
{
comparisonRoot
=
flutterRoot
.
childDirectory
(
fs
.
path
.
join
(
'bin'
,
'cache'
,
'pkg'
,
'skia_goldens'
,
)
);
}
final
Directory
testDirectory
=
fs
.
directory
(
defaultComparator
.
basedir
);
final
String
testDirectoryRelativePath
=
fs
.
path
.
relative
(
testDirectory
.
path
,
from:
flutterRoot
.
path
,
);
return
comparisonRoot
.
childDirectory
(
testDirectoryRelativePath
);
}
/// Returns the golden [File] identified by the given [Uri].
@protected
File
getGoldenFile
(
Uri
uri
)
{
final
File
goldenFile
=
fs
.
directory
(
basedir
).
childFile
(
fs
.
file
(
uri
).
path
);
return
goldenFile
;
}
/// Prepends the golden URL with the library name that encloses the current
/// test.
Uri
_addPrefix
(
Uri
golden
)
{
// Ensure the Uri ends in .png as the SkiaClient expects
assert
(
golden
.
toString
().
split
(
'.'
).
last
==
'png'
,
'Golden files in the Flutter framework must end with the file extension '
'.png.'
);
return
Uri
.
parse
(<
String
>[
if
(
namePrefix
!=
null
)
namePrefix
!,
basedir
.
pathSegments
[
basedir
.
pathSegments
.
length
-
2
],
golden
.
toString
(),
].
join
(
'.'
));
}
}
/// A [FlutterGoldenFileComparator] for testing golden images with Skia Gold in
/// post-submit.
///
/// For testing across all platforms, the [SkiaGoldClient] is used to upload
/// images for framework-related golden tests and process results.
///
/// See also:
///
/// * [GoldenFileComparator], the abstract class that
/// [FlutterGoldenFileComparator] implements.
/// * [FlutterPreSubmitFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images before changes are
/// merged into the master branch.
/// * [FlutterLocalFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images locally on your
/// current machine.
class
FlutterPostSubmitFileComparator
extends
FlutterGoldenFileComparator
{
/// Creates a [FlutterPostSubmitFileComparator] that will test golden file
/// images against Skia Gold.
///
/// The [fs] and [platform] parameters are useful in tests, where the default
/// file system and platform can be replaced by mock instances.
FlutterPostSubmitFileComparator
(
super
.
basedir
,
super
.
skiaClient
,
{
super
.
fs
,
super
.
platform
,
super
.
namePrefix
,
});
/// Creates a new [FlutterPostSubmitFileComparator] that mirrors the relative
/// path resolution of the default [goldenFileComparator].
///
/// The [goldens] and [defaultComparator] parameters are visible for testing
/// purposes only.
static
Future
<
FlutterPostSubmitFileComparator
>
fromDefaultComparator
(
final
Platform
platform
,
{
SkiaGoldClient
?
goldens
,
LocalFileComparator
?
defaultComparator
,
String
?
namePrefix
,
})
async
{
defaultComparator
??=
goldenFileComparator
as
LocalFileComparator
;
final
Directory
baseDirectory
=
FlutterGoldenFileComparator
.
getBaseDirectory
(
defaultComparator
,
platform
,
suffix:
'flutter_goldens_postsubmit.'
,
);
baseDirectory
.
createSync
(
recursive:
true
);
goldens
??=
SkiaGoldClient
(
baseDirectory
);
await
goldens
.
auth
();
return
FlutterPostSubmitFileComparator
(
baseDirectory
.
uri
,
goldens
,
namePrefix:
namePrefix
);
}
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
await
skiaClient
.
imgtestInit
();
golden
=
_addPrefix
(
golden
);
await
update
(
golden
,
imageBytes
);
final
File
goldenFile
=
getGoldenFile
(
golden
);
return
skiaClient
.
imgtestAdd
(
golden
.
path
,
goldenFile
);
}
/// Decides based on the current environment if goldens tests should be
/// executed through Skia Gold.
static
bool
isAvailableForEnvironment
(
Platform
platform
)
{
final
bool
luciPostSubmit
=
platform
.
environment
.
containsKey
(
'SWARMING_TASK_ID'
)
&&
platform
.
environment
.
containsKey
(
'GOLDCTL'
)
// Luci tryjob environments contain this value to inform the [FlutterPreSubmitComparator].
&&
!
platform
.
environment
.
containsKey
(
'GOLD_TRYJOB'
);
return
luciPostSubmit
;
}
}
/// A [FlutterGoldenFileComparator] for testing golden images before changes are
/// merged into the master branch. The comparator executes tryjobs using the
/// [SkiaGoldClient].
///
/// See also:
///
/// * [GoldenFileComparator], the abstract class that
/// [FlutterGoldenFileComparator] implements.
/// * [FlutterPostSubmitFileComparator], another
/// [FlutterGoldenFileComparator] that uploads tests to the Skia Gold
/// dashboard in post-submit.
/// * [FlutterLocalFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images locally on your
/// current machine.
class
FlutterPreSubmitFileComparator
extends
FlutterGoldenFileComparator
{
/// Creates a [FlutterPreSubmitFileComparator] that will test golden file
/// images against baselines requested from Flutter Gold.
///
/// The [fs] and [platform] parameters are useful in tests, where the default
/// file system and platform can be replaced by mock instances.
FlutterPreSubmitFileComparator
(
super
.
basedir
,
super
.
skiaClient
,
{
super
.
fs
,
super
.
platform
,
super
.
namePrefix
,
});
/// Creates a new [FlutterPreSubmitFileComparator] that mirrors the
/// relative path resolution of the default [goldenFileComparator].
///
/// The [goldens] and [defaultComparator] parameters are visible for testing
/// purposes only.
static
Future
<
FlutterGoldenFileComparator
>
fromDefaultComparator
(
final
Platform
platform
,
{
SkiaGoldClient
?
goldens
,
LocalFileComparator
?
defaultComparator
,
Directory
?
testBasedir
,
String
?
namePrefix
,
})
async
{
defaultComparator
??=
goldenFileComparator
as
LocalFileComparator
;
final
Directory
baseDirectory
=
testBasedir
??
FlutterGoldenFileComparator
.
getBaseDirectory
(
defaultComparator
,
platform
,
suffix:
'flutter_goldens_presubmit.'
,
);
if
(!
baseDirectory
.
existsSync
())
{
baseDirectory
.
createSync
(
recursive:
true
);
}
goldens
??=
SkiaGoldClient
(
baseDirectory
);
await
goldens
.
auth
();
return
FlutterPreSubmitFileComparator
(
baseDirectory
.
uri
,
goldens
,
platform:
platform
,
namePrefix:
namePrefix
,
);
}
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
await
skiaClient
.
tryjobInit
();
golden
=
_addPrefix
(
golden
);
await
update
(
golden
,
imageBytes
);
final
File
goldenFile
=
getGoldenFile
(
golden
);
await
skiaClient
.
tryjobAdd
(
golden
.
path
,
goldenFile
);
// This will always return true since golden file test failures are managed
// in pre-submit checks by the flutter-gold status check.
return
true
;
}
/// Decides based on the current environment if goldens tests should be
/// executed as pre-submit tests with Skia Gold.
static
bool
isAvailableForEnvironment
(
Platform
platform
)
{
final
bool
luciPreSubmit
=
platform
.
environment
.
containsKey
(
'SWARMING_TASK_ID'
)
&&
platform
.
environment
.
containsKey
(
'GOLDCTL'
)
&&
platform
.
environment
.
containsKey
(
'GOLD_TRYJOB'
);
return
luciPreSubmit
;
}
}
/// A [FlutterGoldenFileComparator] for testing conditions that do not execute
/// golden file tests.
///
/// Currently, this comparator is used on Cirrus, or in Luci environments when executing tests
/// outside of the flutter/flutter repository.
///
/// See also:
///
/// * [FlutterPostSubmitFileComparator], another [FlutterGoldenFileComparator]
/// that tests golden images through Skia Gold.
/// * [FlutterPreSubmitFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images before changes are
/// merged into the master branch.
/// * [FlutterLocalFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images locally on your
/// current machine.
class
FlutterSkippingFileComparator
extends
FlutterGoldenFileComparator
{
/// Creates a [FlutterSkippingFileComparator] that will skip tests that
/// are not in the right environment for golden file testing.
FlutterSkippingFileComparator
(
super
.
basedir
,
super
.
skiaClient
,
this
.
reason
,
{
super
.
namePrefix
,
});
/// Describes the reason for using the [FlutterSkippingFileComparator].
///
/// Cannot be null.
final
String
reason
;
/// Creates a new [FlutterSkippingFileComparator] that mirrors the
/// relative path resolution of the default [goldenFileComparator].
static
FlutterSkippingFileComparator
fromDefaultComparator
(
String
reason
,
{
LocalFileComparator
?
defaultComparator
,
String
?
namePrefix
,
})
{
defaultComparator
??=
goldenFileComparator
as
LocalFileComparator
;
const
FileSystem
fs
=
LocalFileSystem
();
final
Uri
basedir
=
defaultComparator
.
basedir
;
final
SkiaGoldClient
skiaClient
=
SkiaGoldClient
(
fs
.
directory
(
basedir
));
return
FlutterSkippingFileComparator
(
basedir
,
skiaClient
,
reason
,
namePrefix:
namePrefix
);
}
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
// Ideally we would use markTestSkipped here but in some situations,
// comparators are called outside of tests.
// See also: https://github.com/flutter/flutter/issues/91285
// ignore: avoid_print
print
(
'Skipping "
$golden
" test:
$reason
'
);
return
true
;
}
@override
Future
<
void
>
update
(
Uri
golden
,
Uint8List
imageBytes
)
async
{}
/// Decides, based on the current environment, if this comparator should be
/// used.
///
/// If we are in a CI environment, LUCI or Cirrus, but are not using the other
/// comparators, we skip.
static
bool
isAvailableForEnvironment
(
Platform
platform
)
{
return
platform
.
environment
.
containsKey
(
'SWARMING_TASK_ID'
)
// Some builds are still being run on Cirrus, we should skip these.
||
platform
.
environment
.
containsKey
(
'CIRRUS_CI'
);
}
}
/// A [FlutterGoldenFileComparator] for testing golden images locally on your
/// current machine.
///
/// This comparator utilizes the [SkiaGoldClient] to request baseline images for
/// the given device under test for comparison. This comparator is initialized
/// when conditions for all other [FlutterGoldenFileComparators] have not been
/// met, see the `isAvailableForEnvironment` method for each one listed below.
///
/// The [FlutterLocalFileComparator] is intended to run on local machines and
/// serve as a smoke test during development. As such, it will not be able to
/// detect unintended changes on environments other than the currently executing
/// machine, until they are tested using the [FlutterPreSubmitFileComparator].
///
/// See also:
///
/// * [GoldenFileComparator], the abstract class that
/// [FlutterGoldenFileComparator] implements.
/// * [FlutterPostSubmitFileComparator], another
/// [FlutterGoldenFileComparator] that uploads tests to the Skia Gold
/// dashboard.
/// * [FlutterPreSubmitFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images before changes are
/// merged into the master branch.
/// * [FlutterSkippingFileComparator], another
/// [FlutterGoldenFileComparator] that controls post-submit testing
/// conditions that do not execute golden file tests.
class
FlutterLocalFileComparator
extends
FlutterGoldenFileComparator
with
LocalComparisonOutput
{
/// Creates a [FlutterLocalFileComparator] that will test golden file
/// images against baselines requested from Flutter Gold.
///
/// The [fs] and [platform] parameters are useful in tests, where the default
/// file system and platform can be replaced by mock instances.
FlutterLocalFileComparator
(
super
.
basedir
,
super
.
skiaClient
,
{
super
.
fs
,
super
.
platform
,
});
/// Creates a new [FlutterLocalFileComparator] that mirrors the
/// relative path resolution of the default [goldenFileComparator].
///
/// The [goldens], [defaultComparator], and [baseDirectory] parameters are
/// visible for testing purposes only.
static
Future
<
FlutterGoldenFileComparator
>
fromDefaultComparator
(
final
Platform
platform
,
{
SkiaGoldClient
?
goldens
,
LocalFileComparator
?
defaultComparator
,
Directory
?
baseDirectory
,
})
async
{
defaultComparator
??=
goldenFileComparator
as
LocalFileComparator
;
baseDirectory
??=
FlutterGoldenFileComparator
.
getBaseDirectory
(
defaultComparator
,
platform
,
);
if
(!
baseDirectory
.
existsSync
())
{
baseDirectory
.
createSync
(
recursive:
true
);
}
goldens
??=
SkiaGoldClient
(
baseDirectory
);
try
{
// Check if we can reach Gold.
await
goldens
.
getExpectationForTest
(
''
);
}
on
io
.
OSError
catch
(
_
)
{
return
FlutterSkippingFileComparator
(
baseDirectory
.
uri
,
goldens
,
'OSError occurred, could not reach Gold. '
'Switching to FlutterSkippingGoldenFileComparator.'
,
);
}
on
io
.
SocketException
catch
(
_
)
{
return
FlutterSkippingFileComparator
(
baseDirectory
.
uri
,
goldens
,
'SocketException occurred, could not reach Gold. '
'Switching to FlutterSkippingGoldenFileComparator.'
,
);
}
return
FlutterLocalFileComparator
(
baseDirectory
.
uri
,
goldens
);
}
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
golden
=
_addPrefix
(
golden
);
final
String
testName
=
skiaClient
.
cleanTestName
(
golden
.
path
);
late
String
?
testExpectation
;
testExpectation
=
await
skiaClient
.
getExpectationForTest
(
testName
);
if
(
testExpectation
==
null
||
testExpectation
.
isEmpty
)
{
// There is no baseline for this test.
// Ideally we would use markTestSkipped here but in some situations,
// comparators are called outside of tests.
// See also: https://github.com/flutter/flutter/issues/91285
// ignore: avoid_print
print
(
'No expectations provided by Skia Gold for test:
$golden
. '
'This may be a new test. If this is an unexpected result, check '
'https://flutter-gold.skia.org.
\n
'
'Validate image output found at
$basedir
'
);
update
(
golden
,
imageBytes
);
return
true
;
}
ComparisonResult
result
;
final
List
<
int
>
goldenBytes
=
await
skiaClient
.
getImageBytes
(
testExpectation
);
result
=
await
GoldenFileComparator
.
compareLists
(
imageBytes
,
goldenBytes
,
);
if
(
result
.
passed
)
{
return
true
;
}
final
String
error
=
await
generateFailureOutput
(
result
,
golden
,
basedir
);
throw
FlutterError
(
error
);
}
}
packages/flutter_goldens/lib/src/flaky_goldens.dart
deleted
100644 → 0
View file @
a1432a9c
// Copyright 2014 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.
// flutter_ignore_for_file: golden_tag (see analyze.dart)
import
'package:flutter_test/flutter_test.dart'
;
/// Similar to [matchesGoldenFile] but specialized for Flutter's own tests when
/// they are flaky.
///
/// Asserts that a [Finder], [Future<ui.Image>], or [ui.Image] - the [key] -
/// matches the golden image file identified by [goldenFile].
///
/// For the case of a [Finder], the [Finder] must match exactly one widget and
/// the rendered image of the first [RepaintBoundary] ancestor of the widget is
/// treated as the image for the widget. As such, you may choose to wrap a test
/// widget in a [RepaintBoundary] to specify a particular focus for the test.
///
/// The [goldenFile] may be either a [Uri] or a [String] representation of a URL.
///
/// Flaky golden file tests are always uploaded to Skia Gold for manual
/// inspection. This allows contributors to validate when a test is no longer
/// flaky by visiting https://flutter-gold.skia.org/list,
/// and clicking on the respective golden test name. The UI will show the
/// history of generated goldens over time. Each unique golden gets a unique
/// color. If the color is the same for all commits in the recent history, the
/// golden is likely no longer flaky and the standard [matchesGoldenFile] can be
/// used in the given test. If the color changes from commit to commit then it
/// is still flaky.
Future
<
void
>
expectFlakyGolden
(
Object
key
,
String
goldenFile
)
{
if
(
isBrowser
)
{
_setFlakyForWeb
();
}
else
{
_setFlakyForIO
();
}
return
expectLater
(
key
,
matchesGoldenFile
(
goldenFile
));
}
void
_setFlakyForWeb
(
)
{
assert
(
webGoldenComparator
is
FlakyGoldenMixin
,
'expectFlakyGolden can only be used with a comparator with the FlakyGoldenMixin '
'but found
${webGoldenComparator.runtimeType}
.'
);
(
webGoldenComparator
as
FlakyGoldenMixin
).
enableFlakyMode
();
}
void
_setFlakyForIO
(
)
{
assert
(
goldenFileComparator
is
FlakyGoldenMixin
,
'expectFlakyGolden can only be used with a comparator with the FlakyGoldenMixin '
'but found
${goldenFileComparator.runtimeType}
.'
);
(
goldenFileComparator
as
FlakyGoldenMixin
).
enableFlakyMode
();
}
/// Allows flaky test handling for the Flutter framework.
///
/// Mixed in with the [FlutterGoldenFileComparator] and
/// [_FlutterWebGoldenComparator].
mixin
FlakyGoldenMixin
{
/// Whether this comparator allows flaky goldens.
///
/// If set to true, concrete implementations of this class are expected to
/// generate the golden and submit it for review, but not fail the test.
bool
_isFlakyModeEnabled
=
false
;
/// Puts this comparator into flaky comparison mode.
///
/// After calling this method the next invocation of [compare] will allow
/// incorrect golden to pass the check.
///
/// Concrete implementations of [compare] must call [getAndResetFlakyMode] so
/// that subsequent tests can run in non-flaky mode. If a subsequent test
/// needs to run in a flaky mode, it must call this method again.
void
enableFlakyMode
()
{
assert
(
!
_isFlakyModeEnabled
,
'Test is already marked as flaky. Call `getAndResetFlakyMode` to reset the '
'flag before calling this method again.'
,
);
_isFlakyModeEnabled
=
true
;
}
/// Returns whether flaky comparison mode was enabled via [enableFlakyMode],
/// and if it was, resets the comparator back to non-flaky mode.
bool
getAndResetFlakyMode
()
{
if
(!
_isFlakyModeEnabled
)
{
// Not in flaky mode. Nothing to do.
return
false
;
}
// In flaky mode. Reset it and return true.
_isFlakyModeEnabled
=
false
;
return
true
;
}
}
packages/flutter_goldens/lib/src/flutter_goldens_io.dart
deleted
100644 → 0
View file @
a1432a9c
// Copyright 2014 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:async'
show
FutureOr
;
import
'dart:convert'
show
jsonEncode
;
import
'dart:io'
as
io
show
File
,
OSError
,
SocketException
,
stdout
;
import
'package:file/file.dart'
;
import
'package:file/local.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter_goldens_client/skia_client.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:platform/platform.dart'
;
import
'flaky_goldens.dart'
;
export
'package:flutter_goldens_client/skia_client.dart'
;
// If you are here trying to figure out how to use golden files in the Flutter
// repo itself, consider reading this wiki page:
// https://github.com/flutter/flutter/wiki/Writing-a-golden-file-test-for-package%3Aflutter
const
String
_kFlutterRootKey
=
'FLUTTER_ROOT'
;
/// Main method that can be used in a `flutter_test_config.dart` file to set
/// [goldenFileComparator] to an instance of [FlutterGoldenFileComparator] that
/// works for the current test. _Which_ FlutterGoldenFileComparator is
/// instantiated is based on the current testing environment.
///
/// When set, the `namePrefix` is prepended to the names of all gold images.
Future
<
void
>
testExecutable
(
FutureOr
<
void
>
Function
()
testMain
,
{
String
?
namePrefix
})
async
{
const
Platform
platform
=
LocalPlatform
();
if
(
FlutterPostSubmitFileComparator
.
isAvailableForEnvironment
(
platform
))
{
goldenFileComparator
=
await
FlutterPostSubmitFileComparator
.
fromDefaultComparator
(
platform
,
namePrefix:
namePrefix
);
}
else
if
(
FlutterPreSubmitFileComparator
.
isAvailableForEnvironment
(
platform
))
{
goldenFileComparator
=
await
FlutterPreSubmitFileComparator
.
fromDefaultComparator
(
platform
,
namePrefix:
namePrefix
);
}
else
if
(
FlutterSkippingFileComparator
.
isAvailableForEnvironment
(
platform
))
{
goldenFileComparator
=
FlutterSkippingFileComparator
.
fromDefaultComparator
(
'Golden file testing is not executed on Cirrus, or LUCI environments outside of flutter/flutter.'
,
namePrefix:
namePrefix
);
}
else
{
goldenFileComparator
=
await
FlutterLocalFileComparator
.
fromDefaultComparator
(
platform
);
}
await
testMain
();
}
/// Processes golden check commands sent from the browser process.
///
/// When running browser tests, goldens are not generated within the app itself
/// due to browser restrictions. Instead, when a test calls [expectFlakyGolden]
/// the browser sends a [command] to a host process. This function handles the
/// command.
///
/// This custom command handler is used for Flutter's own goldens. It
/// understands the "isFlaky" property, a boolean encoded in the command as a
/// custom command property. If true, uses a golden comparator that submits the
/// golden, but does not fail the test, allowing manual inspection in the Skia
/// Gold UI and verification of fixes.
///
/// See also:
/// * [FlakyWebGoldenComparator], which implements custom browser-side logic.
Future
<
void
>
processBrowserCommand
(
dynamic
command
)
async
{
if
(
command
is
Map
<
String
,
dynamic
>)
{
final
io
.
File
imageFile
=
io
.
File
(
command
[
'imageFile'
]
as
String
);
final
Uri
goldenKey
=
Uri
.
parse
(
command
[
'key'
]
as
String
);
final
bool
update
=
command
[
'update'
]
as
bool
;
final
Map
<
String
,
dynamic
>
customProperties
=
(
command
[
'customProperties'
]
as
Map
<
String
,
dynamic
>?)
??
const
<
String
,
dynamic
>{};
final
bool
isFlaky
=
(
customProperties
[
'isFlaky'
]
as
bool
?)
??
false
;
final
Uint8List
bytes
=
await
io
.
File
(
imageFile
.
path
).
readAsBytes
();
if
(
update
)
{
await
goldenFileComparator
.
update
(
goldenKey
,
bytes
);
io
.
stdout
.
writeln
(
jsonEncode
(<
String
,
dynamic
>{
'success'
:
true
}));
}
else
{
try
{
assert
(
goldenFileComparator
is
FlutterGoldenFileComparator
,
'matchesFlutterGolden can only be used with FlutterGoldenFileComparator '
'but found
${goldenFileComparator.runtimeType}
.'
);
if
(
isFlaky
)
{
(
goldenFileComparator
as
FlutterGoldenFileComparator
).
enableFlakyMode
();
}
final
bool
success
=
await
goldenFileComparator
.
compare
(
bytes
,
goldenKey
);
io
.
stdout
.
writeln
(
jsonEncode
(<
String
,
dynamic
>{
'success'
:
success
}));
}
on
Exception
catch
(
exception
)
{
io
.
stdout
.
writeln
(
jsonEncode
(<
String
,
dynamic
>{
'success'
:
false
,
'message'
:
'
$exception
'
}));
}
}
}
else
{
io
.
stdout
.
writeln
(
'object type is not right'
);
}
}
/// Abstract base class golden file comparator specific to the `flutter/flutter`
/// repository.
///
/// Golden file testing for the `flutter/flutter` repository is handled by three
/// different [FlutterGoldenFileComparator]s, depending on the current testing
/// environment.
///
/// * The [FlutterPostSubmitFileComparator] is utilized during post-submit
/// testing, after a pull request has landed on the master branch. This
/// comparator uses the [SkiaGoldClient] and the `goldctl` tool to upload
/// tests to the [Flutter Gold dashboard](https://flutter-gold.skia.org).
/// Flutter Gold manages the master golden files for the `flutter/flutter`
/// repository.
///
/// * The [FlutterPreSubmitFileComparator] is utilized in pre-submit testing,
/// before a pull request lands on the master branch. This
/// comparator uses the [SkiaGoldClient] to execute tryjobs, allowing
/// contributors to view and check in visual differences before landing the
/// change.
///
/// * The [FlutterLocalFileComparator] is used for local development testing.
/// This comparator will use the [SkiaGoldClient] to request baseline images
/// from [Flutter Gold](https://flutter-gold.skia.org) and manually compare
/// pixels. If a difference is detected, this comparator will
/// generate failure output illustrating the found difference. If a baseline
/// is not found for a given test image, it will consider it a new test and
/// output the new image for verification.
///
/// The [FlutterSkippingFileComparator] is utilized to skip tests outside
/// of the appropriate environments described above. Currently, some Luci
/// environments do not execute golden file testing, and as such do not require
/// a comparator. This comparator is also used when an internet connection is
/// unavailable.
abstract
class
FlutterGoldenFileComparator
extends
GoldenFileComparator
with
FlakyGoldenMixin
{
/// Creates a [FlutterGoldenFileComparator] that will resolve golden file
/// URIs relative to the specified [basedir], and retrieve golden baselines
/// using the [skiaClient]. The [basedir] is used for writing and accessing
/// information and files for interacting with the [skiaClient]. When testing
/// locally, the [basedir] will also contain any diffs from failed tests, or
/// goldens generated from newly introduced tests.
///
/// The [fs] and [platform] parameters are useful in tests, where the default
/// file system and platform can be replaced by mock instances.
@visibleForTesting
FlutterGoldenFileComparator
(
this
.
basedir
,
this
.
skiaClient
,
{
this
.
fs
=
const
LocalFileSystem
(),
this
.
platform
=
const
LocalPlatform
(),
this
.
namePrefix
,
});
/// The directory to which golden file URIs will be resolved in [compare] and
/// [update], cannot be null.
final
Uri
basedir
;
/// A client for uploading image tests and making baseline requests to the
/// Flutter Gold Dashboard, cannot be null.
final
SkiaGoldClient
skiaClient
;
/// The file system used to perform file access.
@visibleForTesting
final
FileSystem
fs
;
/// A wrapper for the [dart:io.Platform] API.
@visibleForTesting
final
Platform
platform
;
/// The prefix that is added to all golden names.
final
String
?
namePrefix
;
@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
);
}
@override
Uri
getTestUri
(
Uri
key
,
int
?
version
)
=>
key
;
/// Calculate the appropriate basedir for the current test context.
///
/// The optional [suffix] argument is used by the
/// [FlutterPostSubmitFileComparator] and the [FlutterPreSubmitFileComparator].
/// These [FlutterGoldenFileComparators] randomize their base directories to
/// maintain thread safety while using the `goldctl` tool.
@protected
@visibleForTesting
static
Directory
getBaseDirectory
(
LocalFileComparator
defaultComparator
,
Platform
platform
,
{
String
?
suffix
,
})
{
const
FileSystem
fs
=
LocalFileSystem
();
final
Directory
flutterRoot
=
fs
.
directory
(
platform
.
environment
[
_kFlutterRootKey
]);
Directory
comparisonRoot
;
if
(
suffix
!=
null
)
{
comparisonRoot
=
fs
.
systemTempDirectory
.
createTempSync
(
suffix
);
}
else
{
comparisonRoot
=
flutterRoot
.
childDirectory
(
fs
.
path
.
join
(
'bin'
,
'cache'
,
'pkg'
,
'skia_goldens'
,
)
);
}
final
Directory
testDirectory
=
fs
.
directory
(
defaultComparator
.
basedir
);
final
String
testDirectoryRelativePath
=
fs
.
path
.
relative
(
testDirectory
.
path
,
from:
flutterRoot
.
path
,
);
return
comparisonRoot
.
childDirectory
(
testDirectoryRelativePath
);
}
/// Returns the golden [File] identified by the given [Uri].
@protected
File
getGoldenFile
(
Uri
uri
)
{
final
File
goldenFile
=
fs
.
directory
(
basedir
).
childFile
(
fs
.
file
(
uri
).
path
);
return
goldenFile
;
}
/// Prepends the golden URL with the library name that encloses the current
/// test.
Uri
_addPrefix
(
Uri
golden
)
{
// Ensure the Uri ends in .png as the SkiaClient expects
assert
(
golden
.
toString
().
split
(
'.'
).
last
==
'png'
,
'Golden files in the Flutter framework must end with the file extension '
'.png.'
);
return
Uri
.
parse
(<
String
>[
if
(
namePrefix
!=
null
)
namePrefix
!,
basedir
.
pathSegments
[
basedir
.
pathSegments
.
length
-
2
],
golden
.
toString
(),
].
join
(
'.'
));
}
}
/// A [FlutterGoldenFileComparator] for testing golden images with Skia Gold in
/// post-submit.
///
/// For testing across all platforms, the [SkiaGoldClient] is used to upload
/// images for framework-related golden tests and process results.
///
/// See also:
///
/// * [GoldenFileComparator], the abstract class that
/// [FlutterGoldenFileComparator] implements.
/// * [FlutterPreSubmitFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images before changes are
/// merged into the master branch.
/// * [FlutterLocalFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images locally on your
/// current machine.
class
FlutterPostSubmitFileComparator
extends
FlutterGoldenFileComparator
{
/// Creates a [FlutterPostSubmitFileComparator] that will test golden file
/// images against Skia Gold.
///
/// The [fs] and [platform] parameters are useful in tests, where the default
/// file system and platform can be replaced by mock instances.
FlutterPostSubmitFileComparator
(
super
.
basedir
,
super
.
skiaClient
,
{
super
.
fs
,
super
.
platform
,
super
.
namePrefix
,
});
/// Creates a new [FlutterPostSubmitFileComparator] that mirrors the relative
/// path resolution of the default [goldenFileComparator].
///
/// The [goldens] and [defaultComparator] parameters are visible for testing
/// purposes only.
static
Future
<
FlutterPostSubmitFileComparator
>
fromDefaultComparator
(
final
Platform
platform
,
{
SkiaGoldClient
?
goldens
,
LocalFileComparator
?
defaultComparator
,
String
?
namePrefix
,
})
async
{
defaultComparator
??=
goldenFileComparator
as
LocalFileComparator
;
final
Directory
baseDirectory
=
FlutterGoldenFileComparator
.
getBaseDirectory
(
defaultComparator
,
platform
,
suffix:
'flutter_goldens_postsubmit.'
,
);
baseDirectory
.
createSync
(
recursive:
true
);
goldens
??=
SkiaGoldClient
(
baseDirectory
);
await
goldens
.
auth
();
return
FlutterPostSubmitFileComparator
(
baseDirectory
.
uri
,
goldens
,
namePrefix:
namePrefix
);
}
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
final
bool
isFlaky
=
getAndResetFlakyMode
();
await
skiaClient
.
imgtestInit
(
isFlaky:
isFlaky
);
golden
=
_addPrefix
(
golden
);
await
update
(
golden
,
imageBytes
);
final
File
goldenFile
=
getGoldenFile
(
golden
);
return
skiaClient
.
imgtestAdd
(
golden
.
path
,
goldenFile
,
isFlaky:
isFlaky
);
}
/// Decides based on the current environment if goldens tests should be
/// executed through Skia Gold.
static
bool
isAvailableForEnvironment
(
Platform
platform
)
{
final
bool
luciPostSubmit
=
platform
.
environment
.
containsKey
(
'SWARMING_TASK_ID'
)
&&
platform
.
environment
.
containsKey
(
'GOLDCTL'
)
// Luci tryjob environments contain this value to inform the [FlutterPreSubmitComparator].
&&
!
platform
.
environment
.
containsKey
(
'GOLD_TRYJOB'
);
return
luciPostSubmit
;
}
}
/// A [FlutterGoldenFileComparator] for testing golden images before changes are
/// merged into the master branch. The comparator executes tryjobs using the
/// [SkiaGoldClient].
///
/// See also:
///
/// * [GoldenFileComparator], the abstract class that
/// [FlutterGoldenFileComparator] implements.
/// * [FlutterPostSubmitFileComparator], another
/// [FlutterGoldenFileComparator] that uploads tests to the Skia Gold
/// dashboard in post-submit.
/// * [FlutterLocalFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images locally on your
/// current machine.
class
FlutterPreSubmitFileComparator
extends
FlutterGoldenFileComparator
{
/// Creates a [FlutterPreSubmitFileComparator] that will test golden file
/// images against baselines requested from Flutter Gold.
///
/// The [fs] and [platform] parameters are useful in tests, where the default
/// file system and platform can be replaced by mock instances.
FlutterPreSubmitFileComparator
(
super
.
basedir
,
super
.
skiaClient
,
{
super
.
fs
,
super
.
platform
,
super
.
namePrefix
,
});
/// Creates a new [FlutterPreSubmitFileComparator] that mirrors the
/// relative path resolution of the default [goldenFileComparator].
///
/// The [goldens] and [defaultComparator] parameters are visible for testing
/// purposes only.
static
Future
<
FlutterGoldenFileComparator
>
fromDefaultComparator
(
final
Platform
platform
,
{
SkiaGoldClient
?
goldens
,
LocalFileComparator
?
defaultComparator
,
Directory
?
testBasedir
,
String
?
namePrefix
,
})
async
{
defaultComparator
??=
goldenFileComparator
as
LocalFileComparator
;
final
Directory
baseDirectory
=
testBasedir
??
FlutterGoldenFileComparator
.
getBaseDirectory
(
defaultComparator
,
platform
,
suffix:
'flutter_goldens_presubmit.'
,
);
if
(!
baseDirectory
.
existsSync
())
{
baseDirectory
.
createSync
(
recursive:
true
);
}
goldens
??=
SkiaGoldClient
(
baseDirectory
);
await
goldens
.
auth
();
return
FlutterPreSubmitFileComparator
(
baseDirectory
.
uri
,
goldens
,
platform:
platform
,
namePrefix:
namePrefix
,
);
}
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
final
bool
isFlaky
=
getAndResetFlakyMode
();
await
skiaClient
.
tryjobInit
(
isFlaky:
isFlaky
);
golden
=
_addPrefix
(
golden
);
await
update
(
golden
,
imageBytes
);
final
File
goldenFile
=
getGoldenFile
(
golden
);
await
skiaClient
.
tryjobAdd
(
golden
.
path
,
goldenFile
,
isFlaky:
isFlaky
);
// This will always return true since golden file test failures are managed
// in pre-submit checks by the flutter-gold status check.
return
true
;
}
/// Decides based on the current environment if goldens tests should be
/// executed as pre-submit tests with Skia Gold.
static
bool
isAvailableForEnvironment
(
Platform
platform
)
{
final
bool
luciPreSubmit
=
platform
.
environment
.
containsKey
(
'SWARMING_TASK_ID'
)
&&
platform
.
environment
.
containsKey
(
'GOLDCTL'
)
&&
platform
.
environment
.
containsKey
(
'GOLD_TRYJOB'
);
return
luciPreSubmit
;
}
}
/// A [FlutterGoldenFileComparator] for testing conditions that do not execute
/// golden file tests.
///
/// Currently, this comparator is used on Cirrus, or in Luci environments when executing tests
/// outside of the flutter/flutter repository.
///
/// See also:
///
/// * [FlutterPostSubmitFileComparator], another [FlutterGoldenFileComparator]
/// that tests golden images through Skia Gold.
/// * [FlutterPreSubmitFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images before changes are
/// merged into the master branch.
/// * [FlutterLocalFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images locally on your
/// current machine.
class
FlutterSkippingFileComparator
extends
FlutterGoldenFileComparator
{
/// Creates a [FlutterSkippingFileComparator] that will skip tests that
/// are not in the right environment for golden file testing.
FlutterSkippingFileComparator
(
super
.
basedir
,
super
.
skiaClient
,
this
.
reason
,
{
super
.
namePrefix
,
});
/// Describes the reason for using the [FlutterSkippingFileComparator].
///
/// Cannot be null.
final
String
reason
;
/// Creates a new [FlutterSkippingFileComparator] that mirrors the
/// relative path resolution of the default [goldenFileComparator].
static
FlutterSkippingFileComparator
fromDefaultComparator
(
String
reason
,
{
LocalFileComparator
?
defaultComparator
,
String
?
namePrefix
,
bool
isFlaky
=
false
,
})
{
defaultComparator
??=
goldenFileComparator
as
LocalFileComparator
;
const
FileSystem
fs
=
LocalFileSystem
();
final
Uri
basedir
=
defaultComparator
.
basedir
;
final
SkiaGoldClient
skiaClient
=
SkiaGoldClient
(
fs
.
directory
(
basedir
));
return
FlutterSkippingFileComparator
(
basedir
,
skiaClient
,
reason
,
namePrefix:
namePrefix
);
}
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
// Ideally we would use markTestSkipped here but in some situations,
// comparators are called outside of tests.
// See also: https://github.com/flutter/flutter/issues/91285
// ignore: avoid_print
getAndResetFlakyMode
();
// ignore: avoid_print
print
(
'Skipping "
$golden
" test:
$reason
'
);
return
true
;
}
@override
Future
<
void
>
update
(
Uri
golden
,
Uint8List
imageBytes
)
async
{}
/// Decides, based on the current environment, if this comparator should be
/// used.
///
/// If we are in a CI environment, LUCI or Cirrus, but are not using the other
/// comparators, we skip.
static
bool
isAvailableForEnvironment
(
Platform
platform
)
{
return
platform
.
environment
.
containsKey
(
'SWARMING_TASK_ID'
)
// Some builds are still being run on Cirrus, we should skip these.
||
platform
.
environment
.
containsKey
(
'CIRRUS_CI'
);
}
}
/// A [FlutterGoldenFileComparator] for testing golden images locally on your
/// current machine.
///
/// This comparator utilizes the [SkiaGoldClient] to request baseline images for
/// the given device under test for comparison. This comparator is initialized
/// when conditions for all other [FlutterGoldenFileComparators] have not been
/// met, see the `isAvailableForEnvironment` method for each one listed below.
///
/// The [FlutterLocalFileComparator] is intended to run on local machines and
/// serve as a smoke test during development. As such, it will not be able to
/// detect unintended changes on environments other than the currently executing
/// machine, until they are tested using the [FlutterPreSubmitFileComparator].
///
/// See also:
///
/// * [GoldenFileComparator], the abstract class that
/// [FlutterGoldenFileComparator] implements.
/// * [FlutterPostSubmitFileComparator], another
/// [FlutterGoldenFileComparator] that uploads tests to the Skia Gold
/// dashboard.
/// * [FlutterPreSubmitFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images before changes are
/// merged into the master branch.
/// * [FlutterSkippingFileComparator], another
/// [FlutterGoldenFileComparator] that controls post-submit testing
/// conditions that do not execute golden file tests.
class
FlutterLocalFileComparator
extends
FlutterGoldenFileComparator
with
LocalComparisonOutput
{
/// Creates a [FlutterLocalFileComparator] that will test golden file
/// images against baselines requested from Flutter Gold.
///
/// The [fs] and [platform] parameters are useful in tests, where the default
/// file system and platform can be replaced by mock instances.
FlutterLocalFileComparator
(
super
.
basedir
,
super
.
skiaClient
,
{
super
.
fs
,
super
.
platform
,
});
/// Creates a new [FlutterLocalFileComparator] that mirrors the
/// relative path resolution of the default [goldenFileComparator].
///
/// The [goldens], [defaultComparator], and [baseDirectory] parameters are
/// visible for testing purposes only.
static
Future
<
FlutterGoldenFileComparator
>
fromDefaultComparator
(
final
Platform
platform
,
{
SkiaGoldClient
?
goldens
,
LocalFileComparator
?
defaultComparator
,
Directory
?
baseDirectory
,
})
async
{
defaultComparator
??=
goldenFileComparator
as
LocalFileComparator
;
baseDirectory
??=
FlutterGoldenFileComparator
.
getBaseDirectory
(
defaultComparator
,
platform
,
);
if
(!
baseDirectory
.
existsSync
())
{
baseDirectory
.
createSync
(
recursive:
true
);
}
goldens
??=
SkiaGoldClient
(
baseDirectory
);
try
{
// Check if we can reach Gold.
await
goldens
.
getExpectationForTest
(
''
);
}
on
io
.
OSError
catch
(
_
)
{
return
FlutterSkippingFileComparator
(
baseDirectory
.
uri
,
goldens
,
'OSError occurred, could not reach Gold. '
'Switching to FlutterSkippingGoldenFileComparator.'
,
);
}
on
io
.
SocketException
catch
(
_
)
{
return
FlutterSkippingFileComparator
(
baseDirectory
.
uri
,
goldens
,
'SocketException occurred, could not reach Gold. '
'Switching to FlutterSkippingGoldenFileComparator.'
,
);
}
return
FlutterLocalFileComparator
(
baseDirectory
.
uri
,
goldens
);
}
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
final
bool
isFlaky
=
getAndResetFlakyMode
();
golden
=
_addPrefix
(
golden
);
final
String
testName
=
skiaClient
.
cleanTestName
(
golden
.
path
);
late
String
?
testExpectation
;
testExpectation
=
await
skiaClient
.
getExpectationForTest
(
testName
);
if
(
testExpectation
==
null
||
testExpectation
.
isEmpty
)
{
// There is no baseline for this test.
// Ideally we would use markTestSkipped here but in some situations,
// comparators are called outside of tests.
// See also: https://github.com/flutter/flutter/issues/91285
// ignore: avoid_print
print
(
'No expectations provided by Skia Gold for test:
$golden
. '
'This may be a new test. If this is an unexpected result, check '
'https://flutter-gold.skia.org.
\n
'
'Validate image output found at
$basedir
'
);
update
(
golden
,
imageBytes
);
return
true
;
}
ComparisonResult
result
;
final
List
<
int
>
goldenBytes
=
await
skiaClient
.
getImageBytes
(
testExpectation
);
result
=
await
GoldenFileComparator
.
compareLists
(
imageBytes
,
goldenBytes
,
);
if
(
result
.
passed
)
{
return
true
;
}
final
String
error
=
await
generateFailureOutput
(
result
,
golden
,
basedir
);
if
(!
isFlaky
)
{
throw
FlutterError
(
error
);
}
else
{
// The test was marked as flaky. Do not fail the test.
// TODO(yjbanov): there's no way to communicate warnings to the caller https://github.com/flutter/flutter/issues/91285
// ignore: avoid_print
print
(
'Golden
$golden
is marked as flaky and will not fail the test.'
);
// ignore: avoid_print
print
(
error
);
}
return
true
;
}
}
packages/flutter_goldens/lib/src/flutter_goldens_web.dart
deleted
100644 → 0
View file @
a1432a9c
// Copyright 2014 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:async'
show
FutureOr
;
import
'dart:convert'
show
json
;
import
'dart:html'
as
html
;
import
'package:flutter_test/flutter_test.dart'
;
import
'flaky_goldens.dart'
;
export
'package:flutter_goldens_client/skia_client.dart'
;
/// Wraps a web test, supplying a custom comparator that supports flaky goldens.
Future
<
void
>
testExecutable
(
FutureOr
<
void
>
Function
()
testMain
,
{
String
?
namePrefix
})
async
{
webGoldenComparator
=
FlutterWebGoldenComparator
(
webTestUri
);
await
testMain
();
}
/// See the io implementation of this function.
Future
<
void
>
processBrowserCommand
(
dynamic
command
)
async
{
throw
UnimplementedError
(
'processBrowserCommand is not used inside the browser'
);
}
/// Same as [DefaultWebGoldenComparator] but supports flaky golden checks.
class
FlutterWebGoldenComparator
extends
WebGoldenComparator
with
FlakyGoldenMixin
{
/// Creates a new [FlutterWebGoldenComparator] for the specified [testUri].
///
/// Golden file keys will be interpreted as file paths relative to the
/// directory in which [testUri] resides.
///
/// The [testUri] URL must represent a file.
FlutterWebGoldenComparator
(
this
.
testUri
);
/// The test file currently being executed.
///
/// Golden file keys will be interpreted as file paths relative to the
/// directory in which this file resides.
Uri
testUri
;
@override
Future
<
bool
>
compare
(
double
width
,
double
height
,
Uri
golden
)
async
{
final
bool
isFlaky
=
getAndResetFlakyMode
();
final
String
key
=
golden
.
toString
();
final
html
.
HttpRequest
request
=
await
html
.
HttpRequest
.
request
(
'flutter_goldens'
,
method:
'POST'
,
sendData:
json
.
encode
(<
String
,
Object
>{
'testUri'
:
testUri
.
toString
(),
'key'
:
key
,
'width'
:
width
.
round
(),
'height'
:
height
.
round
(),
'customProperties'
:
<
String
,
dynamic
>{
'isFlaky'
:
isFlaky
,
},
}),
);
final
String
response
=
request
.
response
as
String
;
if
(
response
==
'true'
)
{
return
true
;
}
fail
(
response
);
}
@override
Future
<
void
>
update
(
double
width
,
double
height
,
Uri
golden
)
async
{
// Update is handled on the server side, just use the same logic here
await
compare
(
width
,
height
,
golden
);
}
}
packages/flutter_goldens/test/flaky_goldens_test.dart
deleted
100644 → 0
View file @
a1432a9c
// Copyright 2014 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.
// See also dev/automated_tests/flutter_test/flutter_gold_test.dart
// flutter_ignore_for_file: golden_tag (see analyze.dart)
import
'package:flutter_goldens/flutter_goldens.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'utils/fakes.dart'
;
void
main
(
)
{
test
(
'Sets flaky flag'
,
()
{
final
FakeFlakyLocalFileComparator
comparator
=
FakeFlakyLocalFileComparator
();
// Is not flaky
expect
(
comparator
.
getAndResetFlakyMode
(),
isFalse
);
comparator
.
enableFlakyMode
();
// Flaky was set
expect
(
comparator
.
getAndResetFlakyMode
(),
isTrue
);
// Flaky was unset
expect
(
comparator
.
getAndResetFlakyMode
(),
isFalse
);
});
test
(
'Asserts when comparator is missing mixin'
,
(){
final
GoldenFileComparator
oldComparator
=
goldenFileComparator
;
goldenFileComparator
=
FakeLocalFileComparator
();
expect
(
()
{
expect
(
expectFlakyGolden
(<
int
>[
0
,
1
,
2
,
3
],
'golden_file.png'
),
throwsAssertionError
,
);
},
throwsA
(
isA
<
AssertionError
>().
having
((
AssertionError
error
)
=>
error
.
toString
(),
'description'
,
contains
(
'FlakyGoldenMixin'
)),
),
);
goldenFileComparator
=
oldComparator
;
});
test
(
'top level function sets flag'
,
()
{
final
GoldenFileComparator
oldComparator
=
goldenFileComparator
;
goldenFileComparator
=
FakeFlakyLocalFileComparator
();
expectFlakyGolden
(<
int
>[
0
,
1
,
2
,
3
],
'golden_file.png'
);
final
bool
wasFlaky
=
(
goldenFileComparator
as
FakeFlakyLocalFileComparator
).
getAndResetFlakyMode
();
expect
(
wasFlaky
,
isTrue
);
goldenFileComparator
=
oldComparator
;
});
}
packages/flutter_goldens/test/flutter_goldens_test.dart
View file @
a84e369b
...
...
@@ -4,22 +4,34 @@
// See also dev/automated_tests/flutter_test/flutter_gold_test.dart
import
'dart:convert'
;
import
'dart:io'
hide
Directory
;
import
'package:file/file.dart'
;
import
'package:file/memory.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter_goldens/
src/flutter_goldens_io
.dart'
;
import
'package:flutter_goldens/
flutter_goldens
.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:platform/platform.dart'
;
import
'package:process/process.dart'
;
import
'
utils/fak
es.dart'
;
import
'
json_templat
es.dart'
;
const
String
_kFlutterRoot
=
'/flutter'
;
// 1x1 transparent pixel
const
List
<
int
>
_kTestPngBytes
=
<
int
>[
137
,
80
,
78
,
71
,
13
,
10
,
26
,
10
,
0
,
0
,
0
,
13
,
73
,
72
,
68
,
82
,
0
,
0
,
0
,
1
,
0
,
0
,
0
,
1
,
8
,
6
,
0
,
0
,
0
,
31
,
21
,
196
,
137
,
0
,
0
,
0
,
11
,
73
,
68
,
65
,
84
,
120
,
1
,
99
,
97
,
0
,
2
,
0
,
0
,
25
,
0
,
5
,
144
,
240
,
54
,
245
,
0
,
0
,
0
,
0
,
73
,
69
,
78
,
68
,
174
,
66
,
96
,
130
,
];
void
main
(
)
{
late
MemoryFileSystem
fs
;
late
FakePlatform
platform
;
late
FakeProcessManager
process
;
late
FakeHttpClient
fakeHttpClient
;
setUp
(()
{
fs
=
MemoryFileSystem
();
...
...
@@ -27,9 +39,567 @@ void main() {
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
},
operatingSystem:
'macos'
);
process
=
FakeProcessManager
();
fakeHttpClient
=
FakeHttpClient
();
fs
.
directory
(
_kFlutterRoot
).
createSync
(
recursive:
true
);
});
group
(
'SkiaGoldClient'
,
()
{
late
SkiaGoldClient
skiaClient
;
late
Directory
workDirectory
;
setUp
(()
{
workDirectory
=
fs
.
directory
(
'/workDirectory'
)
..
createSync
(
recursive:
true
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
});
test
(
'web HTML test'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'GOLDCTL'
:
'goldctl'
,
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'FLUTTER_TEST_BROWSER'
:
'Chrome'
,
'FLUTTER_WEB_RENDERER'
:
'html'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
final
File
goldenFile
=
fs
.
file
(
'/workDirectory/temp/golden_file_test.png'
)
..
createSync
(
recursive:
true
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'add'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--test-name'
,
'golden_file_test'
,
'--png-file'
,
'/workDirectory/temp/golden_file_test.png'
,
'--passfail'
,
'--add-test-optional-key'
,
'image_matching_algorithm:fuzzy'
,
'--add-test-optional-key'
,
'fuzzy_max_different_pixels:20'
,
'--add-test-optional-key'
,
'fuzzy_pixel_delta_threshold:4'
,
],
null
,
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
0
,
''
,
''
);
expect
(
await
skiaClient
.
imgtestAdd
(
'golden_file_test.png'
,
goldenFile
),
isTrue
,
);
});
test
(
'web CanvasKit test'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'GOLDCTL'
:
'goldctl'
,
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'FLUTTER_TEST_BROWSER'
:
'Chrome'
,
'FLUTTER_WEB_RENDERER'
:
'canvaskit'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
final
File
goldenFile
=
fs
.
file
(
'/workDirectory/temp/golden_file_test.png'
)
..
createSync
(
recursive:
true
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'add'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--test-name'
,
'golden_file_test'
,
'--png-file'
,
'/workDirectory/temp/golden_file_test.png'
,
'--passfail'
,
],
null
,
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
0
,
''
,
''
);
expect
(
await
skiaClient
.
imgtestAdd
(
'golden_file_test.png'
,
goldenFile
),
isTrue
,
);
});
test
(
'auth performs minimal work if already authorized'
,
()
async
{
final
File
authFile
=
fs
.
file
(
'/workDirectory/temp/auth_opt.json'
)
..
createSync
(
recursive:
true
);
authFile
.
writeAsStringSync
(
authTemplate
());
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
0
,
''
,
''
);
await
skiaClient
.
auth
();
expect
(
process
.
workingDirectories
,
isEmpty
);
});
test
(
'gsutil is checked when authorization file is present'
,
()
async
{
final
File
authFile
=
fs
.
file
(
'/workDirectory/temp/auth_opt.json'
)
..
createSync
(
recursive:
true
);
authFile
.
writeAsStringSync
(
authTemplate
(
gsutil:
true
));
expect
(
await
skiaClient
.
clientIsAuthorized
(),
isFalse
,
);
});
test
(
'throws for error state from auth'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLD_SERVICE_ACCOUNT'
:
'Service Account'
,
'GOLDCTL'
:
'goldctl'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
expect
(
skiaClient
.
auth
(),
throwsException
,
);
});
test
(
'throws for error state from init'
,
()
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
const
RunInvocation
gitInvocation
=
RunInvocation
(
<
String
>[
'git'
,
'rev-parse'
,
'HEAD'
],
'/flutter'
,
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'init'
,
'--instance'
,
'flutter'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--commit'
,
'12345678'
,
'--keys-file'
,
'/workDirectory/keys.json'
,
'--failure-file'
,
'/workDirectory/failures.json'
,
'--passfail'
,
],
null
,
);
process
.
processResults
[
gitInvocation
]
=
ProcessResult
(
12345678
,
0
,
'12345678'
,
''
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
1
,
'Expected failure'
,
'Expected failure'
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
expect
(
skiaClient
.
imgtestInit
(),
throwsException
,
);
});
test
(
'Only calls init once'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
const
RunInvocation
gitInvocation
=
RunInvocation
(
<
String
>[
'git'
,
'rev-parse'
,
'HEAD'
],
'/flutter'
,
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'init'
,
'--instance'
,
'flutter'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--commit'
,
'1234'
,
'--keys-file'
,
'/workDirectory/keys.json'
,
'--failure-file'
,
'/workDirectory/failures.json'
,
'--passfail'
,
],
null
,
);
process
.
processResults
[
gitInvocation
]
=
ProcessResult
(
1234
,
0
,
'1234'
,
''
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
5678
,
0
,
'5678'
,
''
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
// First call
await
skiaClient
.
imgtestInit
();
// Remove fake process result.
// If the init call is executed again, the fallback process will throw.
process
.
processResults
.
remove
(
goldctlInvocation
);
// Second call
await
skiaClient
.
imgtestInit
();
});
test
(
'Only calls tryjob init once'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
'SWARMING_TASK_ID'
:
'4ae997b50dfd4d11'
,
'LOGDOG_STREAM_PREFIX'
:
'buildbucket/cr-buildbucket.appspot.com/8885996262141582672'
,
'GOLD_TRYJOB'
:
'refs/pull/49815/head'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
const
RunInvocation
gitInvocation
=
RunInvocation
(
<
String
>[
'git'
,
'rev-parse'
,
'HEAD'
],
'/flutter'
,
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'init'
,
'--instance'
,
'flutter'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--commit'
,
'1234'
,
'--keys-file'
,
'/workDirectory/keys.json'
,
'--failure-file'
,
'/workDirectory/failures.json'
,
'--passfail'
,
'--crs'
,
'github'
,
'--patchset_id'
,
'1234'
,
'--changelist'
,
'49815'
,
'--cis'
,
'buildbucket'
,
'--jobid'
,
'8885996262141582672'
,
],
null
,
);
process
.
processResults
[
gitInvocation
]
=
ProcessResult
(
1234
,
0
,
'1234'
,
''
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
5678
,
0
,
'5678'
,
''
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
// First call
await
skiaClient
.
tryjobInit
();
// Remove fake process result.
// If the init call is executed again, the fallback process will throw.
process
.
processResults
.
remove
(
goldctlInvocation
);
// Second call
await
skiaClient
.
tryjobInit
();
});
test
(
'throws for error state from imgtestAdd'
,
()
{
final
File
goldenFile
=
fs
.
file
(
'/workDirectory/temp/golden_file_test.png'
)
..
createSync
(
recursive:
true
);
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'add'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--test-name'
,
'golden_file_test'
,
'--png-file'
,
'/workDirectory/temp/golden_file_test.png'
,
'--passfail'
,
],
null
,
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
1
,
'Expected failure'
,
'Expected failure'
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
expect
(
skiaClient
.
imgtestAdd
(
'golden_file_test'
,
goldenFile
),
throwsException
,
);
});
test
(
'correctly inits tryjob for luci'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
'SWARMING_TASK_ID'
:
'4ae997b50dfd4d11'
,
'LOGDOG_STREAM_PREFIX'
:
'buildbucket/cr-buildbucket.appspot.com/8885996262141582672'
,
'GOLD_TRYJOB'
:
'refs/pull/49815/head'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
final
List
<
String
>
ciArguments
=
skiaClient
.
getCIArguments
();
expect
(
ciArguments
,
equals
(
<
String
>[
'--changelist'
,
'49815'
,
'--cis'
,
'buildbucket'
,
'--jobid'
,
'8885996262141582672'
,
],
),
);
});
test
(
'Creates traceID correctly'
,
()
async
{
String
traceID
;
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
'SWARMING_TASK_ID'
:
'4ae997b50dfd4d11'
,
'LOGDOG_STREAM_PREFIX'
:
'buildbucket/cr-buildbucket.appspot.com/8885996262141582672'
,
'GOLD_TRYJOB'
:
'refs/pull/49815/head'
,
},
operatingSystem:
'linux'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
traceID
=
skiaClient
.
getTraceID
(
'flutter.golden.1'
);
expect
(
traceID
,
equals
(
'ae18c7a6aa48e0685525dfe8fdf79003'
),
);
// Browser
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
'SWARMING_TASK_ID'
:
'4ae997b50dfd4d11'
,
'LOGDOG_STREAM_PREFIX'
:
'buildbucket/cr-buildbucket.appspot.com/8885996262141582672'
,
'GOLD_TRYJOB'
:
'refs/pull/49815/head'
,
'FLUTTER_TEST_BROWSER'
:
'chrome'
,
},
operatingSystem:
'linux'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
traceID
=
skiaClient
.
getTraceID
(
'flutter.golden.1'
);
expect
(
traceID
,
equals
(
'e9d5c296c48e7126808520e9cc191243'
),
);
// Locally - should defer to luci traceID
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
traceID
=
skiaClient
.
getTraceID
(
'flutter.golden.1'
);
expect
(
traceID
,
equals
(
'9968695b9ae78cdb77cbb2be621ca2d6'
),
);
});
test
(
'throws for error state from imgtestAdd'
,
()
{
final
File
goldenFile
=
fs
.
file
(
'/workDirectory/temp/golden_file_test.png'
)
..
createSync
(
recursive:
true
);
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'add'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--test-name'
,
'golden_file_test'
,
'--png-file'
,
'/workDirectory/temp/golden_file_test.png'
,
'--passfail'
,
],
null
,
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
1
,
'Expected failure'
,
'Expected failure'
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
expect
(
skiaClient
.
imgtestAdd
(
'golden_file_test'
,
goldenFile
),
throwsA
(
isA
<
SkiaException
>().
having
((
SkiaException
error
)
=>
error
.
message
,
'message'
,
contains
(
'result-state.json'
),
),
),
);
});
test
(
'throws for error state from tryjobAdd'
,
()
{
final
File
goldenFile
=
fs
.
file
(
'/workDirectory/temp/golden_file_test.png'
)
..
createSync
(
recursive:
true
);
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'add'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--test-name'
,
'golden_file_test'
,
'--png-file'
,
'/workDirectory/temp/golden_file_test.png'
,
'--passfail'
,
],
null
,
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
1
,
'Expected failure'
,
'Expected failure'
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
expect
(
skiaClient
.
tryjobAdd
(
'golden_file_test'
,
goldenFile
),
throwsA
(
isA
<
SkiaException
>().
having
((
SkiaException
error
)
=>
error
.
message
,
'message'
,
contains
(
'result-state.json'
),
),
),
);
});
group
(
'Request Handling'
,
()
{
const
String
expectation
=
'55109a4bed52acc780530f7a9aeff6c0'
;
test
(
'image bytes are processed properly'
,
()
async
{
final
Uri
imageUrl
=
Uri
.
parse
(
'https://flutter-gold.skia.org/img/images/
$expectation
.png'
);
final
FakeHttpClientRequest
fakeImageRequest
=
FakeHttpClientRequest
();
final
FakeHttpImageResponse
fakeImageResponse
=
FakeHttpImageResponse
(
imageResponseTemplate
()
);
fakeHttpClient
.
request
=
fakeImageRequest
;
fakeImageRequest
.
response
=
fakeImageResponse
;
final
List
<
int
>
masterBytes
=
await
skiaClient
.
getImageBytes
(
expectation
);
expect
(
fakeHttpClient
.
lastUri
,
imageUrl
);
expect
(
masterBytes
,
equals
(
_kTestPngBytes
));
});
});
});
group
(
'FlutterGoldenFileComparator'
,
()
{
late
FlutterGoldenFileComparator
comparator
;
...
...
@@ -80,7 +650,7 @@ void main() {
namePrefix:
namePrefix
,
);
await
comparator
.
compare
(
Uint8List
.
fromList
(
kTestPngBytes
),
Uint8List
.
fromList
(
_
kTestPngBytes
),
Uri
.
parse
(
fileName
),
);
expect
(
fakeSkiaClient
.
testNames
.
single
,
'
$namePrefix
.
$libraryName
.
$fileName
'
);
...
...
@@ -105,7 +675,7 @@ void main() {
await
expectLater
(
()
async
{
return
comparator
.
compare
(
Uint8List
.
fromList
(
kTestPngBytes
),
Uint8List
.
fromList
(
_
kTestPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1'
),
);
},
...
...
@@ -124,26 +694,12 @@ void main() {
test
(
'calls init during compare'
,
()
{
expect
(
fakeSkiaClient
.
initCalls
,
0
);
comparator
.
compare
(
Uint8List
.
fromList
(
kTestPngBytes
),
Uint8List
.
fromList
(
_
kTestPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1.png'
),
);
expect
(
fakeSkiaClient
.
initCalls
,
1
);
});
test
(
'Passes on flaky flag to client, resets after comparing'
,
()
{
// Not flaky
expect
(
comparator
.
getAndResetFlakyMode
(),
isFalse
);
comparator
.
enableFlakyMode
();
expect
(
fakeSkiaClient
.
calledWithFlaky
,
0
);
comparator
.
compare
(
Uint8List
.
fromList
(
kTestPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1.png'
),
);
expect
(
fakeSkiaClient
.
calledWithFlaky
,
1
);
// Flaky flag was reset during compare.
expect
(
comparator
.
getAndResetFlakyMode
(),
isFalse
);
});
test
(
'does not call init in during construction'
,
()
{
expect
(
fakeSkiaClient
.
initCalls
,
0
);
FlutterPostSubmitFileComparator
.
fromDefaultComparator
(
...
...
@@ -237,7 +793,7 @@ void main() {
await
expectLater
(
()
async
{
return
comparator
.
compare
(
Uint8List
.
fromList
(
kTestPngBytes
),
Uint8List
.
fromList
(
_
kTestPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1'
),
);
},
...
...
@@ -256,27 +812,12 @@ void main() {
test
(
'calls init during compare'
,
()
{
expect
(
fakeSkiaClient
.
tryInitCalls
,
0
);
comparator
.
compare
(
Uint8List
.
fromList
(
kTestPngBytes
),
Uint8List
.
fromList
(
_
kTestPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1.png'
),
);
expect
(
fakeSkiaClient
.
tryInitCalls
,
1
);
});
test
(
'Passes on flaky flag to client, resets after comparing'
,
()
{
// Not flaky
expect
(
comparator
.
getAndResetFlakyMode
(),
isFalse
);
comparator
.
enableFlakyMode
();
expect
(
fakeSkiaClient
.
calledWithFlaky
,
0
);
comparator
.
compare
(
Uint8List
.
fromList
(
kTestPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1.png'
),
);
// Init & add were called with flaky set.
expect
(
fakeSkiaClient
.
calledWithFlaky
,
1
);
// Flaky flag was reset during compare.
expect
(
comparator
.
getAndResetFlakyMode
(),
isFalse
);
});
test
(
'does not call init in during construction'
,
()
{
expect
(
fakeSkiaClient
.
tryInitCalls
,
0
);
FlutterPostSubmitFileComparator
.
fromDefaultComparator
(
...
...
@@ -366,19 +907,6 @@ void main() {
});
group
(
'Skipping'
,
()
{
test
(
'Resets flaky flag after comparing'
,
()
{
// Not flaky
expect
(
comparator
.
getAndResetFlakyMode
(),
isFalse
);
// Set flaky
comparator
.
enableFlakyMode
();
comparator
.
compare
(
Uint8List
.
fromList
(
kTestPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1.png'
),
);
// Flaky flag was reset during compare.
expect
(
comparator
.
getAndResetFlakyMode
(),
isFalse
);
});
group
(
'correctly determines testing environment'
,
()
{
test
(
'returns true on Cirrus builds'
,
()
{
platform
=
FakePlatform
(
...
...
@@ -443,7 +971,7 @@ void main() {
const
String
hash
=
'55109a4bed52acc780530f7a9aeff6c0'
;
fakeSkiaClient
.
expectationForTestValues
[
'flutter.golden_test.1'
]
=
hash
;
fakeSkiaClient
.
imageBytesValues
[
hash
]
=
kTestPngBytes
;
fakeSkiaClient
.
imageBytesValues
[
hash
]
=
_
kTestPngBytes
;
fakeSkiaClient
.
cleanTestNameValues
[
'library.flutter.golden_test.1.png'
]
=
'flutter.golden_test.1'
;
});
...
...
@@ -451,7 +979,7 @@ void main() {
await
expectLater
(
()
async
{
return
comparator
.
compare
(
Uint8List
.
fromList
(
kTestPngBytes
),
Uint8List
.
fromList
(
_
kTestPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1'
),
);
},
...
...
@@ -470,25 +998,13 @@ void main() {
test
(
'passes when bytes match'
,
()
async
{
expect
(
await
comparator
.
compare
(
Uint8List
.
fromList
(
kTestPngBytes
),
Uint8List
.
fromList
(
_
kTestPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1.png'
),
),
isTrue
,
);
});
test
(
'Passes when flaky'
,
()
{
// Not flaky
expect
(
comparator
.
getAndResetFlakyMode
(),
isFalse
);
comparator
.
enableFlakyMode
();
comparator
.
compare
(
Uint8List
.
fromList
(
kTestPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1.png'
),
);
// Flaky flag was reset during compare.
expect
(
comparator
.
getAndResetFlakyMode
(),
isFalse
);
});
test
(
'returns FlutterSkippingGoldenFileComparator when network connection is unavailable'
,
()
async
{
final
FakeDirectory
fakeDirectory
=
FakeDirectory
();
fakeDirectory
.
existsSyncValue
=
true
;
...
...
@@ -517,3 +1033,156 @@ void main() {
});
});
}
@immutable
class
RunInvocation
{
const
RunInvocation
(
this
.
command
,
this
.
workingDirectory
);
final
List
<
String
>
command
;
final
String
?
workingDirectory
;
@override
int
get
hashCode
=>
Object
.
hash
(
Object
.
hashAll
(
command
),
workingDirectory
);
bool
_commandEquals
(
List
<
String
>
other
)
{
if
(
other
==
command
)
{
return
true
;
}
if
(
other
.
length
!=
command
.
length
)
{
return
false
;
}
for
(
int
index
=
0
;
index
<
other
.
length
;
index
+=
1
)
{
if
(
other
[
index
]
!=
command
[
index
])
{
return
false
;
}
}
return
true
;
}
@override
bool
operator
==(
Object
other
)
{
if
(
other
.
runtimeType
!=
runtimeType
)
{
return
false
;
}
return
other
is
RunInvocation
&&
_commandEquals
(
other
.
command
)
&&
other
.
workingDirectory
==
workingDirectory
;
}
@override
String
toString
()
=>
'
$command
(
$workingDirectory
)'
;
}
class
FakeProcessManager
extends
Fake
implements
ProcessManager
{
Map
<
RunInvocation
,
ProcessResult
>
processResults
=
<
RunInvocation
,
ProcessResult
>{};
/// Used if [processResults] does not contain a matching invocation.
ProcessResult
?
fallbackProcessResult
;
final
List
<
String
?>
workingDirectories
=
<
String
?>[];
@override
Future
<
ProcessResult
>
run
(
List
<
Object
>
command
,
{
String
?
workingDirectory
,
Map
<
String
,
String
>?
environment
,
bool
includeParentEnvironment
=
true
,
bool
runInShell
=
false
,
Encoding
?
stdoutEncoding
=
systemEncoding
,
Encoding
?
stderrEncoding
=
systemEncoding
,
})
async
{
workingDirectories
.
add
(
workingDirectory
);
final
ProcessResult
?
result
=
processResults
[
RunInvocation
(
command
.
cast
<
String
>(),
workingDirectory
)];
if
(
result
==
null
&&
fallbackProcessResult
==
null
)
{
printOnFailure
(
'ProcessManager.run was called with
$command
(
$workingDirectory
) unexpectedly -
$processResults
.'
);
fail
(
'See above.'
);
}
return
result
??
fallbackProcessResult
!;
}
}
// See also dev/automated_tests/flutter_test/flutter_gold_test.dart
class
FakeSkiaGoldClient
extends
Fake
implements
SkiaGoldClient
{
Map
<
String
,
String
>
expectationForTestValues
=
<
String
,
String
>{};
Exception
?
getExpectationForTestThrowable
;
@override
Future
<
String
>
getExpectationForTest
(
String
testName
)
async
{
if
(
getExpectationForTestThrowable
!=
null
)
{
throw
getExpectationForTestThrowable
!;
}
return
expectationForTestValues
[
testName
]
??
''
;
}
@override
Future
<
void
>
auth
()
async
{}
final
List
<
String
>
testNames
=
<
String
>[];
int
initCalls
=
0
;
@override
Future
<
void
>
imgtestInit
()
async
=>
initCalls
+=
1
;
@override
Future
<
bool
>
imgtestAdd
(
String
testName
,
File
goldenFile
)
async
{
testNames
.
add
(
testName
);
return
true
;
}
int
tryInitCalls
=
0
;
@override
Future
<
void
>
tryjobInit
()
async
=>
tryInitCalls
+=
1
;
@override
Future
<
bool
>
tryjobAdd
(
String
testName
,
File
goldenFile
)
async
=>
true
;
Map
<
String
,
List
<
int
>>
imageBytesValues
=
<
String
,
List
<
int
>>{};
@override
Future
<
List
<
int
>>
getImageBytes
(
String
imageHash
)
async
=>
imageBytesValues
[
imageHash
]!;
Map
<
String
,
String
>
cleanTestNameValues
=
<
String
,
String
>{};
@override
String
cleanTestName
(
String
fileName
)
=>
cleanTestNameValues
[
fileName
]
??
''
;
}
class
FakeLocalFileComparator
extends
Fake
implements
LocalFileComparator
{
@override
late
Uri
basedir
;
}
class
FakeDirectory
extends
Fake
implements
Directory
{
late
bool
existsSyncValue
;
@override
bool
existsSync
()
=>
existsSyncValue
;
@override
late
Uri
uri
;
}
class
FakeHttpClient
extends
Fake
implements
HttpClient
{
late
Uri
lastUri
;
late
FakeHttpClientRequest
request
;
@override
Future
<
HttpClientRequest
>
getUrl
(
Uri
url
)
async
{
lastUri
=
url
;
return
request
;
}
}
class
FakeHttpClientRequest
extends
Fake
implements
HttpClientRequest
{
late
FakeHttpImageResponse
response
;
@override
Future
<
HttpClientResponse
>
close
()
async
{
return
response
;
}
}
class
FakeHttpImageResponse
extends
Fake
implements
HttpClientResponse
{
FakeHttpImageResponse
(
this
.
response
);
final
List
<
List
<
int
>>
response
;
@override
Future
<
void
>
forEach
(
void
Function
(
List
<
int
>
element
)
action
)
async
{
response
.
forEach
(
action
);
}
}
packages/flutter_goldens/test/
utils/
json_templates.dart
→
packages/flutter_goldens/test/json_templates.dart
View file @
a84e369b
File moved
packages/flutter_goldens/test/skia_client_test.dart
deleted
100644 → 0
View file @
a1432a9c
// Copyright 2014 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.
// See also dev/automated_tests/flutter_test/flutter_gold_test.dart
import
'dart:convert'
;
import
'dart:io'
hide
Directory
;
import
'package:file/file.dart'
;
import
'package:file/memory.dart'
;
import
'package:flutter_goldens/src/flutter_goldens_io.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:platform/platform.dart'
;
import
'utils/fakes.dart'
;
import
'utils/json_templates.dart'
;
const
String
_kFlutterRoot
=
'/flutter'
;
void
main
(
)
{
late
SkiaGoldClient
skiaClient
;
late
Directory
workDirectory
;
late
MemoryFileSystem
fs
;
late
FakePlatform
platform
;
late
FakeProcessManager
process
;
late
FakeHttpClient
fakeHttpClient
;
setUp
(()
{
fs
=
MemoryFileSystem
();
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
},
operatingSystem:
'macos'
);
process
=
FakeProcessManager
();
fakeHttpClient
=
FakeHttpClient
();
fs
.
directory
(
_kFlutterRoot
).
createSync
(
recursive:
true
);
});
setUp
(()
{
workDirectory
=
fs
.
directory
(
'/workDirectory'
)
..
createSync
(
recursive:
true
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
});
test
(
'web HTML test'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'GOLDCTL'
:
'goldctl'
,
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'FLUTTER_TEST_BROWSER'
:
'Chrome'
,
'FLUTTER_WEB_RENDERER'
:
'html'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
final
File
goldenFile
=
fs
.
file
(
'/workDirectory/temp/golden_file_test.png'
)
..
createSync
(
recursive:
true
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'add'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--test-name'
,
'golden_file_test'
,
'--png-file'
,
'/workDirectory/temp/golden_file_test.png'
,
'--passfail'
,
'--add-test-optional-key'
,
'image_matching_algorithm:fuzzy'
,
'--add-test-optional-key'
,
'fuzzy_max_different_pixels:20'
,
'--add-test-optional-key'
,
'fuzzy_pixel_delta_threshold:4'
,
],
null
,
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
0
,
''
,
''
);
final
Map
<
String
,
dynamic
>
keys
=
<
String
,
dynamic
>{
'Platform'
:
'macos-browser'
,
'CI'
:
'luci'
,
'markedFlaky'
:
'false'
,
'Browser'
:
'Chrome'
,
'WebRenderer'
:
'html'
,
};
expect
(
skiaClient
.
getKeysJSON
(),
json
.
encode
(
keys
),
);
expect
(
await
skiaClient
.
imgtestAdd
(
'golden_file_test.png'
,
goldenFile
),
isTrue
,
);
});
test
(
'isFlaky sets right args - img test'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'GOLDCTL'
:
'goldctl'
,
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'FLUTTER_TEST_BROWSER'
:
'Chrome'
,
'FLUTTER_WEB_RENDERER'
:
'html'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
final
File
goldenFile
=
fs
.
file
(
'/workDirectory/temp/golden_file_test.png'
)
..
createSync
(
recursive:
true
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'add'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--test-name'
,
'golden_file_test'
,
'--png-file'
,
'/workDirectory/temp/golden_file_test.png'
,
'--passfail'
,
'--add-test-optional-key'
,
'image_matching_algorithm:fuzzy'
,
'--add-test-optional-key'
,
'fuzzy_max_different_pixels:1000000000'
,
'--add-test-optional-key'
,
'fuzzy_pixel_delta_threshold:1020'
,
],
null
,
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
0
,
''
,
''
);
final
Map
<
String
,
dynamic
>
keys
=
<
String
,
dynamic
>{
'Platform'
:
'macos-browser'
,
'CI'
:
'luci'
,
'markedFlaky'
:
'false'
,
'Browser'
:
'Chrome'
,
'WebRenderer'
:
'html'
,
};
expect
(
skiaClient
.
getKeysJSON
(),
json
.
encode
(
keys
),
);
expect
(
await
skiaClient
.
imgtestAdd
(
'golden_file_test.png'
,
goldenFile
,
isFlaky:
true
),
isTrue
,
);
});
test
(
'isFlaky sets right args - try job'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'GOLDCTL'
:
'goldctl'
,
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'FLUTTER_TEST_BROWSER'
:
'Chrome'
,
'FLUTTER_WEB_RENDERER'
:
'html'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
final
File
goldenFile
=
fs
.
file
(
'/workDirectory/temp/golden_file_test.png'
)
..
createSync
(
recursive:
true
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'add'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--test-name'
,
'golden_file_test'
,
'--png-file'
,
'/workDirectory/temp/golden_file_test.png'
,
'--add-test-optional-key'
,
'image_matching_algorithm:fuzzy'
,
'--add-test-optional-key'
,
'fuzzy_max_different_pixels:1000000000'
,
'--add-test-optional-key'
,
'fuzzy_pixel_delta_threshold:1020'
,
],
null
,
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
0
,
''
,
''
);
final
Map
<
String
,
dynamic
>
keys
=
<
String
,
dynamic
>{
'Platform'
:
'macos-browser'
,
'CI'
:
'luci'
,
'markedFlaky'
:
'false'
,
'Browser'
:
'Chrome'
,
'WebRenderer'
:
'html'
,
};
expect
(
skiaClient
.
getKeysJSON
(),
json
.
encode
(
keys
),
);
await
skiaClient
.
tryjobAdd
(
'golden_file_test.png'
,
goldenFile
,
isFlaky:
true
);
});
test
(
'web CanvasKit test'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'GOLDCTL'
:
'goldctl'
,
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'FLUTTER_TEST_BROWSER'
:
'Chrome'
,
'FLUTTER_WEB_RENDERER'
:
'canvaskit'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
final
File
goldenFile
=
fs
.
file
(
'/workDirectory/temp/golden_file_test.png'
)
..
createSync
(
recursive:
true
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'add'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--test-name'
,
'golden_file_test'
,
'--png-file'
,
'/workDirectory/temp/golden_file_test.png'
,
'--passfail'
,
],
null
,
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
0
,
''
,
''
);
final
Map
<
String
,
dynamic
>
keys
=
<
String
,
dynamic
>{
'Platform'
:
'macos-browser'
,
'CI'
:
'luci'
,
'markedFlaky'
:
'false'
,
'Browser'
:
'Chrome'
,
'WebRenderer'
:
'canvaskit'
,
};
expect
(
skiaClient
.
getKeysJSON
(),
json
.
encode
(
keys
),
);
expect
(
await
skiaClient
.
imgtestAdd
(
'golden_file_test.png'
,
goldenFile
),
isTrue
,
);
});
test
(
'auth performs minimal work if already authorized'
,
()
async
{
final
File
authFile
=
fs
.
file
(
'/workDirectory/temp/auth_opt.json'
)
..
createSync
(
recursive:
true
);
authFile
.
writeAsStringSync
(
authTemplate
());
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
0
,
''
,
''
);
await
skiaClient
.
auth
();
expect
(
process
.
workingDirectories
,
isEmpty
);
});
test
(
'gsutil is checked when authorization file is present'
,
()
async
{
final
File
authFile
=
fs
.
file
(
'/workDirectory/temp/auth_opt.json'
)
..
createSync
(
recursive:
true
);
authFile
.
writeAsStringSync
(
authTemplate
(
gsutil:
true
));
expect
(
await
skiaClient
.
clientIsAuthorized
(),
isFalse
,
);
});
test
(
'throws for error state from auth'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLD_SERVICE_ACCOUNT'
:
'Service Account'
,
'GOLDCTL'
:
'goldctl'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
expect
(
skiaClient
.
auth
(),
throwsException
,
);
});
test
(
'throws for error state from init'
,
()
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
const
RunInvocation
gitInvocation
=
RunInvocation
(
<
String
>[
'git'
,
'rev-parse'
,
'HEAD'
],
'/flutter'
,
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'init'
,
'--instance'
,
'flutter'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--commit'
,
'12345678'
,
'--keys-file'
,
'/workDirectory/keys.json'
,
'--failure-file'
,
'/workDirectory/failures.json'
,
'--passfail'
,
],
null
,
);
process
.
processResults
[
gitInvocation
]
=
ProcessResult
(
12345678
,
0
,
'12345678'
,
''
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
1
,
'Expected failure'
,
'Expected failure'
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
expect
(
skiaClient
.
imgtestInit
(),
throwsException
,
);
});
test
(
'Only calls init once'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
const
RunInvocation
gitInvocation
=
RunInvocation
(
<
String
>[
'git'
,
'rev-parse'
,
'HEAD'
],
'/flutter'
,
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'init'
,
'--instance'
,
'flutter'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--commit'
,
'1234'
,
'--keys-file'
,
'/workDirectory/keys.json'
,
'--failure-file'
,
'/workDirectory/failures.json'
,
'--passfail'
,
],
null
,
);
process
.
processResults
[
gitInvocation
]
=
ProcessResult
(
1234
,
0
,
'1234'
,
''
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
5678
,
0
,
'5678'
,
''
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
// First call
await
skiaClient
.
imgtestInit
();
// Remove fake process result.
// If the init call is executed again, the fallback process will throw.
process
.
processResults
.
remove
(
goldctlInvocation
);
// Second call
await
skiaClient
.
imgtestInit
();
});
test
(
'Only calls tryjob init once'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
'SWARMING_TASK_ID'
:
'4ae997b50dfd4d11'
,
'LOGDOG_STREAM_PREFIX'
:
'buildbucket/cr-buildbucket.appspot.com/8885996262141582672'
,
'GOLD_TRYJOB'
:
'refs/pull/49815/head'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
const
RunInvocation
gitInvocation
=
RunInvocation
(
<
String
>[
'git'
,
'rev-parse'
,
'HEAD'
],
'/flutter'
,
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'init'
,
'--instance'
,
'flutter'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--commit'
,
'1234'
,
'--keys-file'
,
'/workDirectory/keys.json'
,
'--failure-file'
,
'/workDirectory/failures.json'
,
'--passfail'
,
'--crs'
,
'github'
,
'--patchset_id'
,
'1234'
,
'--changelist'
,
'49815'
,
'--cis'
,
'buildbucket'
,
'--jobid'
,
'8885996262141582672'
,
],
null
,
);
process
.
processResults
[
gitInvocation
]
=
ProcessResult
(
1234
,
0
,
'1234'
,
''
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
5678
,
0
,
'5678'
,
''
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
// First call
await
skiaClient
.
tryjobInit
();
// Remove fake process result.
// If the init call is executed again, the fallback process will throw.
process
.
processResults
.
remove
(
goldctlInvocation
);
// Second call
await
skiaClient
.
tryjobInit
();
});
test
(
'throws for error state from imgtestAdd'
,
()
{
final
File
goldenFile
=
fs
.
file
(
'/workDirectory/temp/golden_file_test.png'
)
..
createSync
(
recursive:
true
);
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'add'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--test-name'
,
'golden_file_test'
,
'--png-file'
,
'/workDirectory/temp/golden_file_test.png'
,
'--passfail'
,
],
null
,
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
1
,
'Expected failure'
,
'Expected failure'
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
expect
(
skiaClient
.
imgtestAdd
(
'golden_file_test'
,
goldenFile
),
throwsException
,
);
});
test
(
'correctly inits tryjob for luci'
,
()
async
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
'SWARMING_TASK_ID'
:
'4ae997b50dfd4d11'
,
'LOGDOG_STREAM_PREFIX'
:
'buildbucket/cr-buildbucket.appspot.com/8885996262141582672'
,
'GOLD_TRYJOB'
:
'refs/pull/49815/head'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
final
List
<
String
>
ciArguments
=
skiaClient
.
getCIArguments
();
expect
(
ciArguments
,
equals
(
<
String
>[
'--changelist'
,
'49815'
,
'--cis'
,
'buildbucket'
,
'--jobid'
,
'8885996262141582672'
,
],
),
);
});
test
(
'Creates traceID correctly'
,
()
async
{
String
traceID
;
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
'SWARMING_TASK_ID'
:
'4ae997b50dfd4d11'
,
'LOGDOG_STREAM_PREFIX'
:
'buildbucket/cr-buildbucket.appspot.com/8885996262141582672'
,
'GOLD_TRYJOB'
:
'refs/pull/49815/head'
,
},
operatingSystem:
'linux'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
traceID
=
skiaClient
.
getTraceID
(
'flutter.golden.1'
);
expect
(
traceID
,
equals
(
'ae18c7a6aa48e0685525dfe8fdf79003'
),
);
// Browser
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
'SWARMING_TASK_ID'
:
'4ae997b50dfd4d11'
,
'LOGDOG_STREAM_PREFIX'
:
'buildbucket/cr-buildbucket.appspot.com/8885996262141582672'
,
'GOLD_TRYJOB'
:
'refs/pull/49815/head'
,
'FLUTTER_TEST_BROWSER'
:
'chrome'
,
},
operatingSystem:
'linux'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
traceID
=
skiaClient
.
getTraceID
(
'flutter.golden.1'
);
expect
(
traceID
,
equals
(
'e9d5c296c48e7126808520e9cc191243'
),
);
// Locally - should defer to luci traceID
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
traceID
=
skiaClient
.
getTraceID
(
'flutter.golden.1'
);
expect
(
traceID
,
equals
(
'9968695b9ae78cdb77cbb2be621ca2d6'
),
);
});
test
(
'throws for error state from imgtestAdd'
,
()
{
final
File
goldenFile
=
fs
.
file
(
'/workDirectory/temp/golden_file_test.png'
)
..
createSync
(
recursive:
true
);
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'add'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--test-name'
,
'golden_file_test'
,
'--png-file'
,
'/workDirectory/temp/golden_file_test.png'
,
'--passfail'
,
],
null
,
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
1
,
'Expected failure'
,
'Expected failure'
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
expect
(
skiaClient
.
imgtestAdd
(
'golden_file_test'
,
goldenFile
),
throwsA
(
isA
<
SkiaException
>().
having
((
SkiaException
error
)
=>
error
.
message
,
'message'
,
contains
(
'result-state.json'
),
),
),
);
});
test
(
'throws for error state from tryjobAdd'
,
()
{
final
File
goldenFile
=
fs
.
file
(
'/workDirectory/temp/golden_file_test.png'
)
..
createSync
(
recursive:
true
);
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'GOLDCTL'
:
'goldctl'
,
},
operatingSystem:
'macos'
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
fakeHttpClient
,
);
const
RunInvocation
goldctlInvocation
=
RunInvocation
(
<
String
>[
'goldctl'
,
'imgtest'
,
'add'
,
'--work-dir'
,
'/workDirectory/temp'
,
'--test-name'
,
'golden_file_test'
,
'--png-file'
,
'/workDirectory/temp/golden_file_test.png'
,
'--passfail'
,
],
null
,
);
process
.
processResults
[
goldctlInvocation
]
=
ProcessResult
(
123
,
1
,
'Expected failure'
,
'Expected failure'
);
process
.
fallbackProcessResult
=
ProcessResult
(
123
,
1
,
'Fallback failure'
,
'Fallback failure'
);
expect
(
skiaClient
.
tryjobAdd
(
'golden_file_test'
,
goldenFile
),
throwsA
(
isA
<
SkiaException
>().
having
((
SkiaException
error
)
=>
error
.
message
,
'message'
,
contains
(
'result-state.json'
),
),
),
);
});
group
(
'Request Handling'
,
()
{
const
String
expectation
=
'55109a4bed52acc780530f7a9aeff6c0'
;
test
(
'image bytes are processed properly'
,
()
async
{
final
Uri
imageUrl
=
Uri
.
parse
(
'https://flutter-gold.skia.org/img/images/
$expectation
.png'
);
final
FakeHttpClientRequest
fakeImageRequest
=
FakeHttpClientRequest
();
final
FakeHttpImageResponse
fakeImageResponse
=
FakeHttpImageResponse
(
imageResponseTemplate
()
);
fakeHttpClient
.
request
=
fakeImageRequest
;
fakeImageRequest
.
response
=
fakeImageResponse
;
final
List
<
int
>
masterBytes
=
await
skiaClient
.
getImageBytes
(
expectation
);
expect
(
fakeHttpClient
.
lastUri
,
imageUrl
);
expect
(
masterBytes
,
equals
(
kTestPngBytes
));
});
});
}
packages/flutter_goldens/test/utils/fakes.dart
deleted
100644 → 0
View file @
a1432a9c
// Copyright 2014 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.
// See also dev/automated_tests/flutter_test/flutter_gold_test.dart
import
'dart:convert'
;
import
'dart:io'
hide
Directory
;
import
'package:file/file.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter_goldens/src/flaky_goldens.dart'
;
import
'package:flutter_goldens/src/flutter_goldens_io.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:process/process.dart'
;
// 1x1 transparent pixel
const
List
<
int
>
kTestPngBytes
=
<
int
>[
137
,
80
,
78
,
71
,
13
,
10
,
26
,
10
,
0
,
0
,
0
,
13
,
73
,
72
,
68
,
82
,
0
,
0
,
0
,
1
,
0
,
0
,
0
,
1
,
8
,
6
,
0
,
0
,
0
,
31
,
21
,
196
,
137
,
0
,
0
,
0
,
11
,
73
,
68
,
65
,
84
,
120
,
1
,
99
,
97
,
0
,
2
,
0
,
0
,
25
,
0
,
5
,
144
,
240
,
54
,
245
,
0
,
0
,
0
,
0
,
73
,
69
,
78
,
68
,
174
,
66
,
96
,
130
,
];
@immutable
class
RunInvocation
{
const
RunInvocation
(
this
.
command
,
this
.
workingDirectory
);
final
List
<
String
>
command
;
final
String
?
workingDirectory
;
@override
int
get
hashCode
=>
Object
.
hash
(
Object
.
hashAll
(
command
),
workingDirectory
);
bool
_commandEquals
(
List
<
String
>
other
)
{
if
(
other
==
command
)
{
return
true
;
}
if
(
other
.
length
!=
command
.
length
)
{
return
false
;
}
for
(
int
index
=
0
;
index
<
other
.
length
;
index
+=
1
)
{
if
(
other
[
index
]
!=
command
[
index
])
{
return
false
;
}
}
return
true
;
}
@override
bool
operator
==(
Object
other
)
{
if
(
other
.
runtimeType
!=
runtimeType
)
{
return
false
;
}
return
other
is
RunInvocation
&&
_commandEquals
(
other
.
command
)
&&
other
.
workingDirectory
==
workingDirectory
;
}
@override
String
toString
()
=>
'
$command
(
$workingDirectory
)'
;
}
class
FakeProcessManager
extends
Fake
implements
ProcessManager
{
Map
<
RunInvocation
,
ProcessResult
>
processResults
=
<
RunInvocation
,
ProcessResult
>{};
/// Used if [processResults] does not contain a matching invocation.
ProcessResult
?
fallbackProcessResult
;
final
List
<
String
?>
workingDirectories
=
<
String
?>[];
@override
Future
<
ProcessResult
>
run
(
List
<
Object
>
command
,
{
String
?
workingDirectory
,
Map
<
String
,
String
>?
environment
,
bool
includeParentEnvironment
=
true
,
bool
runInShell
=
false
,
Encoding
?
stdoutEncoding
=
systemEncoding
,
Encoding
?
stderrEncoding
=
systemEncoding
,
})
async
{
workingDirectories
.
add
(
workingDirectory
);
final
ProcessResult
?
result
=
processResults
[
RunInvocation
(
command
.
cast
<
String
>(),
workingDirectory
)];
if
(
result
==
null
&&
fallbackProcessResult
==
null
)
{
printOnFailure
(
'ProcessManager.run was called with
$command
(
$workingDirectory
) unexpectedly -
$processResults
.'
);
fail
(
'See above.'
);
}
return
result
??
fallbackProcessResult
!;
}
}
// See also dev/automated_tests/flutter_test/flutter_gold_test.dart
class
FakeSkiaGoldClient
extends
Fake
implements
SkiaGoldClient
{
Map
<
String
,
String
>
expectationForTestValues
=
<
String
,
String
>{};
Exception
?
getExpectationForTestThrowable
;
@override
Future
<
String
>
getExpectationForTest
(
String
testName
)
async
{
if
(
getExpectationForTestThrowable
!=
null
)
{
throw
getExpectationForTestThrowable
!;
}
return
expectationForTestValues
[
testName
]
??
''
;
}
@override
Future
<
void
>
auth
()
async
{}
final
List
<
String
>
testNames
=
<
String
>[];
int
initCalls
=
0
;
int
calledWithFlaky
=
0
;
@override
Future
<
void
>
imgtestInit
({
bool
isFlaky
=
false
})
async
{
initCalls
+=
1
;
if
(
isFlaky
)
{
calledWithFlaky
+=
1
;
}
}
@override
Future
<
bool
>
imgtestAdd
(
String
testName
,
File
goldenFile
,
{
bool
isFlaky
=
false
})
async
{
testNames
.
add
(
testName
);
if
(
isFlaky
)
{
calledWithFlaky
+=
1
;
}
return
true
;
}
int
tryInitCalls
=
0
;
@override
Future
<
void
>
tryjobInit
({
bool
isFlaky
=
false
})
async
{
tryInitCalls
+=
1
;
if
(
isFlaky
)
{
calledWithFlaky
+=
1
;
}
}
@override
Future
<
bool
>
tryjobAdd
(
String
testName
,
File
goldenFile
,
{
bool
isFlaky
=
false
})
async
{
if
(
isFlaky
)
{
calledWithFlaky
+=
1
;
}
return
true
;
}
Map
<
String
,
List
<
int
>>
imageBytesValues
=
<
String
,
List
<
int
>>{};
@override
Future
<
List
<
int
>>
getImageBytes
(
String
imageHash
)
async
=>
imageBytesValues
[
imageHash
]!;
Map
<
String
,
String
>
cleanTestNameValues
=
<
String
,
String
>{};
@override
String
cleanTestName
(
String
fileName
)
=>
cleanTestNameValues
[
fileName
]
??
''
;
}
class
FakeFlakyLocalFileComparator
extends
FakeLocalFileComparator
with
FlakyGoldenMixin
{}
class
FakeLocalFileComparator
extends
Fake
implements
LocalFileComparator
{
@override
late
Uri
basedir
;
@override
Uri
getTestUri
(
Uri
key
,
int
?
version
)
=>
Uri
.
parse
(
'fake'
);
@override
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
=>
true
;
}
class
FakeDirectory
extends
Fake
implements
Directory
{
late
bool
existsSyncValue
;
@override
bool
existsSync
()
=>
existsSyncValue
;
@override
late
Uri
uri
;
}
class
FakeHttpClient
extends
Fake
implements
HttpClient
{
late
Uri
lastUri
;
late
FakeHttpClientRequest
request
;
@override
Future
<
HttpClientRequest
>
getUrl
(
Uri
url
)
async
{
lastUri
=
url
;
return
request
;
}
}
class
FakeHttpClientRequest
extends
Fake
implements
HttpClientRequest
{
late
FakeHttpImageResponse
response
;
@override
Future
<
HttpClientResponse
>
close
()
async
{
return
response
;
}
}
class
FakeHttpImageResponse
extends
Fake
implements
HttpClientResponse
{
FakeHttpImageResponse
(
this
.
response
);
final
List
<
List
<
int
>>
response
;
@override
Future
<
void
>
forEach
(
void
Function
(
List
<
int
>
element
)
action
)
async
{
response
.
forEach
(
action
);
}
}
packages/flutter_goldens_client/lib/skia_client.dart
View file @
a84e369b
...
...
@@ -8,7 +8,6 @@ import 'dart:io' as io;
import
'package:crypto/crypto.dart'
;
import
'package:file/file.dart'
;
import
'package:file/local.dart'
;
import
'package:meta/meta.dart'
;
import
'package:path/path.dart'
as
path
;
import
'package:platform/platform.dart'
;
import
'package:process/process.dart'
;
...
...
@@ -139,7 +138,7 @@ class SkiaGoldClient {
/// The `imgtest` command collects and uploads test results to the Skia Gold
/// backend, the `init` argument initializes the current test. Used by the
/// [FlutterPostSubmitFileComparator].
Future
<
void
>
imgtestInit
(
{
bool
isFlaky
=
false
}
)
async
{
Future
<
void
>
imgtestInit
()
async
{
// This client has already been initialized
if
(
_initialized
)
{
return
;
...
...
@@ -148,7 +147,7 @@ class SkiaGoldClient {
final
File
keys
=
workDirectory
.
childFile
(
'keys.json'
);
final
File
failures
=
workDirectory
.
childFile
(
'failures.json'
);
await
keys
.
writeAsString
(
getKeysJSON
(
isFlaky:
isFlaky
));
await
keys
.
writeAsString
(
_getKeysJSON
(
));
await
failures
.
create
();
final
String
commitHash
=
await
_getCurrentCommit
();
...
...
@@ -200,7 +199,7 @@ class SkiaGoldClient {
///
/// The [testName] and [goldenFile] parameters reference the current
/// comparison being evaluated by the [FlutterPostSubmitFileComparator].
Future
<
bool
>
imgtestAdd
(
String
testName
,
File
goldenFile
,
{
bool
isFlaky
=
false
}
)
async
{
Future
<
bool
>
imgtestAdd
(
String
testName
,
File
goldenFile
)
async
{
final
List
<
String
>
imgtestCommand
=
<
String
>[
_goldctl
,
'imgtest'
,
'add'
,
...
...
@@ -210,7 +209,7 @@ class SkiaGoldClient {
'--test-name'
,
cleanTestName
(
testName
),
'--png-file'
,
goldenFile
.
path
,
'--passfail'
,
...
_getPixelMatchingArguments
(
isFlaky:
isFlaky
),
...
_getPixelMatchingArguments
(),
];
final
io
.
ProcessResult
result
=
await
process
.
run
(
imgtestCommand
);
...
...
@@ -260,7 +259,7 @@ class SkiaGoldClient {
/// The `imgtest` command collects and uploads test results to the Skia Gold
/// backend, the `init` argument initializes the current tryjob. Used by the
/// [FlutterPreSubmitFileComparator].
Future
<
void
>
tryjobInit
(
{
bool
isFlaky
=
false
}
)
async
{
Future
<
void
>
tryjobInit
()
async
{
// This client has already been initialized
if
(
_tryjobInitialized
)
{
return
;
...
...
@@ -269,7 +268,7 @@ class SkiaGoldClient {
final
File
keys
=
workDirectory
.
childFile
(
'keys.json'
);
final
File
failures
=
workDirectory
.
childFile
(
'failures.json'
);
await
keys
.
writeAsString
(
getKeysJSON
(
isFlaky:
isFlaky
));
await
keys
.
writeAsString
(
_getKeysJSON
(
));
await
failures
.
create
();
final
String
commitHash
=
await
_getCurrentCommit
();
...
...
@@ -324,7 +323,7 @@ class SkiaGoldClient {
///
/// The [testName] and [goldenFile] parameters reference the current
/// comparison being evaluated by the [FlutterPreSubmitFileComparator].
Future
<
void
>
tryjobAdd
(
String
testName
,
File
goldenFile
,
{
bool
isFlaky
=
false
}
)
async
{
Future
<
void
>
tryjobAdd
(
String
testName
,
File
goldenFile
)
async
{
final
List
<
String
>
imgtestCommand
=
<
String
>[
_goldctl
,
'imgtest'
,
'add'
,
...
...
@@ -333,7 +332,7 @@ class SkiaGoldClient {
.
path
,
'--test-name'
,
cleanTestName
(
testName
),
'--png-file'
,
goldenFile
.
path
,
...
_getPixelMatchingArguments
(
isFlaky:
isFlaky
),
...
_getPixelMatchingArguments
(),
];
final
io
.
ProcessResult
result
=
await
process
.
run
(
imgtestCommand
);
...
...
@@ -363,42 +362,6 @@ class SkiaGoldClient {
}
}
List
<
String
>
_getPixelMatchingArguments
({
required
bool
isFlaky
})
{
if
(
isFlaky
)
{
return
_getFlakyPixelMatchingArguments
();
}
else
{
return
_getNormalPixelMatchingArguments
();
}
}
List
<
String
>
_getFlakyPixelMatchingArguments
()
{
// The algorithm to be used when matching images. The available options are:
// - "fuzzy": Allows for customizing the thresholds of pixel differences.
// - "sobel": Same as "fuzzy" but performs edge detection before performing
// a fuzzy match.
const
String
algorithm
=
'fuzzy'
;
// The number of pixels in this image that are allowed to differ from the
// baseline.
//
// The chosen number - 1 billion - indicates that a flaky test should pass
// no matter how many pixels are different from the master golden.
const
int
maxDifferentPixels
=
1000
*
1000
*
1000
;
// The maximum acceptable difference per pixel.
//
// The chosen number - 1020 - is the maximum supported pixel delta and
// indicates that a flaky test should pass no matter how far the new pixels
// deviate from the master golden.
const
int
pixelDeltaThreshold
=
1020
;
return
<
String
>[
'--add-test-optional-key'
,
'image_matching_algorithm:
$algorithm
'
,
'--add-test-optional-key'
,
'fuzzy_max_different_pixels:
$maxDifferentPixels
'
,
'--add-test-optional-key'
,
'fuzzy_pixel_delta_threshold:
$pixelDeltaThreshold
'
,
];
}
// Constructs arguments for `goldctl` for controlling how pixels are compared.
//
// For AOT and CanvasKit exact pixel matching is used. For the HTML renderer
...
...
@@ -406,7 +369,7 @@ class SkiaGoldClient {
// because Chromium cannot exactly reproduce the same golden on all computers.
// It seems to depend on the hardware/OS/driver combination. However, those
// differences are very small (typically not noticeable to human eye).
List
<
String
>
_get
Normal
PixelMatchingArguments
()
{
List
<
String
>
_getPixelMatchingArguments
()
{
// Only use fuzzy pixel matching in the HTML renderer.
if
(!
_isBrowserTest
||
_isBrowserCanvasKitTest
)
{
return
const
<
String
>[];
...
...
@@ -522,17 +485,17 @@ class SkiaGoldClient {
/// Currently, the only key value pairs being tracked is the platform the
/// image was rendered on, and for web tests, the browser the image was
/// rendered on.
@visibleForTesting
String
getKeysJSON
({
bool
isFlaky
=
false
})
{
String
_getKeysJSON
()
{
final
Map
<
String
,
dynamic
>
keys
=
<
String
,
dynamic
>{
'Platform'
:
platform
.
operatingSystem
,
'CI'
:
'luci'
,
'markedFlaky'
:
isFlaky
.
toString
(),
};
if
(
_isBrowserTest
)
{
keys
[
'Browser'
]
=
_browserKey
;
keys
[
'Platform'
]
=
'
${keys['Platform']}
-browser'
;
keys
[
'WebRenderer'
]
=
_isBrowserCanvasKitTest
?
'canvaskit'
:
'html'
;
if
(
_isBrowserCanvasKitTest
)
{
keys
[
'WebRenderer'
]
=
'canvaskit'
;
}
}
return
json
.
encode
(
keys
);
}
...
...
packages/flutter_test/lib/src/_goldens_web.dart
View file @
a84e369b
...
...
@@ -41,12 +41,12 @@ Future<ComparisonResult> compareLists(List<int> test, List<int> master) async {
/// * [matchesGoldenFile], the function from [flutter_test] that invokes the
/// comparator.
class
DefaultWebGoldenComparator
extends
WebGoldenComparator
{
/// Creates a new [DefaultWebGoldenComparator] for the specified [test
Uri
].
/// Creates a new [DefaultWebGoldenComparator] for the specified [test
File
].
///
/// Golden file keys will be interpreted as file paths relative to the
/// directory in which [test
Uri
] resides.
/// directory in which [test
File
] resides.
///
/// The [test
Uri
] URL must represent a file.
/// The [test
File
] URL must represent a file.
DefaultWebGoldenComparator
(
this
.
testUri
);
/// The test file currently being executed.
...
...
packages/flutter_test/lib/src/goldens.dart
View file @
a84e369b
...
...
@@ -238,14 +238,6 @@ set webGoldenComparator(WebGoldenComparator value) {
_webGoldenComparator
=
value
;
}
/// The URI of the test file currently being executed.
///
/// This variable is populated by the Flutter Tool automatically.
///
/// Golden file keys will be interpreted as file paths relative to the directory
/// in which this file resides.
late
Uri
webTestUri
;
/// Whether golden files should be automatically updated during tests rather
/// than compared to the image bytes recorded by the tests.
///
...
...
packages/flutter_tools/lib/src/test/flutter_web_goldens.dart
View file @
a84e369b
...
...
@@ -98,14 +98,14 @@ class TestGoldenComparator {
return
_processManager
.
start
(
command
,
environment:
environment
);
}
Future
<
String
?>
compareGoldens
(
Uri
testUri
,
Uint8List
bytes
,
Uri
goldenKey
,
bool
?
updateGoldens
,
Map
<
String
,
dynamic
>?
customProperties
)
async
{
Future
<
String
?>
compareGoldens
(
Uri
testUri
,
Uint8List
bytes
,
Uri
goldenKey
,
bool
?
updateGoldens
)
async
{
final
File
imageFile
=
await
(
await
tempDir
.
createTemp
(
'image'
)).
childFile
(
'image'
).
writeAsBytes
(
bytes
);
final
TestGoldenComparatorProcess
?
process
=
await
_processForTestFile
(
testUri
);
if
(
process
==
null
)
{
return
'process was null'
;
}
process
.
sendCommand
(
imageFile
,
goldenKey
,
updateGoldens
,
customProperties
);
process
.
sendCommand
(
imageFile
,
goldenKey
,
updateGoldens
);
final
Map
<
String
,
dynamic
>
result
=
await
process
.
getResponse
();
...
...
@@ -152,13 +152,11 @@ class TestGoldenComparatorProcess {
await
process
.
exitCode
;
}
void
sendCommand
(
File
imageFile
,
Uri
?
goldenKey
,
bool
?
updateGoldens
,
Map
<
String
,
dynamic
>?
customProperties
)
{
void
sendCommand
(
File
imageFile
,
Uri
?
goldenKey
,
bool
?
updateGoldens
)
{
final
Object
command
=
jsonEncode
(<
String
,
dynamic
>{
'imageFile'
:
imageFile
.
path
,
'key'
:
goldenKey
.
toString
(),
'update'
:
updateGoldens
,
if
(
customProperties
!=
null
)
'customProperties'
:
customProperties
,
});
_logger
.
printTrace
(
'Preparing to send command:
$command
'
);
process
.
stdin
.
writeln
(
command
);
...
...
@@ -170,22 +168,7 @@ class TestGoldenComparatorProcess {
return
streamIterator
.
current
;
}
/// Generates the source code for the comparator process for the test file.
///
/// If a test configuation exists for the tested package, uses its
/// implementation. Otherwise, uses the default implementation.
static
String
generateBootstrap
(
File
testFile
,
Uri
testUri
,
{
required
Logger
logger
})
{
final
File
?
webTestConfigFile
=
findWebTestConfigFile
(
testFile
,
logger
);
if
(
webTestConfigFile
!=
null
)
{
return
_generateBootstrapWithWebTestConfig
(
webTestConfigFile
,
testFile
,
testUri
);
}
else
{
return
_generateBasicBootstrap
(
testFile
,
testUri
,
logger:
logger
);
}
}
// Generates the bootstrap used by tests that either don't have a test
// configuration file, or don't have a `flutter_web_test_config.dart`.
static
String
_generateBasicBootstrap
(
File
testFile
,
Uri
testUri
,
{
required
Logger
logger
})
{
final
File
?
testConfigFile
=
findTestConfigFile
(
testFile
,
logger
);
// Generate comparator process for the file.
return
'''
...
...
@@ -228,19 +211,6 @@ void main() async {
}
}
${testConfigFile != null ? '}
);' : ''}
}
''';
}
// Generates the bootstrap used by tests that have a `flutter_web_test_config.dart`.
static String _generateBootstrapWithWebTestConfig(File webTestConfigFile, File testFile, Uri testUri) {
return '''
import 'package:flutter_test/flutter_test.dart';
import '
${Uri.file(webTestConfigFile.path)}
' as web_test_config;
void main() async {
final String testUri = '
$testUri
';
goldenFileComparator = LocalFileComparator(Uri.parse(testUri));
await web_test_config.startWebTestHostConfiguration(testUri);
}
''';
}
...
...
packages/flutter_tools/lib/src/test/flutter_web_platform.dart
View file @
a84e369b
...
...
@@ -343,9 +343,10 @@ class FlutterWebPlatform extends PlatformPlugin {
}
Future
<
shelf
.
Response
>
_goldenFileHandler
(
shelf
.
Request
request
)
async
{
if
(
request
.
method
==
'POST'
&&
request
.
url
.
path
.
contains
(
'flutter_goldens'
))
{
final
String
requestJson
=
await
request
.
readAsString
();
final
Map
<
String
,
Object
?>
body
=
json
.
decode
(
requestJson
)
as
Map
<
String
,
Object
?>;
if
(
request
.
url
.
path
.
contains
(
'flutter_goldens'
))
{
final
Map
<
String
,
Object
?>
body
=
json
.
decode
(
await
request
.
readAsString
())
as
Map
<
String
,
Object
?>;
final
Uri
goldenKey
=
Uri
.
parse
(
body
[
'key'
]!
as
String
);
final
Uri
testUri
=
Uri
.
parse
(
body
[
'testUri'
]!
as
String
);
final
num
width
=
body
[
'width'
]!
as
num
;
final
num
height
=
body
[
'height'
]!
as
num
;
Uint8List
bytes
;
...
...
@@ -382,10 +383,7 @@ class FlutterWebPlatform extends PlatformPlugin {
return
shelf
.
Response
.
ok
(
'Unknown error, bytes is null'
);
}
final
Uri
goldenKey
=
Uri
.
parse
(
body
[
'key'
]!
as
String
);
final
Uri
testUri
=
Uri
.
parse
(
body
[
'testUri'
]!
as
String
);
final
Map
<
String
,
dynamic
>?
customProperties
=
body
[
'customProperties'
]
as
Map
<
String
,
dynamic
>?;
final
String
?
errorMessage
=
await
_testGoldenComparator
.
compareGoldens
(
testUri
,
bytes
,
goldenKey
,
updateGoldens
,
customProperties
);
final
String
?
errorMessage
=
await
_testGoldenComparator
.
compareGoldens
(
testUri
,
bytes
,
goldenKey
,
updateGoldens
);
return
shelf
.
Response
.
ok
(
errorMessage
??
'true'
);
}
else
{
return
shelf
.
Response
.
notFound
(
'Not Found'
);
...
...
packages/flutter_tools/lib/src/test/test_config.dart
View file @
a84e369b
...
...
@@ -9,36 +9,23 @@ import '../base/logger.dart';
/// test harness if it exists in the project directory hierarchy.
const
String
_kTestConfigFileName
=
'flutter_test_config.dart'
;
/// The name of the web test configuration file that will be discovered by the
/// test harness if it exists in the project directory hierarchy.
const
String
_kWebTestConfigFileName
=
'flutter_web_test_config.dart'
;
/// The name of the file that signals the root of the project and that will
/// cause the test harness to stop scanning for configuration files.
const
String
_kProjectRootSentinel
=
'pubspec.yaml'
;
/// Find the `flutter_test_config.dart` file for a specific test file.
File
?
findTestConfigFile
(
File
testFile
,
Logger
logger
)
{
return
_findConfigFile
(
testFile
,
_kTestConfigFileName
,
logger
);
}
/// Find the `flutter_web_test_config.dart` file for a specific test file.
File
?
findWebTestConfigFile
(
File
testFile
,
Logger
logger
)
{
return
_findConfigFile
(
testFile
,
_kWebTestConfigFileName
,
logger
);
}
File
?
_findConfigFile
(
File
testFile
,
String
configFileName
,
Logger
logger
)
{
File
?
testConfigFile
;
Directory
directory
=
testFile
.
parent
;
while
(
directory
.
path
!=
directory
.
parent
.
path
)
{
final
File
configFile
=
directory
.
childFile
(
c
onfigFileName
);
final
File
configFile
=
directory
.
childFile
(
_kTestC
onfigFileName
);
if
(
configFile
.
existsSync
())
{
logger
.
printTrace
(
'Discovered
$
c
onfigFileName
in
${directory.path}
'
);
logger
.
printTrace
(
'Discovered
$
_kTestC
onfigFileName
in
${directory.path}
'
);
testConfigFile
=
configFile
;
break
;
}
if
(
directory
.
childFile
(
_kProjectRootSentinel
).
existsSync
())
{
logger
.
printTrace
(
'Stopping scan for
$
c
onfigFileName
; '
logger
.
printTrace
(
'Stopping scan for
$
_kTestC
onfigFileName
; '
'found project root at
${directory.path}
'
);
break
;
}
...
...
packages/flutter_tools/lib/src/web/bootstrap.dart
View file @
a84e369b
...
...
@@ -224,8 +224,7 @@ String generateTestEntrypoint({
Future
<
void
>
main
()
async
{
ui
.
debugEmulateFlutterTesterEnvironment
=
true
;
await
ui
.
webOnlyInitializePlatform
();
webTestUri
=
Uri
.
parse
(
'
${Uri.file(absolutePath)}
'
);
webGoldenComparator
=
DefaultWebGoldenComparator
(
webTestUri
);
webGoldenComparator
=
DefaultWebGoldenComparator
(
Uri
.
parse
(
'
${Uri.file(absolutePath)}
'
));
(
ui
.
window
as
dynamic
).
debugOverrideDevicePixelRatio
(
3.0
);
(
ui
.
window
as
dynamic
).
webOnlyDebugPhysicalSizeOverride
=
const
ui
.
Size
(
2400
,
1800
);
...
...
packages/flutter_tools/test/general.shard/web/golden_comparator_process_test.dart
View file @
a84e369b
...
...
@@ -41,13 +41,13 @@ void main() {
final
MemoryIOSink
ioSink
=
mockProcess
.
stdin
as
MemoryIOSink
;
final
TestGoldenComparatorProcess
process
=
TestGoldenComparatorProcess
(
mockProcess
,
logger:
BufferLogger
.
test
());
process
.
sendCommand
(
imageFile
,
goldenKey
,
false
,
<
String
,
String
>{
'additional data'
:
'data'
}
);
process
.
sendCommand
(
imageFile
,
goldenKey
,
false
);
final
Map
<
String
,
dynamic
>
response
=
await
process
.
getResponse
();
final
String
stringToStdin
=
ioSink
.
getAndClear
();
expect
(
response
,
expectedResponse
);
expect
(
stringToStdin
,
'{"imageFile":"test_image_file","key":"file://golden_key/","update":false
,"customProperties":{"additional data":"data"}
}
\n
'
);
expect
(
stringToStdin
,
'{"imageFile":"test_image_file","key":"file://golden_key/","update":false}
\n
'
);
});
testWithoutContext
(
'can handle multiple requests'
,
()
async
{
...
...
@@ -64,21 +64,18 @@ void main() {
final
MemoryIOSink
ioSink
=
mockProcess
.
stdin
as
MemoryIOSink
;
final
TestGoldenComparatorProcess
process
=
TestGoldenComparatorProcess
(
mockProcess
,
logger:
BufferLogger
.
test
());
process
.
sendCommand
(
imageFile
,
goldenKey
,
false
,
null
);
process
.
sendCommand
(
imageFile
,
goldenKey
,
false
);
final
Map
<
String
,
dynamic
>
response1
=
await
process
.
getResponse
();
process
.
sendCommand
(
imageFile2
,
goldenKey2
,
true
,
null
);
process
.
sendCommand
(
imageFile2
,
goldenKey2
,
true
);
final
Map
<
String
,
dynamic
>
response2
=
await
process
.
getResponse
();
final
String
stringToStdin
=
ioSink
.
getAndClear
();
expect
(
response1
,
expectedResponse1
);
expect
(
response2
,
expectedResponse2
);
expect
(
stringToStdin
,
'{"imageFile":"test_image_file","key":"file://golden_key/","update":false}
\n
'
'{"imageFile":"second_test_image_file","key":"file://second_golden_key/","update":true}
\n
'
);
expect
(
stringToStdin
,
'{"imageFile":"test_image_file","key":"file://golden_key/","update":false}
\n
{"imageFile":"second_test_image_file","key":"file://second_golden_key/","update":true}
\n
'
);
});
testWithoutContext
(
'ignores anything that does not look like JSON'
,
()
async
{
...
...
@@ -97,7 +94,7 @@ Other JSON data after the initial data
final
MemoryIOSink
ioSink
=
mockProcess
.
stdin
as
MemoryIOSink
;
final
TestGoldenComparatorProcess
process
=
TestGoldenComparatorProcess
(
mockProcess
,
logger:
BufferLogger
.
test
());
process
.
sendCommand
(
imageFile
,
goldenKey
,
false
,
null
);
process
.
sendCommand
(
imageFile
,
goldenKey
,
false
);
final
Map
<
String
,
dynamic
>
response
=
await
process
.
getResponse
();
final
String
stringToStdin
=
ioSink
.
getAndClear
();
...
...
packages/flutter_tools/test/general.shard/web/golden_comparator_test.dart
View file @
a84e369b
...
...
@@ -61,7 +61,7 @@ void main() {
webRenderer:
WebRendererMode
.
html
,
);
final
String
?
result
=
await
comparator
.
compareGoldens
(
testUri
,
imageBytes
,
goldenKey
,
false
,
null
);
final
String
?
result
=
await
comparator
.
compareGoldens
(
testUri
,
imageBytes
,
goldenKey
,
false
);
expect
(
result
,
null
);
});
...
...
@@ -90,7 +90,7 @@ void main() {
webRenderer:
WebRendererMode
.
canvaskit
,
);
final
String
?
result
=
await
comparator
.
compareGoldens
(
testUri
,
imageBytes
,
goldenKey
,
false
,
null
);
final
String
?
result
=
await
comparator
.
compareGoldens
(
testUri
,
imageBytes
,
goldenKey
,
false
);
expect
(
result
,
'some message'
);
});
...
...
@@ -123,10 +123,10 @@ void main() {
webRenderer:
WebRendererMode
.
html
,
);
final
String
?
result1
=
await
comparator
.
compareGoldens
(
testUri
,
imageBytes
,
goldenKey
,
false
,
null
);
final
String
?
result1
=
await
comparator
.
compareGoldens
(
testUri
,
imageBytes
,
goldenKey
,
false
);
expect
(
result1
,
'some message'
);
final
String
?
result2
=
await
comparator
.
compareGoldens
(
testUri
,
imageBytes
,
goldenKey2
,
false
,
null
);
final
String
?
result2
=
await
comparator
.
compareGoldens
(
testUri
,
imageBytes
,
goldenKey2
,
false
);
expect
(
result2
,
'some other message'
);
});
...
...
@@ -168,10 +168,10 @@ void main() {
webRenderer:
WebRendererMode
.
canvaskit
,
);
final
String
?
result1
=
await
comparator
.
compareGoldens
(
testUri
,
imageBytes
,
goldenKey
,
false
,
null
);
final
String
?
result1
=
await
comparator
.
compareGoldens
(
testUri
,
imageBytes
,
goldenKey
,
false
);
expect
(
result1
,
'some message'
);
final
String
?
result2
=
await
comparator
.
compareGoldens
(
testUri2
,
imageBytes
,
goldenKey2
,
false
,
null
);
final
String
?
result2
=
await
comparator
.
compareGoldens
(
testUri2
,
imageBytes
,
goldenKey2
,
false
);
expect
(
result2
,
'some other message'
);
});
...
...
@@ -203,7 +203,7 @@ void main() {
webRenderer:
WebRendererMode
.
html
,
);
final
String
?
result
=
await
comparator
.
compareGoldens
(
testUri
,
imageBytes
,
goldenKey
,
false
,
null
);
final
String
?
result
=
await
comparator
.
compareGoldens
(
testUri
,
imageBytes
,
goldenKey
,
false
);
expect
(
result
,
null
);
await
comparator
.
close
();
...
...
packages/flutter_tools/test/integration.shard/forbidden_imports_test.dart
View file @
a84e369b
...
...
@@ -117,7 +117,7 @@ void main() {
final
List
<
String
>
allowedPaths
=
<
String
>[
fileSystem
.
path
.
join
(
flutterTools
,
'lib'
,
'src'
,
'test'
,
'flutter_platform.dart'
),
fileSystem
.
path
.
join
(
flutterTools
,
'lib'
,
'src'
,
'test'
,
'flutter_web_platform.dart'
),
fileSystem
.
path
.
join
(
flutterTools
,
'lib'
,
'src'
,
'test'
,
'
flutter_goldens
.dart'
),
fileSystem
.
path
.
join
(
flutterTools
,
'lib'
,
'src'
,
'test'
,
'
test_wrapper
.dart'
),
];
bool
isNotAllowed
(
FileSystemEntity
entity
)
=>
allowedPaths
.
every
((
String
path
)
=>
path
!=
entity
.
path
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment