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
7bc02037
Unverified
Commit
7bc02037
authored
Oct 25, 2019
by
Kate Lovett
Committed by
GitHub
Oct 25, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Re-land Local & Pre-Submit Support for Skia Gold (#43371)
parent
4bd5eb59
Changes
40
Show whitespace changes
Inline
Side-by-side
Showing
40 changed files
with
1494 additions
and
1043 deletions
+1494
-1043
goldens.version
bin/internal/goldens.version
+0
-1
activity_indicator_test.dart
packages/flutter/test/cupertino/activity_indicator_test.dart
+2
-2
date_picker_test.dart
packages/flutter/test/cupertino/date_picker_test.dart
+4
-16
nav_bar_test.dart
packages/flutter/test/cupertino/nav_bar_test.dart
+2
-8
segmented_control_test.dart
packages/flutter/test/cupertino/segmented_control_test.dart
+2
-8
switch_test.dart
packages/flutter/test/cupertino/switch_test.dart
+5
-20
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+3
-12
bottom_app_bar_test.dart
packages/flutter/test/material/bottom_app_bar_test.dart
+2
-8
bottom_app_bar_theme_test.dart
...ages/flutter/test/material/bottom_app_bar_theme_test.dart
+1
-4
bottom_navigation_bar_test.dart
...ges/flutter/test/material/bottom_navigation_bar_test.dart
+1
-4
card_theme_test.dart
packages/flutter/test/material/card_theme_test.dart
+1
-4
dialog_theme_test.dart
packages/flutter/test/material/dialog_theme_test.dart
+1
-4
dropdown_test.dart
packages/flutter/test/material/dropdown_test.dart
+2
-8
floating_action_button_test.dart
...es/flutter/test/material/floating_action_button_test.dart
+1
-4
input_decorator_test.dart
packages/flutter/test/material/input_decorator_test.dart
+2
-8
material_test.dart
packages/flutter/test/material/material_test.dart
+2
-8
radio_test.dart
packages/flutter/test/material/radio_test.dart
+1
-4
tab_bar_theme_test.dart
packages/flutter/test/material/tab_bar_theme_test.dart
+4
-16
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+3
-12
continous_rectangle_border_test.dart
...lutter/test/painting/continous_rectangle_border_test.dart
+3
-12
gradient_test.dart
packages/flutter/test/painting/gradient_test.dart
+4
-1
localized_fonts_test.dart
packages/flutter/test/rendering/localized_fonts_test.dart
+3
-12
backdrop_filter_test.dart
packages/flutter/test/widgets/backdrop_filter_test.dart
+1
-4
clip_test.dart
packages/flutter/test/widgets/clip_test.dart
+13
-52
color_filter_test.dart
packages/flutter/test/widgets/color_filter_test.dart
+2
-8
editable_text_cursor_test.dart
packages/flutter/test/widgets/editable_text_cursor_test.dart
+3
-12
invert_colors_test.dart
packages/flutter/test/widgets/invert_colors_test.dart
+2
-8
list_wheel_scroll_view_test.dart
...ges/flutter/test/widgets/list_wheel_scroll_view_test.dart
+2
-8
opacity_test.dart
packages/flutter/test/widgets/opacity_test.dart
+1
-4
physical_model_test.dart
packages/flutter/test/widgets/physical_model_test.dart
+1
-4
shadow_test.dart
packages/flutter/test/widgets/shadow_test.dart
+6
-24
text_golden_test.dart
packages/flutter/test/widgets/text_golden_test.dart
+23
-92
widget_inspector_test.dart
packages/flutter/test/widgets/widget_inspector_test.dart
+25
-100
flutter_goldens.dart
packages/flutter_goldens/lib/flutter_goldens.dart
+339
-121
flutter_goldens_test.dart
packages/flutter_goldens/test/flutter_goldens_test.dart
+495
-128
json_templates.dart
packages/flutter_goldens/test/json_templates.dart
+202
-0
client.dart
packages/flutter_goldens_client/lib/client.dart
+0
-212
skia_client.dart
packages/flutter_goldens_client/lib/skia_client.dart
+272
-63
_goldens_io.dart
packages/flutter_test/lib/src/_goldens_io.dart
+52
-22
matchers.dart
packages/flutter_test/lib/src/matchers.dart
+6
-5
No files found.
bin/internal/goldens.version
deleted
100644 → 0
View file @
4bd5eb59
65bd07204149c4f7612bbf179cf088a2d69ca549
packages/flutter/test/cupertino/activity_indicator_test.dart
View file @
7bc02037
...
...
@@ -45,7 +45,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
key
),
matchesGoldenFile
(
'activityIndicator.paused.light.png'
,
version:
0
),
matchesGoldenFile
(
'activityIndicator.paused.light.png'
),
);
await
tester
.
pumpWidget
(
...
...
@@ -65,7 +65,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
key
),
matchesGoldenFile
(
'activityIndicator.paused.dark.png'
,
version:
0
),
matchesGoldenFile
(
'activityIndicator.paused.dark.png'
),
);
});
...
...
packages/flutter/test/cupertino/date_picker_test.dart
View file @
7bc02037
...
...
@@ -922,10 +922,7 @@ void main() {
await
expectLater
(
find
.
byType
(
CupertinoDatePicker
),
matchesGoldenFile
(
'date_picker_test.datetime.initial.png'
,
version:
2
,
),
matchesGoldenFile
(
'date_picker_test.datetime.initial.png'
),
);
// Slightly drag the hour component to make the current hour off-center.
...
...
@@ -934,10 +931,7 @@ void main() {
await
expectLater
(
find
.
byType
(
CupertinoDatePicker
),
matchesGoldenFile
(
'date_picker_test.datetime.drag.png'
,
version:
2
,
),
matchesGoldenFile
(
'date_picker_test.datetime.drag.png'
),
);
});
});
...
...
@@ -971,10 +965,7 @@ void main() {
await
expectLater
(
find
.
byType
(
CupertinoTimerPicker
),
matchesGoldenFile
(
'timer_picker_test.datetime.initial.png'
,
version:
1
,
),
matchesGoldenFile
(
'timer_picker_test.datetime.initial.png'
),
);
// Slightly drag the minute component to make the current minute off-center.
...
...
@@ -983,10 +974,7 @@ void main() {
await
expectLater
(
find
.
byType
(
CupertinoTimerPicker
),
matchesGoldenFile
(
'timer_picker_test.datetime.drag.png'
,
version:
1
,
),
matchesGoldenFile
(
'timer_picker_test.datetime.drag.png'
),
);
});
...
...
packages/flutter/test/cupertino/nav_bar_test.dart
View file @
7bc02037
...
...
@@ -820,10 +820,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
last
,
matchesGoldenFile
(
'nav_bar_test.standard_title.png'
,
version:
2
,
),
matchesGoldenFile
(
'nav_bar_test.standard_title.png'
),
);
},
);
...
...
@@ -854,10 +851,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
last
,
matchesGoldenFile
(
'nav_bar_test.large_title.png'
,
version:
2
,
),
matchesGoldenFile
(
'nav_bar_test.large_title.png'
),
);
},
);
...
...
packages/flutter/test/cupertino/segmented_control_test.dart
View file @
7bc02037
...
...
@@ -1414,10 +1414,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'segmented_control_test.0.png'
,
version:
0
,
),
matchesGoldenFile
(
'segmented_control_test.0.png'
),
);
});
...
...
@@ -1455,10 +1452,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'segmented_control_test.1.png'
,
version:
0
,
),
matchesGoldenFile
(
'segmented_control_test.1.png'
),
);
});
}
packages/flutter/test/cupertino/switch_test.dart
View file @
7bc02037
...
...
@@ -542,10 +542,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
switchKey
),
matchesGoldenFile
(
'switch.tap.off.png'
,
version:
1
,
),
matchesGoldenFile
(
'switch.tap.off.png'
),
);
await
tester
.
tap
(
find
.
byKey
(
switchKey
));
...
...
@@ -556,19 +553,13 @@ void main() {
await
tester
.
pump
(
const
Duration
(
milliseconds:
60
));
await
expectLater
(
find
.
byKey
(
switchKey
),
matchesGoldenFile
(
'switch.tap.turningOn.png'
,
version:
1
,
),
matchesGoldenFile
(
'switch.tap.turningOn.png'
),
);
await
tester
.
pumpAndSettle
();
await
expectLater
(
find
.
byKey
(
switchKey
),
matchesGoldenFile
(
'switch.tap.on.png'
,
version:
1
,
),
matchesGoldenFile
(
'switch.tap.on.png'
),
);
});
...
...
@@ -604,10 +595,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
switchKey
),
matchesGoldenFile
(
'switch.tap.off.dark.png'
,
version:
0
,
),
matchesGoldenFile
(
'switch.tap.off.dark.png'
),
);
await
tester
.
tap
(
find
.
byKey
(
switchKey
));
...
...
@@ -616,10 +604,7 @@ void main() {
await
tester
.
pumpAndSettle
();
await
expectLater
(
find
.
byKey
(
switchKey
),
matchesGoldenFile
(
'switch.tap.on.dark.png'
,
version:
0
,
),
matchesGoldenFile
(
'switch.tap.on.dark.png'
),
);
});
}
packages/flutter/test/cupertino/text_field_test.dart
View file @
7bc02037
...
...
@@ -503,10 +503,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
const
ValueKey
<
int
>(
1
)),
matchesGoldenFile
(
'text_field_cursor_test.cupertino.0.png'
,
version:
3
,
),
matchesGoldenFile
(
'text_field_cursor_test.cupertino.0.png'
),
);
});
...
...
@@ -536,10 +533,7 @@ void main() {
debugDefaultTargetPlatformOverride
=
null
;
await
expectLater
(
find
.
byKey
(
const
ValueKey
<
int
>(
1
)),
matchesGoldenFile
(
'text_field_cursor_test.cupertino.1.png'
,
version:
3
,
),
matchesGoldenFile
(
'text_field_cursor_test.cupertino.1.png'
),
);
});
...
...
@@ -3042,10 +3036,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
const
ValueKey
<
int
>(
1
)),
matchesGoldenFile
(
'text_field_test.disabled.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_field_test.disabled.png'
),
);
});
...
...
packages/flutter/test/material/bottom_app_bar_test.dart
View file @
7bc02037
...
...
@@ -71,19 +71,13 @@ void main() {
await
pump
(
FloatingActionButtonLocation
.
endDocked
);
await
expectLater
(
find
.
byKey
(
key
),
matchesGoldenFile
(
'bottom_app_bar.custom_shape.1.png'
,
version:
null
,
),
matchesGoldenFile
(
'bottom_app_bar.custom_shape.1.png'
),
);
await
pump
(
FloatingActionButtonLocation
.
centerDocked
);
await
tester
.
pumpAndSettle
();
await
expectLater
(
find
.
byKey
(
key
),
matchesGoldenFile
(
'bottom_app_bar.custom_shape.2.png'
,
version:
null
,
),
matchesGoldenFile
(
'bottom_app_bar.custom_shape.2.png'
),
);
},
skip:
isBrowser
);
...
...
packages/flutter/test/material/bottom_app_bar_theme_test.dart
View file @
7bc02037
...
...
@@ -80,10 +80,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
_painterKey
),
matchesGoldenFile
(
'bottom_app_bar_theme.custom_shape.png'
,
version:
null
,
),
matchesGoldenFile
(
'bottom_app_bar_theme.custom_shape.png'
),
);
},
skip:
isBrowser
);
...
...
packages/flutter/test/material/bottom_navigation_bar_test.dart
View file @
7bc02037
...
...
@@ -1426,10 +1426,7 @@ void main() {
await
tester
.
pump
(
const
Duration
(
milliseconds:
30
));
await
expectLater
(
find
.
byType
(
BottomNavigationBar
),
matchesGoldenFile
(
'bottom_navigation_bar.shifting_transition.
$pump
.png'
,
version:
2
,
),
matchesGoldenFile
(
'bottom_navigation_bar.shifting_transition.
$pump
.png'
),
);
}
},
skip:
isBrowser
);
...
...
packages/flutter/test/material/card_theme_test.dart
View file @
7bc02037
...
...
@@ -137,10 +137,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
painterKey
),
matchesGoldenFile
(
'card_theme.custom_shape.png'
,
version:
null
,
),
matchesGoldenFile
(
'card_theme.custom_shape.png'
),
);
},
skip:
isBrowser
);
}
...
...
packages/flutter/test/material/dialog_theme_test.dart
View file @
7bc02037
...
...
@@ -130,10 +130,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
_painterKey
),
matchesGoldenFile
(
'dialog_theme.dialog_with_custom_border.png'
,
version:
null
,
),
matchesGoldenFile
(
'dialog_theme.dialog_with_custom_border.png'
),
);
},
skip:
isBrowser
);
...
...
packages/flutter/test/material/dropdown_test.dart
View file @
7bc02037
...
...
@@ -227,10 +227,7 @@ void main() {
assert
(
tester
.
renderObject
(
buttonFinder
).
attached
);
await
expectLater
(
find
.
ancestor
(
of:
buttonFinder
,
matching:
find
.
byType
(
RepaintBoundary
)).
first
,
matchesGoldenFile
(
'dropdown_test.default.png'
,
version:
0
,
),
matchesGoldenFile
(
'dropdown_test.default.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -242,10 +239,7 @@ void main() {
assert
(
tester
.
renderObject
(
buttonFinder
).
attached
);
await
expectLater
(
find
.
ancestor
(
of:
buttonFinder
,
matching:
find
.
byType
(
RepaintBoundary
)).
first
,
matchesGoldenFile
(
'dropdown_test.expanded.png'
,
version:
0
,
),
matchesGoldenFile
(
'dropdown_test.expanded.png'
),
);
},
skip:
isBrowser
);
...
...
packages/flutter/test/material/floating_action_button_test.dart
View file @
7bc02037
...
...
@@ -740,10 +740,7 @@ void main() {
await
tester
.
pump
(
const
Duration
(
milliseconds:
1000
));
await
expectLater
(
find
.
byKey
(
key
),
matchesGoldenFile
(
'floating_action_button_test.clip.png'
,
version:
2
,
),
matchesGoldenFile
(
'floating_action_button_test.clip.png'
),
);
});
...
...
packages/flutter/test/material/input_decorator_test.dart
View file @
7bc02037
...
...
@@ -2960,19 +2960,13 @@ void main() {
await
tester
.
pumpWidget
(
buildFrame
(
TextDirection
.
ltr
));
await
expectLater
(
find
.
byType
(
InputDecorator
),
matchesGoldenFile
(
'input_decorator.outline_icon_label.ltr.png'
,
version:
null
,
),
matchesGoldenFile
(
'input_decorator.outline_icon_label.ltr.png'
),
);
await
tester
.
pumpWidget
(
buildFrame
(
TextDirection
.
rtl
));
await
expectLater
(
find
.
byType
(
InputDecorator
),
matchesGoldenFile
(
'input_decorator.outline_icon_label.rtl.png'
,
version:
null
,
),
matchesGoldenFile
(
'input_decorator.outline_icon_label.rtl.png'
),
);
},
);
...
...
packages/flutter/test/material/material_test.dart
View file @
7bc02037
...
...
@@ -715,10 +715,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
painterKey
),
matchesGoldenFile
(
'material.border_paint_above.png'
,
version:
null
,
),
matchesGoldenFile
(
'material.border_paint_above.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -758,10 +755,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
painterKey
),
matchesGoldenFile
(
'material.border_paint_below.png'
,
version:
null
,
),
matchesGoldenFile
(
'material.border_paint_below.png'
),
);
},
skip:
isBrowser
);
});
...
...
packages/flutter/test/material/radio_test.dart
View file @
7bc02037
...
...
@@ -276,10 +276,7 @@ void main() {
await
tester
.
pumpAndSettle
();
await
expectLater
(
find
.
byKey
(
painterKey
),
matchesGoldenFile
(
'radio.ink_ripple.png'
,
version:
null
,
),
matchesGoldenFile
(
'radio.ink_ripple.png'
),
);
},
skip:
isBrowser
);
}
...
...
packages/flutter/test/material/tab_bar_theme_test.dart
View file @
7bc02037
...
...
@@ -267,10 +267,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
_painterKey
),
matchesGoldenFile
(
'tab_bar_theme.tab_indicator_size_tab.png'
,
version:
null
,
),
matchesGoldenFile
(
'tab_bar_theme.tab_indicator_size_tab.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -281,10 +278,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
_painterKey
),
matchesGoldenFile
(
'tab_bar_theme.tab_indicator_size_label.png'
,
version:
null
,
),
matchesGoldenFile
(
'tab_bar_theme.tab_indicator_size_label.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -300,10 +294,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
_painterKey
),
matchesGoldenFile
(
'tab_bar_theme.custom_tab_indicator.png'
,
version:
null
,
),
matchesGoldenFile
(
'tab_bar_theme.custom_tab_indicator.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -319,10 +310,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
_painterKey
),
matchesGoldenFile
(
'tab_bar_theme.beveled_rect_indicator.png'
,
version:
null
,
),
matchesGoldenFile
(
'tab_bar_theme.beveled_rect_indicator.png'
),
);
},
skip:
isBrowser
);
}
packages/flutter/test/material/text_field_test.dart
View file @
7bc02037
...
...
@@ -412,10 +412,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
const
ValueKey
<
int
>(
1
)),
matchesGoldenFile
(
'text_field_cursor_test.material.0.png'
,
version:
0
,
),
matchesGoldenFile
(
'text_field_cursor_test.material.0.png'
),
);
});
...
...
@@ -444,10 +441,7 @@ void main() {
debugDefaultTargetPlatformOverride
=
null
;
await
expectLater
(
find
.
byKey
(
const
ValueKey
<
int
>(
1
)),
matchesGoldenFile
(
'text_field_cursor_test.material.1.png'
,
version:
0
,
),
matchesGoldenFile
(
'text_field_cursor_test.material.1.png'
),
);
});
...
...
@@ -498,10 +492,7 @@ void main() {
await
expectLater
(
// The toolbar exists in the Overlay above the MaterialApp.
find
.
byType
(
Overlay
),
matchesGoldenFile
(
'text_field_opacity_test.0.png'
,
version:
3
,
),
matchesGoldenFile
(
'text_field_opacity_test.0.png'
),
);
},
skip:
isBrowser
);
...
...
packages/flutter/test/painting/continous_rectangle_border_test.dart
View file @
7bc02037
...
...
@@ -71,10 +71,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'continuous_rectangle_border.golden_test_even_radii.png'
,
version:
null
,
),
matchesGoldenFile
(
'continuous_rectangle_border.golden_test_even_radii.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -95,10 +92,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'continuous_rectangle_border.golden_test_varying_radii.png'
,
version:
null
,
),
matchesGoldenFile
(
'continuous_rectangle_border.golden_test_varying_radii.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -116,10 +110,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'continuous_rectangle_border.golden_test_large_radii.png'
,
version:
null
,
),
matchesGoldenFile
(
'continuous_rectangle_border.golden_test_large_radii.png'
),
);
},
skip:
isBrowser
);
...
...
packages/flutter/test/painting/gradient_test.dart
View file @
7bc02037
...
...
@@ -806,7 +806,10 @@ void main() {
),
),
));
await
expectLater
(
find
.
byKey
(
painterKey
),
matchesGoldenFile
(
goldenName
));
await
expectLater
(
find
.
byKey
(
painterKey
),
matchesGoldenFile
(
goldenName
),
);
}
testWidgets
(
'Gradients - 45 degrees'
,
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/rendering/localized_fonts_test.dart
View file @
7bc02037
...
...
@@ -49,10 +49,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RichText
),
matchesGoldenFile
(
'localized_fonts.rich_text.styled_text_span.png'
,
version:
null
,
),
matchesGoldenFile
(
'localized_fonts.rich_text.styled_text_span.png'
),
);
},
skip:
isBrowser
,
// TODO(yjbanov): implement goldens on the Web: https://github.com/flutter/flutter/issues/40297
...
...
@@ -104,10 +101,7 @@ void main() {
await
expectLater
(
find
.
byType
(
Row
),
matchesGoldenFile
(
'localized_fonts.text_ambient_locale.chars.png'
,
version:
null
,
),
matchesGoldenFile
(
'localized_fonts.text_ambient_locale.chars.png'
),
);
},
skip:
isBrowser
,
// TODO(yjbanov): implement goldens on the Web: https://github.com/flutter/flutter/issues/40297
...
...
@@ -151,10 +145,7 @@ void main() {
await
expectLater
(
find
.
byType
(
Row
),
matchesGoldenFile
(
'localized_fonts.text_explicit_locale.chars.png'
,
version:
null
,
),
matchesGoldenFile
(
'localized_fonts.text_explicit_locale.chars.png'
),
);
},
skip:
isBrowser
,
// TODO(yjbanov): implement goldens on the Web: https://github.com/flutter/flutter/issues/40297
...
...
packages/flutter/test/widgets/backdrop_filter_test.dart
View file @
7bc02037
...
...
@@ -43,10 +43,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'backdrop_filter_test.cull_rect.png'
,
version:
1
,
),
matchesGoldenFile
(
'backdrop_filter_test.cull_rect.png'
),
);
},
skip:
isBrowser
);
}
packages/flutter/test/widgets/clip_test.dart
View file @
7bc02037
...
...
@@ -381,10 +381,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.ClipRect.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.ClipRect.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -424,10 +421,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.ClipRectOverlay.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.ClipRectOverlay.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -476,10 +470,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.ClipRRect.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.ClipRRect.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -522,10 +513,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.ClipOval.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.ClipOval.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -573,10 +561,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.ClipPath.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.ClipPath.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -621,10 +606,7 @@ void main() {
await
tester
.
pumpWidget
(
genPhysicalModel
(
Clip
.
antiAlias
));
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.PhysicalModel.antiAlias.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.PhysicalModel.antiAlias.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -632,10 +614,7 @@ void main() {
await
tester
.
pumpWidget
(
genPhysicalModel
(
Clip
.
hardEdge
));
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.PhysicalModel.hardEdge.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.PhysicalModel.hardEdge.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -645,10 +624,7 @@ void main() {
await
tester
.
pumpWidget
(
genPhysicalModel
(
Clip
.
antiAliasWithSaveLayer
));
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.PhysicalModel.antiAliasWithSaveLayer.png'
,
version:
null
,
),
matchesGoldenFile
(
'clip.PhysicalModel.antiAliasWithSaveLayer.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -690,10 +666,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.PhysicalModel.default.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.PhysicalModel.default.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -742,10 +715,7 @@ void main() {
await
tester
.
pumpWidget
(
genPhysicalShape
(
Clip
.
antiAlias
));
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.PhysicalShape.antiAlias.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.PhysicalShape.antiAlias.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -753,10 +723,7 @@ void main() {
await
tester
.
pumpWidget
(
genPhysicalShape
(
Clip
.
hardEdge
));
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.PhysicalShape.hardEdge.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.PhysicalShape.hardEdge.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -764,10 +731,7 @@ void main() {
await
tester
.
pumpWidget
(
genPhysicalShape
(
Clip
.
antiAliasWithSaveLayer
));
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.PhysicalShape.antiAliasWithSaveLayer.png'
,
version:
null
,
),
matchesGoldenFile
(
'clip.PhysicalShape.antiAliasWithSaveLayer.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -813,10 +777,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.PhysicalShape.default.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.PhysicalShape.default.png'
),
);
},
skip:
isBrowser
);
...
...
packages/flutter/test/widgets/color_filter_test.dart
View file @
7bc02037
...
...
@@ -18,10 +18,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
ColorFiltered
),
matchesGoldenFile
(
'color_filter_red.png'
,
version:
1
,
),
matchesGoldenFile
(
'color_filter_red.png'
),
);
});
...
...
@@ -58,10 +55,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
ColorFiltered
),
matchesGoldenFile
(
'color_filter_sepia.png'
,
version:
1
,
),
matchesGoldenFile
(
'color_filter_sepia.png'
),
);
});
...
...
packages/flutter/test/widgets/editable_text_cursor_test.dart
View file @
7bc02037
...
...
@@ -90,10 +90,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
const
ValueKey
<
int
>(
1
)),
matchesGoldenFile
(
'editable_text_test.0.png'
,
version:
3
,
),
matchesGoldenFile
(
'editable_text_test.0.png'
),
);
});
...
...
@@ -144,10 +141,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
const
ValueKey
<
int
>(
1
)),
matchesGoldenFile
(
'editable_text_test.1.png'
,
version:
3
,
),
matchesGoldenFile
(
'editable_text_test.1.png'
),
);
});
...
...
@@ -797,10 +791,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
const
ValueKey
<
int
>(
1
)),
matchesGoldenFile
(
'editable_text_test.2.png'
,
version:
0
,
),
matchesGoldenFile
(
'editable_text_test.2.png'
),
);
debugDefaultTargetPlatformOverride
=
null
;
});
...
...
packages/flutter/test/widgets/invert_colors_test.dart
View file @
7bc02037
...
...
@@ -20,10 +20,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'invert_colors_test.0.png'
,
version:
null
,
),
matchesGoldenFile
(
'invert_colors_test.0.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -41,10 +38,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'invert_colors_test.1.png'
,
version:
null
,
),
matchesGoldenFile
(
'invert_colors_test.1.png'
),
);
},
skip:
isBrowser
);
}
...
...
packages/flutter/test/widgets/list_wheel_scroll_view_test.dart
View file @
7bc02037
...
...
@@ -535,10 +535,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
const
Key
(
'list_wheel_scroll_view'
)),
matchesGoldenFile
(
'list_wheel_scroll_view.center_child.magnified.png'
,
version:
null
,
),
matchesGoldenFile
(
'list_wheel_scroll_view.center_child.magnified.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -592,10 +589,7 @@ void main() {
await
expectLater
(
find
.
byKey
(
const
Key
(
'list_wheel_scroll_view'
)),
matchesGoldenFile
(
'list_wheel_scroll_view.curved_wheel.left.png'
,
version:
null
,
),
matchesGoldenFile
(
'list_wheel_scroll_view.curved_wheel.left.png'
),
);
},
skip:
isBrowser
);
...
...
packages/flutter/test/widgets/opacity_test.dart
View file @
7bc02037
...
...
@@ -178,10 +178,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'opacity_test.offset.png'
,
version:
1
,
),
matchesGoldenFile
(
'opacity_test.offset.png'
),
);
},
skip:
isBrowser
);
...
...
packages/flutter/test/widgets/physical_model_test.dart
View file @
7bc02037
...
...
@@ -110,10 +110,7 @@ void main() {
expect
(
exception
.
diagnostics
.
first
.
toString
(),
startsWith
(
'A RenderFlex overflowed by '
));
await
expectLater
(
find
.
byKey
(
key
),
matchesGoldenFile
(
'physical_model_overflow.png'
,
version:
null
,
),
matchesGoldenFile
(
'physical_model_overflow.png'
),
);
},
skip:
isBrowser
);
...
...
packages/flutter/test/widgets/shadow_test.dart
View file @
7bc02037
...
...
@@ -23,20 +23,14 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'shadow.BoxDecoration.disabled.png'
,
version:
null
,
),
matchesGoldenFile
(
'shadow.BoxDecoration.disabled.png'
),
);
debugDisableShadows
=
false
;
tester
.
binding
.
reassembleApplication
();
await
tester
.
pump
();
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'shadow.BoxDecoration.enabled.png'
,
version:
null
,
),
matchesGoldenFile
(
'shadow.BoxDecoration.enabled.png'
),
);
debugDisableShadows
=
true
;
},
skip:
isBrowser
);
...
...
@@ -62,10 +56,7 @@ void main() {
await
tester
.
pumpWidget
(
build
(
elevation
));
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'shadow.ShapeDecoration.
$elevation
.png'
,
version:
null
,
),
matchesGoldenFile
(
'shadow.ShapeDecoration.
$elevation
.png'
),
);
}
debugDisableShadows
=
true
;
...
...
@@ -92,20 +83,14 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'shadow.PhysicalModel.disabled.png'
,
version:
null
,
),
matchesGoldenFile
(
'shadow.PhysicalModel.disabled.png'
),
);
debugDisableShadows
=
false
;
tester
.
binding
.
reassembleApplication
();
await
tester
.
pump
();
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'shadow.PhysicalModel.enabled.png'
,
version:
null
,
),
matchesGoldenFile
(
'shadow.PhysicalModel.enabled.png'
),
);
debugDisableShadows
=
true
;
},
skip:
isBrowser
);
...
...
@@ -135,10 +120,7 @@ void main() {
await
tester
.
pumpWidget
(
build
(
elevation
.
toDouble
()));
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'shadow.PhysicalShape.
$elevation
.png'
,
version:
1
,
),
matchesGoldenFile
(
'shadow.PhysicalShape.
$elevation
.png'
),
);
}
debugDisableShadows
=
true
;
...
...
packages/flutter/test/widgets/text_golden_test.dart
View file @
7bc02037
...
...
@@ -30,10 +30,7 @@ void main() {
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.Centered.png'
,
version:
null
,
),
matchesGoldenFile
(
'text_golden.Centered.png'
),
);
await
tester
.
pumpWidget
(
...
...
@@ -57,10 +54,7 @@ void main() {
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.Centered.wrap.png'
,
version:
null
,
),
matchesGoldenFile
(
'text_golden.Centered.wrap.png'
),
);
});
...
...
@@ -91,10 +85,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'text_golden.Foreground.gradient.png'
,
version:
null
,
),
matchesGoldenFile
(
'text_golden.Foreground.gradient.png'
),
);
await
tester
.
pumpWidget
(
...
...
@@ -116,10 +107,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'text_golden.Foreground.stroke.png'
,
version:
null
,
),
matchesGoldenFile
(
'text_golden.Foreground.stroke.png'
),
);
await
tester
.
pumpWidget
(
...
...
@@ -142,10 +130,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'text_golden.Foreground.stroke_and_gradient.png'
,
version:
null
,
),
matchesGoldenFile
(
'text_golden.Foreground.stroke_and_gradient.png'
),
);
});
...
...
@@ -195,10 +180,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'text_golden.Background.png'
,
version:
null
,
),
matchesGoldenFile
(
'text_golden.Background.png'
),
);
});
...
...
@@ -234,10 +216,7 @@ void main() {
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'text_golden.Fade.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.Fade.png'
),
);
});
...
...
@@ -262,10 +241,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.StrutDefault.png'
,
version:
null
,
),
matchesGoldenFile
(
'text_golden.StrutDefault.png'
),
);
});
...
...
@@ -292,10 +268,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.Strut.1.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.Strut.1.png'
),
);
});
...
...
@@ -323,10 +296,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.Strut.2.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.Strut.2.png'
),
);
});
...
...
@@ -377,10 +347,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.Strut.3.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.Strut.3.png'
),
);
});
...
...
@@ -415,10 +382,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.Strut.4.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.Strut.4.png'
),
);
});
...
...
@@ -469,10 +433,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.StrutForce.1.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.StrutForce.1.png'
),
);
});
...
...
@@ -510,10 +471,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.Decoration.1.png'
,
version:
0
,
),
matchesGoldenFile
(
'text_golden.Decoration.1.png'
),
);
});
...
...
@@ -552,10 +510,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.DecorationThickness.1.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.DecorationThickness.1.png'
),
);
});
...
...
@@ -649,10 +604,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidget.1.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.TextInlineWidget.1.png'
),
);
});
...
...
@@ -697,10 +649,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidget.2.png'
,
version:
2
,
),
matchesGoldenFile
(
'text_golden.TextInlineWidget.2.png'
),
);
});
...
...
@@ -829,10 +778,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetNest.1.png'
,
version:
3
,
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetNest.1.png'
),
);
});
...
...
@@ -939,10 +885,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetBaseline.1.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetBaseline.1.png'
),
);
});
...
...
@@ -1049,10 +992,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetAboveBaseline.1.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetAboveBaseline.1.png'
),
);
});
...
...
@@ -1159,10 +1099,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetBelowBaseline.1.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetBelowBaseline.1.png'
),
);
});
...
...
@@ -1269,10 +1206,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetTop.1.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetTop.1.png'
),
);
});
...
...
@@ -1379,10 +1313,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetMiddle.1.png'
,
version:
1
,
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetMiddle.1.png'
),
);
});
}
packages/flutter/test/widgets/widget_inspector_test.dart
View file @
7bc02037
...
...
@@ -2039,10 +2039,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
expect
(
expectedChildLayerCount
,
equals
(
2
));
await
expectLater
(
layer
.
toImage
(
renderObject
.
semanticBounds
.
inflate
(
50.0
)),
matchesGoldenFile
(
'inspector.repaint_boundary_margin.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_margin.png'
),
);
// Regression test for how rendering with a pixel scale other than 1.0
...
...
@@ -2052,10 +2049,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
renderObject
.
semanticBounds
.
inflate
(
50.0
),
pixelRatio:
0.5
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_margin_small.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_margin_small.png'
),
);
await
expectLater
(
...
...
@@ -2063,10 +2057,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
renderObject
.
semanticBounds
.
inflate
(
50.0
),
pixelRatio:
2.0
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_margin_large.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_margin_large.png'
),
);
final
Layer
layerParent
=
layer
.
parent
;
...
...
@@ -2081,10 +2072,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
width:
300.0
,
height:
300.0
,
),
matchesGoldenFile
(
'inspector.repaint_boundary.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.repaint_boundary.png'
),
);
// Verify that taking a screenshot didn't change the layers associated with
...
...
@@ -2101,10 +2089,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
height:
500.0
,
margin:
50.0
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_margin.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_margin.png'
),
);
// Verify that taking a screenshot didn't change the layers associated with
...
...
@@ -2124,10 +2109,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
height:
300.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_debugPaint.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_debugPaint.png'
),
);
// Verify that taking a screenshot with debug paint on did not change
// the number of children the layer has.
...
...
@@ -2137,10 +2119,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
// hasn't changed the regular render of the widget.
await
expectLater
(
find
.
byType
(
RepaintBoundaryWithDebugPaint
),
matchesGoldenFile
(
'inspector.repaint_boundary.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.repaint_boundary.png'
),
);
expect
(
renderObject
.
debugLayer
,
equals
(
layer
));
...
...
@@ -2153,10 +2132,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
width:
100.0
,
height:
100.0
,
),
matchesGoldenFile
(
'inspector.container.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.container.png'
),
);
await
expectLater
(
...
...
@@ -2166,10 +2142,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
height:
100.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.container_debugPaint.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.container_debugPaint.png'
),
);
{
...
...
@@ -2189,10 +2162,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
height:
100.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.container_debugPaint.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.container_debugPaint.png'
),
);
expect
(
container
.
debugNeedsLayout
,
isFalse
);
}
...
...
@@ -2204,10 +2174,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
width:
50.0
,
height:
100.0
,
),
matchesGoldenFile
(
'inspector.container_small.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.container_small.png'
),
);
await
expectLater
(
...
...
@@ -2217,10 +2184,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
height:
400.0
,
maxPixelRatio:
3.0
,
),
matchesGoldenFile
(
'inspector.container_large.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.container_large.png'
),
);
// This screenshot will show the clip rect debug paint but no other
...
...
@@ -2232,10 +2196,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
height:
100.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.clipRect_debugPaint.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.clipRect_debugPaint.png'
),
);
final
Element
clipRect
=
find
.
byType
(
ClipRRect
).
evaluate
().
single
;
...
...
@@ -2251,10 +2212,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
// This golden image is platform dependent due to the clip icon.
await
expectLater
(
clipRectScreenshot
,
matchesGoldenFile
(
'inspector.clipRect_debugPaint_margin.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.clipRect_debugPaint_margin.png'
),
);
// Verify we get the same image if we go through the service extension
...
...
@@ -2293,10 +2251,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
height:
300.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.padding_debugPaint.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.padding_debugPaint.png'
),
);
// The bounds for this box crop its rendered content.
...
...
@@ -2307,10 +2262,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
height:
300.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.sizedBox_debugPaint.png'
,
version:
1
,
),
matchesGoldenFile
(
'inspector.sizedBox_debugPaint.png'
),
);
// Verify that setting a margin includes the previously cropped content.
...
...
@@ -2322,10 +2274,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
margin:
50.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.sizedBox_debugPaint_margin.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.sizedBox_debugPaint_margin.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -2461,10 +2410,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
await
expectLater
(
find
.
byKey
(
mainStackKey
),
matchesGoldenFile
(
'inspector.composited_transform.only_offsets.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.composited_transform.only_offsets.png'
),
);
await
expectLater
(
...
...
@@ -2473,18 +2419,12 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
width:
5000.0
,
height:
500.0
,
),
matchesGoldenFile
(
'inspector.composited_transform.only_offsets_follower.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.composited_transform.only_offsets_follower.png'
),
);
await
expectLater
(
WidgetInspectorService
.
instance
.
screenshot
(
find
.
byType
(
Stack
).
evaluate
().
first
,
width:
300.0
,
height:
300.0
),
matchesGoldenFile
(
'inspector.composited_transform.only_offsets_small.png'
,
version:
1
,
),
matchesGoldenFile
(
'inspector.composited_transform.only_offsets_small.png'
),
);
await
expectLater
(
...
...
@@ -2493,10 +2433,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
width:
500.0
,
height:
500.0
,
),
matchesGoldenFile
(
'inspector.composited_transform.only_offsets_target.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.composited_transform.only_offsets_target.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -2568,10 +2505,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
// screenshots of specific subtrees are reasonable.
await
expectLater
(
find
.
byKey
(
mainStackKey
),
matchesGoldenFile
(
'inspector.composited_transform.with_rotations.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.composited_transform.with_rotations.png'
),
);
await
expectLater
(
...
...
@@ -2580,10 +2514,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
width:
500.0
,
height:
500.0
,
),
matchesGoldenFile
(
'inspector.composited_transform.with_rotations_small.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.composited_transform.with_rotations_small.png'
),
);
await
expectLater
(
...
...
@@ -2592,10 +2523,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
width:
500.0
,
height:
500.0
,
),
matchesGoldenFile
(
'inspector.composited_transform.with_rotations_target.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.composited_transform.with_rotations_target.png'
),
);
await
expectLater
(
...
...
@@ -2604,10 +2532,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
width:
500.0
,
height:
500.0
,
),
matchesGoldenFile
(
'inspector.composited_transform.with_rotations_follower.png'
,
version:
null
,
),
matchesGoldenFile
(
'inspector.composited_transform.with_rotations_follower.png'
),
);
// Make sure taking screenshots hasn't modified the positions of the
...
...
packages/flutter_goldens/lib/flutter_goldens.dart
View file @
7bc02037
...
...
@@ -11,49 +11,99 @@ import 'package:flutter_test/flutter_test.dart';
import
'package:meta/meta.dart'
;
import
'package:platform/platform.dart'
;
import
'package:flutter_goldens_client/client.dart'
;
import
'package:flutter_goldens_client/skia_client.dart'
;
export
'package:flutter_goldens_client/client.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.
Future
<
void
>
main
(
FutureOr
<
void
>
testMain
())
async
{
const
Platform
platform
=
LocalPlatform
();
if
(
FlutterSkiaGoldFileComparator
.
isAvailable
OnPlatform
(
platform
))
{
goldenFileComparator
=
await
FlutterSkiaGoldFileComparator
.
fromDefaultComparator
();
}
else
if
(
Flutter
GoldensRepositoryFileComparator
.
isAvailableOnPlatform
(
platform
))
{
goldenFileComparator
=
await
Flutter
GoldensRepositoryFileComparator
.
fromDefaultComparator
(
);
}
else
{
if
(
FlutterSkiaGoldFileComparator
.
isAvailable
ForEnvironment
(
platform
))
{
goldenFileComparator
=
await
FlutterSkiaGoldFileComparator
.
fromDefaultComparator
(
platform
);
}
else
if
(
Flutter
PreSubmitFileComparator
.
isAvailableForEnvironment
(
platform
))
{
goldenFileComparator
=
await
Flutter
PreSubmitFileComparator
.
fromDefaultComparator
(
platform
);
}
else
if
(
FlutterSkippingGoldenFileComparator
.
isAvailableForEnvironment
(
platform
))
{
goldenFileComparator
=
FlutterSkippingGoldenFileComparator
.
fromDefaultComparator
();
}
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 [FlutterSkiaGoldFileComparator] 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 can land on the master branch. This comparator
/// uses the [SkiaGoldClient] to request the baseline images kept by the
/// [Flutter Gold dashboard](https://flutter-gold.skia.org). It then
/// compares the current test image to the baseline images using the
/// standard [GoldenFileComparator.compareLists] to detect any pixel
/// difference. The [SkiaGoldClient] is also used here to check the active
/// ignores from the dashboard, in order to allow intended changes to pass
/// tests.
///
/// * The [FlutterLocalFileComparator] is used for any other tests run outside
/// of the above conditions. Similar to the
/// [FlutterPreSubmitFileComparator], this comparator will use the
/// [SkiaGoldClient] to request baseline images from
/// [Flutter Gold](https://flutter-gold.skia.org) and compares for the
/// current test image. 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 [FlutterSkippingGoldenFileComparator] is utilized to skip tests outside
/// of the appropriate environments. Currently, tests executing in post-submit
/// on the LUCI build environment are skipped, as post-submit checks are done
/// on Cirrus.
abstract
class
FlutterGoldenFileComparator
extends
GoldenFileComparator
{
/// Creates a [FlutterGoldenFileComparator] that will resolve golden file
/// URIs relative to the specified [basedir].
/// 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
useful in tests, where the default file
/// system and platform can be replaced by mock instances.
/// 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
.
basedir
,
this
.
skiaClient
,
{
this
.
fs
=
const
LocalFileSystem
(),
this
.
platform
=
const
LocalPlatform
(),
})
:
assert
(
basedir
!=
null
),
assert
(
skiaClient
!=
null
),
assert
(
fs
!=
null
),
assert
(
platform
!=
null
);
/// The directory to which golden file URIs will be resolved in [compare] and
/// [update].
/// [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
;
...
...
@@ -69,191 +119,340 @@ abstract class FlutterGoldenFileComparator extends GoldenFileComparator {
await
goldenFile
.
writeAsBytes
(
imageBytes
,
flush:
true
);
}
@override
Uri
getTestUri
(
Uri
key
,
int
version
)
=>
key
;
/// Calculate the appropriate basedir for the current test context.
@protected
@visibleForTesting
static
Directory
getBaseDirectory
(
GoldensClient
goldens
,
LocalFileComparator
defaultComparator
)
{
final
FileSystem
fs
=
goldens
.
fs
;
static
Directory
getBaseDirectory
(
LocalFileComparator
defaultComparator
,
Platform
platform
)
{
const
FileSystem
fs
=
LocalFileSystem
();
final
Directory
flutterRoot
=
fs
.
directory
(
platform
.
environment
[
_kFlutterRootKey
]);
final
Directory
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:
goldens
.
flutterRoot
.
path
);
return
goldens
.
comparisonRoot
.
childDirectory
(
testDirectoryRelativePath
);
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
)
{
assert
(
basedir
.
scheme
==
'file'
);
final
File
goldenFile
=
fs
.
directory
(
basedir
).
childFile
(
fs
.
file
(
uri
).
path
);
assert
(
goldenFile
.
uri
.
scheme
==
'file'
);
return
goldenFile
;
}
/// Prepends the golden Uri with the library name that encloses the current
/// test.
Uri
_addPrefix
(
Uri
golden
)
{
final
String
prefix
=
basedir
.
pathSegments
[
basedir
.
pathSegments
.
length
-
2
];
return
Uri
.
parse
(
prefix
+
'.'
+
golden
.
toString
());
}
}
/// A [FlutterGoldenFileComparator] for testing golden images against the
/// `flutter/goldens` repository.
///
/// Within the https://github.com/flutter/flutter repository, it's important
/// not to check-in binaries in order to keep the size of the repository to a
/// minimum. To satisfy this requirement, this comparator retrieves the golden
/// files from a sibling repository, `flutter/goldens`.
/// A [FlutterGoldenFileComparator] for testing golden images with Skia Gold.
///
///
This comparator will locally clone the `flutter/goldens` repository into
///
the `$FLUTTER_ROOT/bin/cache/pkg/goldens` folder using the
///
[GoldensRepositoryClient], then perform the comparison against the files
///
therein
.
///
For testing across all platforms, the [SkiaGoldClient] is used to upload
///
images for framework-related golden tests and process results. Currently
///
these tests are designed to be run post-submit on Cirrus CI, informed by the
///
environment
.
///
/// See also:
///
/// * [GoldenFileComparator], the abstract class that
/// [FlutterGoldenFileComparator] implements.
/// * [FlutterSkiaGoldFileComparator], another [FlutterGoldenFileComparator]
/// that tests golden images through Skia Gold.
class
FlutterGoldensRepositoryFileComparator
extends
FlutterGoldenFileComparator
{
/// Creates a [FlutterGoldensRepositoryFileComparator] that will test golden
/// file images against the `flutter/goldens` repository.
/// * [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
FlutterSkiaGoldFileComparator
extends
FlutterGoldenFileComparator
{
/// Creates a [FlutterSkiaGoldFileComparator] that will test golden file
/// images against Skia Gold.
///
/// The [fs] and [platform] parameters useful in tests, where the default file
/// system and platform can be replaced by mock instances.
FlutterGoldensRepositoryFileComparator
(
Uri
basedir
,
{
FileSystem
fs
=
const
LocalFileSystem
(),
Platform
platform
=
const
LocalPlatform
(),
/// The [fs] and [platform] parameters are useful in tests, where the default
/// file system and platform can be replaced by mock instances.
FlutterSkiaGoldFileComparator
(
final
Uri
basedir
,
final
SkiaGoldClient
skiaClient
,
{
final
FileSystem
fs
=
const
LocalFileSystem
(),
final
Platform
platform
=
const
LocalPlatform
(),
})
:
super
(
basedir
,
skiaClient
,
fs:
fs
,
platform:
platform
,
);
/// Creates a new [FlutterGoldensRespositoryFileComparator] that mirrors the
/// relative path resolution of the default [goldenFileComparator].
///
/// By the time the future completes, the clone of the `flutter/goldens`
/// repository is guaranteed to be ready to use.
/// Creates a new [FlutterSkiaGoldFileComparator] that mirrors the relative
/// path resolution of the default [goldenFileComparator].
///
/// The [goldens] and [defaultComparator] parameters are visible for testing
/// purposes only.
static
Future
<
FlutterGoldensRepositoryFileComparator
>
fromDefaultComparator
({
GoldensRepositoryClient
goldens
,
static
Future
<
FlutterSkiaGoldFileComparator
>
fromDefaultComparator
(
final
Platform
platform
,
{
SkiaGoldClient
goldens
,
LocalFileComparator
defaultComparator
,
})
async
{
defaultComparator
??=
goldenFileComparator
;
final
Directory
baseDirectory
=
FlutterGoldenFileComparator
.
getBaseDirectory
(
defaultComparator
,
platform
,
);
// Prepare the goldens repo.
goldens
??=
GoldensRepositoryClient
(
);
await
goldens
.
prepare
();
if
(!
baseDirectory
.
existsSync
())
{
baseDirectory
.
createSync
(
recursive:
true
);
}
final
Directory
baseDirectory
=
FlutterGoldenFileComparator
.
getBaseDirectory
(
goldens
,
defaultComparator
);
return
FlutterGoldensRepositoryFileComparator
(
baseDirectory
.
uri
);
goldens
??=
SkiaGoldClient
(
baseDirectory
);
await
goldens
.
auth
();
await
goldens
.
imgtestInit
();
return
FlutterSkiaGoldFileComparator
(
baseDirectory
.
uri
,
goldens
);
}
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
golden
=
_addPrefix
(
golden
);
await
update
(
golden
,
imageBytes
);
final
File
goldenFile
=
getGoldenFile
(
golden
);
if
(!
goldenFile
.
existsSync
())
{
throw
TestFailure
(
'Could not be compared against non-existent file: "
$golden
"'
);
}
final
List
<
int
>
goldenBytes
=
await
goldenFile
.
readAsBytes
();
final
ComparisonResult
result
=
GoldenFileComparator
.
compareLists
(
imageBytes
,
goldenBytes
);
return
result
.
passed
;
return
skiaClient
.
imgtestAdd
(
golden
.
path
,
goldenFile
);
}
/// Decides based on the current platform whether goldens tests should be
/// performed against the flutter/goldens repository.
static
bool
isAvailableOnPlatform
(
Platform
platform
)
=>
platform
.
isLinux
;
/// Decides based on the current environment whether goldens tests should be
/// performed against Skia Gold.
static
bool
isAvailableForEnvironment
(
Platform
platform
)
{
final
String
cirrusCI
=
platform
.
environment
[
'CIRRUS_CI'
]
??
''
;
final
String
cirrusPR
=
platform
.
environment
[
'CIRRUS_PR'
]
??
''
;
final
String
cirrusBranch
=
platform
.
environment
[
'CIRRUS_BRANCH'
]
??
''
;
final
String
goldServiceAccount
=
platform
.
environment
[
'GOLD_SERVICE_ACCOUNT'
]
??
''
;
return
cirrusCI
.
isNotEmpty
&&
cirrusPR
.
isEmpty
&&
cirrusBranch
==
'master'
&&
goldServiceAccount
.
isNotEmpty
;
}
}
/// A [FlutterGoldenFileComparator] for testing golden images with Skia Gold.
/// A [FlutterGoldenFileComparator] for testing golden images before changes are
/// merged into the master branch.
///
/// For testing across all platforms, the [SkiaGoldClient] is used to upload
/// images for framework-related golden tests and process results. Currently
/// these tests are designed to be run post-submit on Cirrus CI, informed by the
/// environment.
/// This comparator utilizes the [SkiaGoldClient] to request baseline images for
/// the given device under test for comparison. This comparator is only
/// initialized during pre-submit testing on Cirrus CI.
///
/// See also:
///
/// * [GoldenFileComparator], the abstract class that
/// [FlutterGoldenFileComparator] implements.
/// * [FlutterGoldensRepositoryFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images using the
/// flutter/goldens repository.
class
FlutterSkiaGoldFileComparator
extends
FlutterGoldenFileComparator
{
/// Creates a [FlutterSkiaGoldFileComparator] that will test golden file
/// images against Skia Gold.
/// * [FlutterSkiaGoldFileComparator], another
/// [FlutterGoldenFileComparator] that uploads tests to the Skia Gold
/// dashboard.
/// * [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
useful in tests, where the default file
/// system and platform can be replaced by mock instances.
Flutter
SkiaGold
FileComparator
(
/// The [fs] and [platform] parameters
are useful in tests, where the default
///
file
system and platform can be replaced by mock instances.
Flutter
PreSubmit
FileComparator
(
final
Uri
basedir
,
this
.
skiaClient
,
{
FileSystem
fs
=
const
LocalFileSystem
(),
Platform
platform
=
const
LocalPlatform
(),
final
SkiaGoldClient
skiaClient
,
{
final
FileSystem
fs
=
const
LocalFileSystem
(),
final
Platform
platform
=
const
LocalPlatform
(),
})
:
super
(
basedir
,
skiaClient
,
fs:
fs
,
platform:
platform
,
);
final
SkiaGoldClient
skiaClient
;
/// Creates a new [FlutterSkiaGoldFileComparator] that mirrors the relative
/// path resolution of the default [goldenFileComparator].
/// 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
<
FlutterSkiaGoldFileComparator
>
fromDefaultComparator
({
static
Future
<
FlutterGoldenFileComparator
>
fromDefaultComparator
(
final
Platform
platform
,
{
SkiaGoldClient
goldens
,
LocalFileComparator
defaultComparator
,
})
async
{
defaultComparator
??=
goldenFileComparator
;
goldens
??=
SkiaGoldClient
();
final
Directory
baseDirectory
=
FlutterGoldenFileComparator
.
getBaseDirectory
(
defaultComparator
,
platform
,
);
final
Directory
baseDirectory
=
FlutterGoldenFileComparator
.
getBaseDirectory
(
goldens
,
defaultComparator
);
if
(!
baseDirectory
.
existsSync
())
if
(!
baseDirectory
.
existsSync
())
{
baseDirectory
.
createSync
(
recursive:
true
);
await
goldens
.
auth
(
baseDirectory
);
await
goldens
.
imgtestInit
();
return
FlutterSkiaGoldFileComparator
(
baseDirectory
.
uri
,
goldens
);
}
goldens
??=
SkiaGoldClient
(
baseDirectory
);
await
goldens
.
getExpectations
();
return
FlutterPreSubmitFileComparator
(
baseDirectory
.
uri
,
goldens
);
}
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
golden
=
_addPrefix
(
golden
);
await
update
(
golden
,
imageBytes
);
final
String
testName
=
skiaClient
.
cleanTestName
(
golden
.
path
);
final
List
<
String
>
testExpectations
=
skiaClient
.
expectations
[
testName
];
final
File
goldenFile
=
getGoldenFile
(
golden
);
if
(!
goldenFile
.
existsSync
())
{
throw
TestFailure
(
'Could not be compared against non-existent file: "
$golden
"'
);
if
(
testExpectations
==
null
)
{
// There is no baseline for this test
return
true
;
}
ComparisonResult
result
;
for
(
String
expectation
in
testExpectations
)
{
final
List
<
int
>
goldenBytes
=
await
skiaClient
.
getImageBytes
(
expectation
);
result
=
GoldenFileComparator
.
compareLists
(
imageBytes
,
goldenBytes
,
);
if
(
result
.
passed
)
{
return
true
;
}
return
await
skiaClient
.
imgtestAdd
(
golden
.
path
,
goldenFile
);
}
@override
Uri
getTestUri
(
Uri
key
,
int
version
)
=>
key
;
return
skiaClient
.
testIsIgnoredForPullRequest
(
platform
.
environment
[
'CIRRUS_PR'
]
??
''
,
golden
.
path
,
);
}
/// Decides based on the current environment whether goldens tests should be
/// performed a
gainst
Skia Gold.
static
bool
isAvailable
OnPlatform
(
Platform
platform
)
{
/// performed a
s pre-submit tests with
Skia Gold.
static
bool
isAvailable
ForEnvironment
(
Platform
platform
)
{
final
String
cirrusCI
=
platform
.
environment
[
'CIRRUS_CI'
]
??
''
;
final
String
cirrusPR
=
platform
.
environment
[
'CIRRUS_PR'
]
??
''
;
final
String
cirrusBranch
=
platform
.
environment
[
'CIRRUS_BRANCH'
]
??
''
;
final
String
goldServiceAccount
=
platform
.
environment
[
'GOLD_SERVICE_ACCOUNT'
]
??
''
;
return
cirrusCI
.
isNotEmpty
&&
cirrusPR
.
isEmpty
&&
cirrusBranch
==
'master'
&&
goldServiceAccount
.
isNotEmpty
;
return
cirrusCI
.
isNotEmpty
&&
cirrusPR
.
isNotEmpty
;
}
}
/// Prepends the golden Uri with the library name that encloses the current
/// test.
Uri
_addPrefix
(
Uri
golden
)
{
final
String
prefix
=
basedir
.
pathSegments
[
basedir
.
pathSegments
.
length
-
2
];
return
Uri
.
parse
(
prefix
+
'.'
+
golden
.
toString
());
/// 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 only
/// initialized when running tests locally, and is intended to serve as a smoke
/// test during development. As such, it will not be able to detect unintended
/// changes on other machines until it they are tested using the
/// [FlutterPreSubmitFileComparator].
///
/// See also:
///
/// * [GoldenFileComparator], the abstract class that
/// [FlutterGoldenFileComparator] implements.
/// * [FlutterSkiaGoldFileComparator], 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.
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
(
final
Uri
basedir
,
final
SkiaGoldClient
skiaClient
,
{
final
FileSystem
fs
=
const
LocalFileSystem
(),
final
Platform
platform
=
const
LocalPlatform
(),
})
:
super
(
basedir
,
skiaClient
,
fs:
fs
,
platform:
platform
,
);
/// Creates a new [FlutterLocalFileComparator] 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
,
})
async
{
defaultComparator
??=
goldenFileComparator
;
final
Directory
baseDirectory
=
FlutterGoldenFileComparator
.
getBaseDirectory
(
defaultComparator
,
platform
,
);
if
(!
baseDirectory
.
existsSync
())
{
baseDirectory
.
createSync
(
recursive:
true
);
}
goldens
??=
SkiaGoldClient
(
baseDirectory
);
await
goldens
.
getExpectations
();
return
FlutterLocalFileComparator
(
baseDirectory
.
uri
,
goldens
);
}
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
golden
=
_addPrefix
(
golden
);
final
String
testName
=
skiaClient
.
cleanTestName
(
golden
.
path
);
final
List
<
String
>
testExpectations
=
skiaClient
.
expectations
[
testName
];
if
(
testExpectations
==
null
)
{
// There is no baseline for this test
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
Map
<
String
,
ComparisonResult
>
failureDiffs
=
<
String
,
ComparisonResult
>{};
for
(
String
expectation
in
testExpectations
)
{
final
List
<
int
>
goldenBytes
=
await
skiaClient
.
getImageBytes
(
expectation
);
result
=
GoldenFileComparator
.
compareLists
(
imageBytes
,
goldenBytes
,
);
if
(
result
.
passed
)
{
return
true
;
}
failureDiffs
[
expectation
]
=
result
;
}
failureDiffs
.
forEach
((
String
expectation
,
ComparisonResult
result
)
async
{
if
(
await
skiaClient
.
isValidDigestForExpectation
(
expectation
,
golden
.
path
))
generateFailureOutput
(
result
,
golden
,
basedir
,
key:
expectation
);
});
return
false
;
}
}
/// A [FlutterGoldenFileComparator] for skipping golden image tests when
Skia
///
Gold is unavailable or the current platform that is executing tests is no
t
///
Linux
.
/// A [FlutterGoldenFileComparator] for skipping golden image tests when
the
///
current environment is not supported. This comparator is used in post-submi
t
///
checks on LUCI
.
///
/// See also:
///
...
...
@@ -262,29 +461,48 @@ class FlutterSkiaGoldFileComparator extends FlutterGoldenFileComparator {
/// flutter/goldens repository.
/// * [FlutterSkiaGoldFileComparator], 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
FlutterSkippingGoldenFileComparator
extends
FlutterGoldenFileComparator
{
/// Creates a [FlutterSkippingGoldenFileComparator] that will skip tests that
/// are not in the right environment for golden file testing.
FlutterSkippingGoldenFileComparator
(
Uri
basedir
)
:
super
(
basedir
);
FlutterSkippingGoldenFileComparator
(
final
Uri
basedir
,
final
SkiaGoldClient
skiaClient
,
)
:
super
(
basedir
,
skiaClient
);
/// Creates a new [FlutterSkippingGoldenFileComparator] that mirrors the
relative
/// path resolution of the default [goldenFileComparator].
/// Creates a new [FlutterSkippingGoldenFileComparator] that mirrors the
///
relative
path resolution of the default [goldenFileComparator].
static
FlutterSkippingGoldenFileComparator
fromDefaultComparator
({
LocalFileComparator
defaultComparator
,
})
{
defaultComparator
??=
goldenFileComparator
;
return
FlutterSkippingGoldenFileComparator
(
defaultComparator
.
basedir
);
const
FileSystem
fs
=
LocalFileSystem
();
final
Uri
basedir
=
defaultComparator
.
basedir
;
final
SkiaGoldClient
skiaClient
=
SkiaGoldClient
(
fs
.
directory
(
basedir
));
return
FlutterSkippingGoldenFileComparator
(
basedir
,
skiaClient
);
}
@override
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
print
(
'Skipping "
$golden
" test : Skia Gold is not available in this testing '
'
environment and flutter/goldens repository comparison is only available
'
'on Linux machines
.'
print
(
'
Skipping "
$golden
" test : Golden file testing is unavailble in LUCI
'
'environment
.'
);
return
true
;
}
@override
Future
<
void
>
update
(
Uri
golden
,
Uint8List
imageBytes
)
=>
null
;
/// Decides based on the current environment whether goldens tests should be
/// skipped.
static
bool
isAvailableForEnvironment
(
Platform
platform
)
{
final
String
luci
=
platform
.
environment
[
'SWARMING_TASK_ID'
]
??
''
;
return
luci
.
isNotEmpty
;
}
}
packages/flutter_goldens/test/flutter_goldens_test.dart
View file @
7bc02037
...
...
@@ -2,7 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:io'
as
io
;
import
'dart:async'
;
import
'dart:convert'
;
import
'dart:core'
;
import
'dart:io'
;
import
'dart:typed_data'
;
import
'package:file/file.dart'
;
...
...
@@ -13,196 +16,560 @@ import 'package:mockito/mockito.dart';
import
'package:platform/platform.dart'
;
import
'package:process/process.dart'
;
import
'json_templates.dart'
;
const
String
_kFlutterRoot
=
'/flutter'
;
const
String
_kRepositoryRoot
=
'
$_kFlutterRoot
/bin/cache/pkg/goldens'
;
const
String
_kVersionFile
=
'
$_kFlutterRoot
/bin/internal/goldens.version'
;
const
String
_kGoldensVersion
=
'123456abcdef'
;
// 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
];
// 1x1 colored pixel
const
List
<
int
>
_kFailPngBytes
=
<
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
,
13
,
73
,
68
,
65
,
84
,
120
,
1
,
99
,
249
,
207
,
240
,
255
,
63
,
0
,
7
,
18
,
3
,
2
,
164
,
147
,
160
,
197
,
0
,
0
,
0
,
0
,
73
,
69
,
78
,
68
,
174
,
66
,
96
,
130
];
void
main
(
)
{
MemoryFileSystem
fs
;
FakePlatform
platform
;
MockProcessManager
process
;
MockHttpClient
mockHttpClient
;
setUp
(()
{
fs
=
MemoryFileSystem
();
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
});
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
},
operatingSystem:
'macos'
);
process
=
MockProcessManager
();
mockHttpClient
=
MockHttpClient
();
fs
.
directory
(
_kFlutterRoot
).
createSync
(
recursive:
true
);
fs
.
directory
(
_kRepositoryRoot
).
createSync
(
recursive:
true
);
fs
.
file
(
_kVersionFile
).
createSync
(
recursive:
true
);
fs
.
file
(
_kVersionFile
).
writeAsStringSync
(
_kGoldensVersion
);
});
group
(
'
Goldens
Client'
,
()
{
GoldensRepositoryClient
goldens
;
group
(
'
SkiaGold
Client'
,
()
{
SkiaGoldClient
skiaClient
;
setUp
(()
{
goldens
=
GoldensRepositoryClient
(
final
Directory
workDirectory
=
fs
.
directory
(
'/workDirectory'
)
..
createSync
(
recursive:
true
);
skiaClient
=
SkiaGoldClient
(
workDirectory
,
fs:
fs
,
process:
process
,
platform:
platform
,
httpClient:
mockHttpClient
,
);
});
group
(
'prepare'
,
()
{
test
(
'performs minimal work if versions match'
,
()
async
{
when
(
process
.
run
(
any
,
workingDirectory:
anyNamed
(
'workingDirectory'
)))
.
thenAnswer
((
_
)
=>
Future
<
io
.
ProcessResult
>.
value
(
io
.
ProcessResult
(
123
,
0
,
_kGoldensVersion
,
''
)));
await
goldens
.
prepare
();
group
(
'auth'
,
()
{
test
(
'performs minimal work if already authorized'
,
()
async
{
fs
.
file
(
'/workDirectory/temp/auth_opt.json'
)
..
createSync
(
recursive:
true
);
when
(
process
.
run
(
any
))
.
thenAnswer
((
_
)
=>
Future
<
ProcessResult
>
.
value
(
ProcessResult
(
123
,
0
,
''
,
''
)));
await
skiaClient
.
auth
();
// Verify that we only spawned `git rev-parse HEAD`
final
VerificationResult
verifyProcessRun
=
verify
(
process
.
run
(
captureAny
,
workingDirectory:
captureAnyNamed
(
'workingDirectory'
)));
verifyProcessRun
.
called
(
1
);
expect
(
verifyProcessRun
.
captured
.
first
,
<
String
>[
'git'
,
'rev-parse'
,
'HEAD'
]);
expect
(
verifyProcessRun
.
captured
.
last
,
_kRepositoryRoot
);
});
verifyNever
(
process
.
run
(
captureAny
,
workingDirectory:
captureAnyNamed
(
'workingDirectory'
),
));
});
});
group
(
'SkiaGoldClient'
,
()
{
SkiaGoldClient
goldens
;
group
(
'Request Handling'
,
()
{
String
testName
;
String
pullRequestNumber
;
String
expectation
;
Uri
url
;
MockHttpClientRequest
mockHttpRequest
;
setUp
(()
{
goldens
=
SkiaGoldClient
(
fs:
fs
,
process:
process
,
platform:
platform
,
);
testName
=
'flutter.golden_test.1.png'
;
pullRequestNumber
=
'1234'
;
expectation
=
'55109a4bed52acc780530f7a9aeff6c0'
;
mockHttpRequest
=
MockHttpClientRequest
();
});
group
(
'auth'
,
()
{
test
(
'performs minimal work if already authorized'
,
()
async
{
final
Directory
workDirectory
=
fs
.
directory
(
'/workDirectory'
)..
createSync
(
recursive:
true
);
fs
.
file
(
'/workDirectory/temp/auth_opt.json'
)..
createSync
(
recursive:
true
);
when
(
process
.
run
(
any
)).
thenAnswer
((
_
)
=>
Future
<
io
.
ProcessResult
>.
value
(
io
.
ProcessResult
(
123
,
0
,
''
,
''
)));
await
goldens
.
auth
(
workDirectory
);
test
(
'validates SkiaDigest'
,
()
{
final
Map
<
String
,
dynamic
>
skiaJson
=
json
.
decode
(
digestResponseTemplate
());
final
SkiaGoldDigest
digest
=
SkiaGoldDigest
.
fromJson
(
skiaJson
[
'digest'
]);
expect
(
digest
.
isValid
(
platform
,
'flutter.golden_test.1'
,
expectation
,
),
isTrue
,
);
});
// Verify that we spawned no process calls
final
VerificationResult
verifyProcessRun
=
verifyNever
(
process
.
run
(
captureAny
,
workingDirectory:
captureAnyNamed
(
'workingDirectory'
)));
expect
(
verifyProcessRun
.
callCount
,
0
);
test
(
'invalidates bad SkiaDigest - platform'
,
()
{
final
Map
<
String
,
dynamic
>
skiaJson
=
json
.
decode
(
digestResponseTemplate
(
platform:
'linux'
)
);
final
SkiaGoldDigest
digest
=
SkiaGoldDigest
.
fromJson
(
skiaJson
[
'digest'
]);
expect
(
digest
.
isValid
(
platform
,
'flutter.golden_test.1'
,
expectation
,
),
isFalse
,
);
});
test
(
'invalidates bad SkiaDigest - test name'
,
()
{
final
Map
<
String
,
dynamic
>
skiaJson
=
json
.
decode
(
digestResponseTemplate
(
testName:
'flutter.golden_test.2'
)
);
final
SkiaGoldDigest
digest
=
SkiaGoldDigest
.
fromJson
(
skiaJson
[
'digest'
]);
expect
(
digest
.
isValid
(
platform
,
'flutter.golden_test.1'
,
expectation
,
),
isFalse
,
);
});
test
(
'invalidates bad SkiaDigest - expectation'
,
()
{
final
Map
<
String
,
dynamic
>
skiaJson
=
json
.
decode
(
digestResponseTemplate
(
expectation:
'1deg543sf645erg44awqcc78'
)
);
final
SkiaGoldDigest
digest
=
SkiaGoldDigest
.
fromJson
(
skiaJson
[
'digest'
]);
expect
(
digest
.
isValid
(
platform
,
'flutter.golden_test.1'
,
expectation
,
),
isFalse
,
);
});
group
(
'FlutterGoldenFileComparator'
,
()
{
test
(
'calculates the basedir correctly'
,
()
async
{
final
MockSkiaGoldClient
goldens
=
MockSkiaGoldClient
();
final
MockLocalFileComparator
defaultComparator
=
MockLocalFileComparator
();
final
Directory
flutterRoot
=
fs
.
directory
(
'/foo'
)..
createSync
(
recursive:
true
);
final
Directory
goldensRoot
=
flutterRoot
.
childDirectory
(
'bar'
)..
createSync
(
recursive:
true
);
when
(
goldens
.
fs
).
thenReturn
(
fs
);
when
(
goldens
.
flutterRoot
).
thenReturn
(
flutterRoot
);
when
(
goldens
.
comparisonRoot
).
thenReturn
(
goldensRoot
);
when
(
defaultComparator
.
basedir
).
thenReturn
(
flutterRoot
.
childDirectory
(
'baz'
).
uri
);
final
Directory
basedir
=
FlutterGoldenFileComparator
.
getBaseDirectory
(
goldens
,
defaultComparator
);
expect
(
basedir
.
uri
,
fs
.
directory
(
'/foo/bar/baz'
).
uri
);
test
(
'invalidates bad SkiaDigest - status'
,
()
{
final
Map
<
String
,
dynamic
>
skiaJson
=
json
.
decode
(
digestResponseTemplate
(
status:
'negative'
)
);
final
SkiaGoldDigest
digest
=
SkiaGoldDigest
.
fromJson
(
skiaJson
[
'digest'
]);
expect
(
digest
.
isValid
(
platform
,
'flutter.golden_test.1'
,
expectation
,
),
isFalse
,
);
});
test
(
'sets up expectations'
,
()
async
{
url
=
Uri
.
parse
(
'https://flutter-gold.skia.org/json/expectations/commit/HEAD'
);
final
MockHttpClientResponse
mockHttpResponse
=
MockHttpClientResponse
(
utf8
.
encode
(
rawExpectationsTemplate
())
);
when
(
mockHttpClient
.
getUrl
(
url
))
.
thenAnswer
((
_
)
=>
Future
<
MockHttpClientRequest
>.
value
(
mockHttpRequest
));
when
(
mockHttpRequest
.
close
())
.
thenAnswer
((
_
)
=>
Future
<
MockHttpClientResponse
>.
value
(
mockHttpResponse
));
await
skiaClient
.
getExpectations
();
expect
(
skiaClient
.
expectations
,
isNotNull
);
expect
(
skiaClient
.
expectations
[
'flutter.golden_test.1'
],
contains
(
expectation
),
);
});
group
(
'FlutterGoldensRepositoryFileComparator'
,
()
{
MemoryFileSystem
fs
;
FlutterGoldensRepositoryFileComparator
comparator
;
test
(
'detects invalid digests SkiaDigest'
,
()
{
const
String
testName
=
'flutter.golden_test.2'
;
final
Map
<
String
,
dynamic
>
skiaJson
=
json
.
decode
(
digestResponseTemplate
());
final
SkiaGoldDigest
digest
=
SkiaGoldDigest
.
fromJson
(
skiaJson
[
'digest'
]);
expect
(
digest
.
isValid
(
platform
,
testName
,
expectation
),
isFalse
);
});
setUp
(()
{
fs
=
MemoryFileSystem
();
platform
=
FakePlatform
(
operatingSystem:
'linux'
,
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
},
test
(
'image bytes are processed properly'
,
()
async
{
final
Uri
imageUrl
=
Uri
.
parse
(
'https://flutter-gold.skia.org/img/images/
$expectation
.png'
);
final
Directory
flutterRoot
=
fs
.
directory
(
'/path/to/flutter'
)..
createSync
(
recursive:
true
);
final
Directory
goldensRoot
=
flutterRoot
.
childDirectory
(
'bin/cache/goldens'
)..
createSync
(
recursive:
true
);
final
Directory
testDirectory
=
goldensRoot
.
childDirectory
(
'test/foo/bar'
)..
createSync
(
recursive:
true
);
comparator
=
FlutterGoldensRepositoryFileComparator
(
testDirectory
.
uri
,
fs:
fs
,
platform:
platform
,
final
MockHttpClientRequest
mockImageRequest
=
MockHttpClientRequest
();
final
MockHttpImageResponse
mockImageResponse
=
MockHttpImageResponse
(
imageResponseTemplate
()
);
});
when
(
mockHttpClient
.
getUrl
(
imageUrl
))
.
thenAnswer
((
_
)
=>
Future
<
MockHttpClientRequest
>.
value
(
mockImageRequest
));
when
(
mockImageRequest
.
close
())
.
thenAnswer
((
_
)
=>
Future
<
MockHttpImageResponse
>.
value
(
mockImageResponse
));
group
(
'compare'
,
()
{
test
(
'throws if golden file is not found'
,
()
async
{
try
{
await
comparator
.
compare
(
Uint8List
.
fromList
(<
int
>[
1
,
2
,
3
]),
Uri
.
parse
(
'test.png'
));
fail
(
'TestFailure expected but not thrown'
);
}
on
TestFailure
catch
(
error
)
{
expect
(
error
.
message
,
contains
(
'Could not be compared against non-existent file'
));
}
final
List
<
int
>
masterBytes
=
await
skiaClient
.
getImageBytes
(
expectation
);
expect
(
masterBytes
,
equals
(
_kTestPngBytes
));
});
test
(
'returns false if golden bytes do not match'
,
()
async
{
final
File
goldenFile
=
fs
.
file
(
'/path/to/flutter/bin/cache/goldens/test/foo/bar/test.png'
)
..
createSync
(
recursive:
true
);
goldenFile
.
writeAsBytesSync
(<
int
>[
4
,
5
,
6
],
flush:
true
);
final
bool
result
=
await
comparator
.
compare
(
Uint8List
.
fromList
(<
int
>[
1
,
2
,
3
]),
Uri
.
parse
(
'test.png'
));
expect
(
result
,
isFalse
);
group
(
'ignores'
,
()
{
Uri
url
;
MockHttpClientRequest
mockHttpRequest
;
MockHttpClientResponse
mockHttpResponse
;
setUp
(()
{
url
=
Uri
.
parse
(
'https://flutter-gold.skia.org/json/ignores'
);
mockHttpRequest
=
MockHttpClientRequest
();
mockHttpResponse
=
MockHttpClientResponse
(
utf8
.
encode
(
ignoreResponseTemplate
(
pullRequestNumber:
pullRequestNumber
)
));
when
(
mockHttpClient
.
getUrl
(
url
))
.
thenAnswer
((
_
)
=>
Future
<
MockHttpClientRequest
>.
value
(
mockHttpRequest
));
when
(
mockHttpRequest
.
close
())
.
thenAnswer
((
_
)
=>
Future
<
MockHttpClientResponse
>.
value
(
mockHttpResponse
));
});
test
(
'returns true if golden bytes match'
,
()
async
{
final
File
goldenFile
=
fs
.
file
(
'/path/to/flutter/bin/cache/goldens/test/foo/bar/test.png'
)
..
createSync
(
recursive:
true
);
goldenFile
.
writeAsBytesSync
(<
int
>[
1
,
2
,
3
],
flush:
true
);
final
bool
result
=
await
comparator
.
compare
(
Uint8List
.
fromList
(<
int
>[
1
,
2
,
3
]),
Uri
.
parse
(
'test.png'
));
expect
(
result
,
isTrue
);
test
(
'returns true for ignored test and ignored pull request number'
,
()
async
{
expect
(
await
skiaClient
.
testIsIgnoredForPullRequest
(
pullRequestNumber
,
testName
,
),
isTrue
,
);
});
test
(
'returns false for not ignored test and ignored pull request number'
,
()
async
{
expect
(
await
skiaClient
.
testIsIgnoredForPullRequest
(
'5678'
,
testName
,
),
isFalse
,
);
});
group
(
'update'
,
()
{
test
(
'creates golden file if it does not already exist'
,
()
async
{
final
File
goldenFile
=
fs
.
file
(
'/path/to/flutter/bin/cache/goldens/test/foo/bar/test.png'
);
expect
(
goldenFile
.
existsSync
(),
isFalse
);
await
comparator
.
update
(
Uri
.
parse
(
'test.png'
),
Uint8List
.
fromList
(<
int
>[
1
,
2
,
3
]));
expect
(
goldenFile
.
existsSync
(),
isTrue
);
expect
(
goldenFile
.
readAsBytesSync
(),
<
int
>[
1
,
2
,
3
]);
test
(
'returns false for ignored test and not ignored pull request number'
,
()
async
{
expect
(
await
skiaClient
.
testIsIgnoredForPullRequest
(
pullRequestNumber
,
'failure.png'
,
),
isFalse
,
);
});
});
test
(
'overwrites golden bytes if golden file already exist'
,
()
async
{
final
File
goldenFile
=
fs
.
file
(
'/path/to/flutter/bin/cache/goldens/test/foo/bar/test.png'
)
..
createSync
(
recursive:
true
);
goldenFile
.
writeAsBytesSync
(<
int
>[
4
,
5
,
6
],
flush:
true
);
await
comparator
.
update
(
Uri
.
parse
(
'test.png'
),
Uint8List
.
fromList
(<
int
>[
1
,
2
,
3
]));
expect
(
goldenFile
.
readAsBytesSync
(),
<
int
>[
1
,
2
,
3
]);
group
(
'digest parsing'
,
()
{
Uri
url
;
MockHttpClientRequest
mockHttpRequest
;
MockHttpClientResponse
mockHttpResponse
;
setUp
(()
{
url
=
Uri
.
parse
(
'https://flutter-gold.skia.org/json/details?'
'test=flutter.golden_test.1&digest=
$expectation
'
);
mockHttpRequest
=
MockHttpClientRequest
();
when
(
mockHttpClient
.
getUrl
(
url
))
.
thenAnswer
((
_
)
=>
Future
<
MockHttpClientRequest
>.
value
(
mockHttpRequest
));
});
test
(
'succeeds when valid'
,
()
async
{
mockHttpResponse
=
MockHttpClientResponse
(
utf8
.
encode
(
digestResponseTemplate
()));
when
(
mockHttpRequest
.
close
())
.
thenAnswer
((
_
)
=>
Future
<
MockHttpClientResponse
>.
value
(
mockHttpResponse
));
expect
(
await
skiaClient
.
isValidDigestForExpectation
(
expectation
,
testName
,
),
isTrue
,
);
});
group
(
'getTestUri'
,
()
{
test
(
'incorporates version number'
,
()
{
final
Uri
key
=
comparator
.
getTestUri
(
Uri
.
parse
(
'foo.png'
),
1
);
expect
(
key
,
Uri
.
parse
(
'foo.1.png'
));
test
(
'fails when invalid'
,
()
async
{
mockHttpResponse
=
MockHttpClientResponse
(
utf8
.
encode
(
digestResponseTemplate
(
platform:
'linux'
)
));
when
(
mockHttpRequest
.
close
())
.
thenAnswer
((
_
)
=>
Future
<
MockHttpClientResponse
>.
value
(
mockHttpResponse
));
expect
(
await
skiaClient
.
isValidDigestForExpectation
(
expectation
,
testName
,
),
isFalse
,
);
});
test
(
'ignores null version number'
,
()
{
final
Uri
key
=
comparator
.
getTestUri
(
Uri
.
parse
(
'foo.png'
),
null
);
expect
(
key
,
Uri
.
parse
(
'foo.png'
));
});
});
});
group
(
'Flutter
SkiaGold
FileComparator'
,
()
{
group
(
'Flutter
Golden
FileComparator'
,
()
{
FlutterSkiaGoldFileComparator
comparator
;
setUp
(()
{
final
Directory
flutterRoot
=
fs
.
directory
(
'/path/to/flutter'
)..
createSync
(
recursive:
true
);
final
Directory
goldensRoot
=
flutterRoot
.
childDirectory
(
'bin/cache/goldens'
)..
createSync
(
recursive:
true
);
final
Directory
testDirectory
=
goldensRoot
.
childDirectory
(
'test/foo/bar'
)..
createSync
(
recursive:
true
);
final
Directory
basedir
=
fs
.
directory
(
'flutter/test/library/'
)
..
createSync
(
recursive:
true
);
comparator
=
FlutterSkiaGoldFileComparator
(
testDirectory
.
uri
,
basedir
.
uri
,
MockSkiaGoldClient
(),
fs:
fs
,
platform:
platform
,
);
});
group
(
'getTestUri'
,
()
{
test
(
'calculates the basedir correctly from defaultComparator'
,
()
async
{
final
MockLocalFileComparator
defaultComparator
=
MockLocalFileComparator
();
final
Directory
flutterRoot
=
fs
.
directory
(
platform
.
environment
[
'FLUTTER_ROOT'
])
..
createSync
(
recursive:
true
);
when
(
defaultComparator
.
basedir
).
thenReturn
(
flutterRoot
.
childDirectory
(
'baz'
).
uri
);
final
Directory
basedir
=
FlutterGoldenFileComparator
.
getBaseDirectory
(
defaultComparator
,
platform
,
);
expect
(
basedir
.
uri
,
fs
.
directory
(
'/flutter/bin/cache/pkg/skia_goldens/baz'
).
uri
,
);
});
test
(
'ignores version number'
,
()
{
final
Uri
key
=
comparator
.
getTestUri
(
Uri
.
parse
(
'foo.png'
),
1
);
expect
(
key
,
Uri
.
parse
(
'foo.png'
));
});
group
(
'Post-Submit'
,
()
{
final
MockSkiaGoldClient
mockSkiaClient
=
MockSkiaGoldClient
();
setUp
(()
{
final
Directory
basedir
=
fs
.
directory
(
'flutter/test/library/'
)
..
createSync
(
recursive:
true
);
comparator
=
FlutterSkiaGoldFileComparator
(
basedir
.
uri
,
mockSkiaClient
,
fs:
fs
,
platform:
platform
,
);
});
test
(
'correctly determines testing environment'
,
()
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'CIRRUS_CI'
:
'true'
,
'CIRRUS_PR'
:
''
,
'CIRRUS_BRANCH'
:
'master'
,
'GOLD_SERVICE_ACCOUNT'
:
'service account...'
,
},
operatingSystem:
'macos'
);
expect
(
FlutterSkiaGoldFileComparator
.
isAvailableForEnvironment
(
platform
),
isTrue
,
);
});
});
group
(
'Pre-Submit'
,
()
{
FlutterPreSubmitFileComparator
comparator
;
final
MockSkiaGoldClient
mockSkiaClient
=
MockSkiaGoldClient
();
setUp
(()
{
final
Directory
basedir
=
fs
.
directory
(
'flutter/test/library/'
)
..
createSync
(
recursive:
true
);
comparator
=
FlutterPreSubmitFileComparator
(
basedir
.
uri
,
mockSkiaClient
,
fs:
fs
,
platform:
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'CIRRUS_CI'
:
'true'
,
'CIRRUS_PR'
:
'1234'
,
},
operatingSystem:
'macos'
),
);
when
(
mockSkiaClient
.
getImageBytes
(
'55109a4bed52acc780530f7a9aeff6c0'
))
.
thenAnswer
((
_
)
=>
Future
<
List
<
int
>>.
value
(
_kTestPngBytes
));
when
(
mockSkiaClient
.
expectations
)
.
thenReturn
(
expectationsTemplate
());
when
(
mockSkiaClient
.
cleanTestName
(
'library.flutter.golden_test.1.png'
))
.
thenReturn
(
'flutter.golden_test.1'
);
when
(
mockSkiaClient
.
isValidDigestForExpectation
(
'55109a4bed52acc780530f7a9aeff6c0'
,
'library.flutter.golden_test.1.png'
,
))
.
thenAnswer
((
_
)
=>
Future
<
bool
>.
value
(
false
));
});
test
(
'correctly determines testing environment'
,
()
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'CIRRUS_CI'
:
'true'
,
'CIRRUS_PR'
:
'1234'
,
},
operatingSystem:
'macos'
);
expect
(
FlutterPreSubmitFileComparator
.
isAvailableForEnvironment
(
platform
),
isTrue
,
);
});
test
(
'comparison passes test that is ignored for this PR'
,
()
async
{
when
(
mockSkiaClient
.
getImageBytes
(
'55109a4bed52acc780530f7a9aeff6c0'
))
.
thenAnswer
((
_
)
=>
Future
<
List
<
int
>>.
value
(
_kTestPngBytes
));
when
(
mockSkiaClient
.
testIsIgnoredForPullRequest
(
'1234'
,
'library.flutter.golden_test.1.png'
,
))
.
thenAnswer
((
_
)
=>
Future
<
bool
>.
value
(
true
));
expect
(
await
comparator
.
compare
(
Uint8List
.
fromList
(
_kFailPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1.png'
),
),
isTrue
,
);
});
test
(
'fails test that is not ignored for this PR'
,
()
async
{
when
(
mockSkiaClient
.
getImageBytes
(
'55109a4bed52acc780530f7a9aeff6c0'
))
.
thenAnswer
((
_
)
=>
Future
<
List
<
int
>>.
value
(
_kTestPngBytes
));
when
(
mockSkiaClient
.
testIsIgnoredForPullRequest
(
'1234'
,
'library.flutter.golden_test.1.png'
,
))
.
thenAnswer
((
_
)
=>
Future
<
bool
>.
value
(
false
));
expect
(
await
comparator
.
compare
(
Uint8List
.
fromList
(
_kFailPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1.png'
),
),
isFalse
,
);
});
test
(
'passes non-existent baseline for new test'
,
()
async
{
expect
(
await
comparator
.
compare
(
Uint8List
.
fromList
(
_kFailPngBytes
),
Uri
.
parse
(
'flutter.new_golden_test.1.png'
),
),
isTrue
,
);
});
});
group
(
'Local'
,
()
{
FlutterLocalFileComparator
comparator
;
final
MockSkiaGoldClient
mockSkiaClient
=
MockSkiaGoldClient
();
setUp
(()
async
{
final
Directory
basedir
=
fs
.
directory
(
'flutter/test/library/'
)
..
createSync
(
recursive:
true
);
comparator
=
FlutterLocalFileComparator
(
basedir
.
uri
,
mockSkiaClient
,
fs:
fs
,
platform:
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
},
operatingSystem:
'macos'
),
);
when
(
mockSkiaClient
.
getImageBytes
(
'55109a4bed52acc780530f7a9aeff6c0'
))
.
thenAnswer
((
_
)
=>
Future
<
List
<
int
>>.
value
(
_kTestPngBytes
));
when
(
mockSkiaClient
.
expectations
)
.
thenReturn
(
expectationsTemplate
());
when
(
mockSkiaClient
.
cleanTestName
(
'library.flutter.golden_test.1.png'
))
.
thenReturn
(
'flutter.golden_test.1'
);
when
(
mockSkiaClient
.
isValidDigestForExpectation
(
'55109a4bed52acc780530f7a9aeff6c0'
,
'library.flutter.golden_test.1.png'
,
))
.
thenAnswer
((
_
)
=>
Future
<
bool
>.
value
(
false
));
});
test
(
'passes when bytes match'
,
()
async
{
expect
(
await
comparator
.
compare
(
Uint8List
.
fromList
(
_kTestPngBytes
),
Uri
.
parse
(
'flutter.golden_test.1.png'
),
),
isTrue
,
);
});
test
(
'passes non-existent baseline for new test'
,
()
async
{
expect
(
await
comparator
.
compare
(
Uint8List
.
fromList
(
_kFailPngBytes
),
Uri
.
parse
(
'flutter.new_golden_test.1'
),
),
isTrue
,
);
});
});
group
(
'Skipping'
,
()
{
test
(
'correctly determines testing environment'
,
()
{
platform
=
FakePlatform
(
environment:
<
String
,
String
>{
'FLUTTER_ROOT'
:
_kFlutterRoot
,
'SWARMING_TASK_ID'
:
'1234567890'
,
},
operatingSystem:
'macos'
);
expect
(
FlutterSkippingGoldenFileComparator
.
isAvailableForEnvironment
(
platform
),
isTrue
,
);
});
});
});
}
class
MockProcessManager
extends
Mock
implements
ProcessManager
{}
class
MockGoldensRepositoryClient
extends
Mock
implements
GoldensRepositoryClient
{}
class
MockSkiaGoldClient
extends
Mock
implements
SkiaGoldClient
{}
class
MockLocalFileComparator
extends
Mock
implements
LocalFileComparator
{}
class
MockHttpClient
extends
Mock
implements
HttpClient
{}
class
MockHttpClientRequest
extends
Mock
implements
HttpClientRequest
{}
class
MockHttpClientResponse
extends
Mock
implements
HttpClientResponse
{
MockHttpClientResponse
(
this
.
response
);
final
Uint8List
response
;
@override
StreamSubscription
<
Uint8List
>
listen
(
void
onData
(
Uint8List
event
),
{
Function
onError
,
void
onDone
(),
bool
cancelOnError
,
})
{
return
Stream
<
Uint8List
>.
fromFuture
(
Future
<
Uint8List
>.
value
(
response
))
.
listen
(
onData
,
onError:
onError
,
onDone:
onDone
,
cancelOnError:
cancelOnError
);
}
}
class
MockHttpImageResponse
extends
Mock
implements
HttpClientResponse
{
MockHttpImageResponse
(
this
.
response
);
final
List
<
List
<
int
>>
response
;
@override
Future
<
void
>
forEach
(
void
action
(
List
<
int
>
element
))
async
{
response
.
forEach
(
action
);
}
}
packages/flutter_goldens/test/json_templates.dart
0 → 100644
View file @
7bc02037
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Json response template for Skia Gold expectations request:
/// https://flutter-gold.skia.org/json/expectations/commit/HEAD
String
rawExpectationsTemplate
(
)
{
return
'''
{
"md5": "a7489b00e03a1846e43500b7c14dd7b0",
"master": {
"flutter.golden_test.1": {
"55109a4bed52acc780530f7a9aeff6c0": 1
},
"flutter.golden_test.3": {
"87cb35131e6ad4b57d4d09d59ae743c3": 1,
"dc94eb2c39c0c8ae11a4efd090b72f94": 1,
"f2583c9003978a06b7888878bdc089e2": 1
},
"flutter.golden_test.2": {
"eb03a5e3114c9ecad5e4f1178f285a49": 1,
"f14631979de24fca6e14ad247d5f2bd6": 1
}
}
}
'''
;
}
/// Decoded json response template for Skia Gold expectations request:
/// https://flutter-gold.skia.org/json/expectations/commit/HEAD
Map
<
String
,
List
<
String
>>
expectationsTemplate
()
{
return
<
String
,
List
<
String
>>{
'flutter.golden_test.1'
:
<
String
>[
'55109a4bed52acc780530f7a9aeff6c0'
],
'flutter.golden_test.3'
:
<
String
>[
'87cb35131e6ad4b57d4d09d59ae743c3'
,
'dc94eb2c39c0c8ae11a4efd090b72f94'
,
'f2583c9003978a06b7888878bdc089e2'
,
],
'flutter.golden_test.2'
:
<
String
>[
'eb03a5e3114c9ecad5e4f1178f285a49'
,
'f14631979de24fca6e14ad247d5f2bd6'
,
],
};
}
/// Json response template for Skia Gold digest request:
/// https://flutter-gold.skia.org/json/details?test=[testName]&digest=[expectation]
String
digestResponseTemplate
(
{
String
testName
=
'flutter.golden_test.1'
,
String
expectation
=
'55109a4bed52acc780530f7a9aeff6c0'
,
String
platform
=
'macos'
,
String
status
=
'positive'
,
})
{
return
'''
{
"digest": {
"test": "
$testName
",
"digest": "
$expectation
",
"status": "
$status
",
"paramset": {
"Platform": [
"
$platform
"
],
"ext": [
"png"
],
"name": [
"
$testName
"
],
"source_type": [
"flutter"
]
},
"traces": {
"tileSize": 200,
"traces": [
{
"data": [
{
"x": 0,
"y": 0,
"s": 0
},
{
"x": 1,
"y": 0,
"s": 0
},
{
"x": 199,
"y": 0,
"s": 0
}
],
"label": ",Platform=
$platform
,name=
$testName
,source_type=flutter,",
"params": {
"Platform": "
$platform
",
"ext": "png",
"name": "
$testName
",
"source_type": "flutter"
}
}
],
"digests": [
{
"digest": "
$expectation
",
"status": "
$status
"
}
]
},
"closestRef": "pos",
"refDiffs": {
"neg": null,
"pos": {
"numDiffPixels": 999,
"pixelDiffPercent": 0.4995,
"maxRGBADiffs": [
86,
86,
86,
0
],
"dimDiffer": false,
"diffs": {
"combined": 0.381955,
"percent": 0.4995,
"pixel": 999
},
"digest": "aa748136c70cefdda646df5be0ae189d",
"status": "positive",
"paramset": {
"Platform": [
"macos"
],
"ext": [
"png"
],
"name": [
"
$testName
"
],
"source_type": [
"flutter"
]
},
"n": 197
}
}
},
"commits": [
{
"commit_time": 1568069344,
"hash": "399bb04e2de41665320d3c888f40af6d8bc734a2",
"author": "Contributor A (contributorA@getMail.com)"
},
{
"commit_time": 1568078053,
"hash": "0f365d3add253a65e5e5af1024f56c6169bf9739",
"author": "Contributor B (contributorB@getMail.com)"
},
{
"commit_time": 1569353925,
"hash": "81e693a7fe3b808cc9ae2bb3a2cbe404e67ec773",
"author": "Contributor C (contributorC@getMail.com)"
}
]
}
'''
;
}
/// Json response template for Skia Gold ignore request:
/// https://flutter-gold.skia.org/json/ignores
String
ignoreResponseTemplate
(
{
String
pullRequestNumber
=
'0000'
,
String
testName
=
'flutter.golden_test.1'
,
})
{
return
'''
[
{
"id": "7579425228619212078",
"name": "contributor@getMail.com",
"updatedBy": "contributor@getMail.com",
"expires": "2019-09-06T21:28:18.815336Z",
"query": "ext=png&name=
$testName
",
"note": "https://github.com/flutter/flutter/pull/
$pullRequestNumber
"
}
]
'''
;
}
/// Json response template for Skia Gold image request:
/// https://flutter-gold.skia.org/img/images/[imageHash].png
List
<
List
<
int
>>
imageResponseTemplate
()
{
return
<
List
<
int
>>[
<
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
],
<
int
>[
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
],
];
}
packages/flutter_goldens_client/lib/client.dart
deleted
100644 → 0
View file @
4bd5eb59
// Copyright 2018 The Chromium 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:io'
as
io
;
import
'package:file/file.dart'
;
import
'package:file/local.dart'
;
import
'package:platform/platform.dart'
;
import
'package:process/process.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'
;
/// A base class that provides shared information to the
/// [FlutterGoldenFileComparator] as well as the [SkiaGoldClient] and
/// [GoldensRepositoryClient].
abstract
class
GoldensClient
{
/// Creates a handle to the local environment of golden file images.
GoldensClient
({
this
.
fs
=
const
LocalFileSystem
(),
this
.
platform
=
const
LocalPlatform
(),
this
.
process
=
const
LocalProcessManager
(),
});
/// The file system to use for storing the local clone of the repository.
///
/// This is useful in tests, where a local file system (the default) can
/// be replaced by a memory file system.
final
FileSystem
fs
;
/// A wrapper for the [dart:io.Platform] API.
///
/// This is useful in tests, where the system platform (the default) can
/// be replaced by a mock platform instance.
final
Platform
platform
;
/// A controller for launching subprocesses.
///
/// This is useful in tests, where the real process manager (the default)
/// can be replaced by a mock process manager that doesn't really create
/// subprocesses.
final
ProcessManager
process
;
/// The local [Directory] where the Flutter repository is hosted.
///
/// Uses the [fs] file system.
Directory
get
flutterRoot
=>
fs
.
directory
(
platform
.
environment
[
_kFlutterRootKey
]);
/// The local [Directory] where the goldens files are located.
///
/// Uses the [fs] file system.
Directory
get
comparisonRoot
=>
flutterRoot
.
childDirectory
(
fs
.
path
.
join
(
'bin'
,
'cache'
,
'pkg'
,
'goldens'
));
}
/// A class that represents a clone of the https://github.com/flutter/goldens
/// repository, nested within the `bin/cache` directory of the caller's Flutter
/// repository.
class
GoldensRepositoryClient
extends
GoldensClient
{
GoldensRepositoryClient
({
FileSystem
fs
=
const
LocalFileSystem
(),
ProcessManager
process
=
const
LocalProcessManager
(),
Platform
platform
=
const
LocalPlatform
(),
})
:
super
(
fs:
fs
,
process:
process
,
platform:
platform
,
);
RandomAccessFile
_lock
;
/// Prepares the local clone of the `flutter/goldens` repository for golden
/// file testing.
///
/// This ensures that the goldens repository has been cloned into its
/// expected location within `bin/cache` and that it is synced to the Git
/// revision specified in `bin/internal/goldens.version`.
///
/// While this is preparing the repository, it obtains a file lock such that
/// [GoldensClient] instances in other processes or isolates will not
/// duplicate the work that this is doing.
Future
<
void
>
prepare
()
async
{
final
String
goldensCommit
=
await
_getGoldensCommit
();
String
currentCommit
=
await
_getCurrentCommit
();
if
(
currentCommit
!=
goldensCommit
)
{
await
_obtainLock
();
try
{
// Check the current commit again now that we have the lock.
currentCommit
=
await
_getCurrentCommit
();
if
(
currentCommit
!=
goldensCommit
)
{
if
(
currentCommit
==
null
)
{
await
_initRepository
();
}
await
_checkCanSync
();
await
_syncTo
(
goldensCommit
);
}
}
finally
{
await
_releaseLock
();
}
}
}
Future
<
String
>
_getCurrentCommit
()
async
{
if
(!
comparisonRoot
.
existsSync
())
{
return
null
;
}
else
{
final
io
.
ProcessResult
revParse
=
await
process
.
run
(
<
String
>[
'git'
,
'rev-parse'
,
'HEAD'
],
workingDirectory:
comparisonRoot
.
path
,
);
return
revParse
.
exitCode
==
0
?
revParse
.
stdout
.
trim
()
:
null
;
}
}
Future
<
String
>
_getGoldensCommit
()
async
{
final
File
versionFile
=
flutterRoot
.
childFile
(
fs
.
path
.
join
(
'bin'
,
'internal'
,
'goldens.version'
));
return
(
await
versionFile
.
readAsString
()).
trim
();
}
Future
<
void
>
_initRepository
()
async
{
await
comparisonRoot
.
create
(
recursive:
true
);
await
_runCommands
(
<
String
>[
'git init'
,
'git remote add upstream https://github.com/flutter/goldens.git'
,
'git remote set-url --push upstream git@github.com:flutter/goldens.git'
,
],
workingDirectory:
comparisonRoot
,
);
}
Future
<
void
>
_checkCanSync
()
async
{
final
io
.
ProcessResult
result
=
await
process
.
run
(
<
String
>[
'git'
,
'status'
,
'--porcelain'
],
workingDirectory:
comparisonRoot
.
path
,
);
if
(
result
.
stdout
.
trim
().
isNotEmpty
)
{
final
StringBuffer
buf
=
StringBuffer
()
..
writeln
(
'flutter_goldens git checkout at
${comparisonRoot.path}
has local changes and cannot be synced.'
)
..
writeln
(
'To reset your client to a clean state, and lose any local golden test changes:'
)
..
writeln
(
'cd
${comparisonRoot.path}
'
)
..
writeln
(
'git reset --hard HEAD'
)
..
writeln
(
'git clean -x -d -f -f'
);
throw
NonZeroExitCode
(
1
,
buf
.
toString
());
}
}
Future
<
void
>
_syncTo
(
String
commit
)
async
{
await
_runCommands
(
<
String
>[
'git pull upstream master'
,
'git fetch upstream
$commit
'
,
'git reset --hard FETCH_HEAD'
,
],
workingDirectory:
comparisonRoot
,
);
}
Future
<
void
>
_runCommands
(
List
<
String
>
commands
,
{
Directory
workingDirectory
,
})
async
{
for
(
String
command
in
commands
)
{
final
List
<
String
>
parts
=
command
.
split
(
' '
);
final
io
.
ProcessResult
result
=
await
process
.
run
(
parts
,
workingDirectory:
workingDirectory
?.
path
,
);
if
(
result
.
exitCode
!=
0
)
{
throw
NonZeroExitCode
(
result
.
exitCode
,
result
.
stderr
);
}
}
}
Future
<
void
>
_obtainLock
()
async
{
final
File
lockFile
=
flutterRoot
.
childFile
(
fs
.
path
.
join
(
'bin'
,
'cache'
,
'goldens.lockfile'
));
await
lockFile
.
create
(
recursive:
true
);
_lock
=
await
lockFile
.
open
(
mode:
io
.
FileMode
.
write
);
await
_lock
.
lock
(
io
.
FileLock
.
blockingExclusive
);
}
Future
<
void
>
_releaseLock
()
async
{
await
_lock
.
close
();
_lock
=
null
;
}
}
/// Exception that signals a process' exit with a non-zero exit code.
class
NonZeroExitCode
implements
Exception
{
/// Create an exception that represents a non-zero exit code.
///
/// The first argument must be non-zero.
const
NonZeroExitCode
(
this
.
exitCode
,
this
.
stderr
)
:
assert
(
exitCode
!=
0
);
/// The code that the process will signal to the operating system.
///
/// By definition, this is not zero.
final
int
exitCode
;
/// The message to show on standard error.
final
String
stderr
;
@override
String
toString
()
{
return
'Exit code
$exitCode
:
$stderr
'
;
}
}
packages/flutter_goldens_client/lib/skia_client.dart
View file @
7bc02037
...
...
@@ -12,30 +12,50 @@ import 'package:path/path.dart' as path;
import
'package:platform/platform.dart'
;
import
'package:process/process.dart'
;
import
'package:flutter_goldens_client/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
// TODO(Piinks): This file will replace ./client.dart when transition to Skia
// Gold testing is complete
const
String
_kFlutterRootKey
=
'FLUTTER_ROOT'
;
const
String
_kGoldctlKey
=
'GOLDCTL'
;
const
String
_kServiceAccountKey
=
'GOLD_SERVICE_ACCOUNT'
;
/// An extension of the [GoldensClient] class that interfaces with Skia Gold
/// for golden file testing.
class
SkiaGoldClient
extends
GoldensClient
{
SkiaGoldClient
({
FileSystem
fs
=
const
LocalFileSystem
(),
ProcessManager
process
=
const
LocalProcessManager
(),
Platform
platform
=
const
LocalPlatform
(),
})
:
super
(
fs:
fs
,
process:
process
,
platform:
platform
,
);
/// A client for uploading image tests and making baseline requests to the
/// Flutter Gold Dashboard.
class
SkiaGoldClient
{
SkiaGoldClient
(
this
.
workDirectory
,
{
this
.
fs
=
const
LocalFileSystem
(),
this
.
process
=
const
LocalProcessManager
(),
this
.
platform
=
const
LocalPlatform
(),
io
.
HttpClient
httpClient
,
})
:
assert
(
workDirectory
!=
null
),
assert
(
fs
!=
null
),
assert
(
process
!=
null
),
assert
(
platform
!=
null
),
httpClient
=
httpClient
??
io
.
HttpClient
();
/// The file system to use for storing the local clone of the repository.
///
/// This is useful in tests, where a local file system (the default) can
/// be replaced by a memory file system.
final
FileSystem
fs
;
/// A wrapper for the [dart:io.Platform] API.
///
/// This is useful in tests, where the system platform (the default) can
/// be replaced by a mock platform instance.
final
Platform
platform
;
/// A controller for launching sub-processes.
///
/// This is useful in tests, where the real process manager (the default)
/// can be replaced by a mock process manager that doesn't really create
/// sub-processes.
final
ProcessManager
process
;
/// A client for making Http requests to the Flutter Gold dashboard.
final
io
.
HttpClient
httpClient
;
/// The local [Directory] within the [comparisonRoot] for the current test
/// context. In this directory, the client will create image and json files
...
...
@@ -43,7 +63,21 @@ class SkiaGoldClient extends GoldensClient {
///
/// This is informed by the [FlutterGoldenFileComparator] [basedir]. It cannot
/// be null.
Directory
_workDirectory
;
final
Directory
workDirectory
;
/// A map of known golden file tests and their associated positive image
/// hashes.
///
/// This is set and used by the [FlutterLocalFileComparator] and
/// [FlutterPreSubmitFileComparator] to test against golden masters maintained
/// in the Flutter Gold dashboard.
Map
<
String
,
List
<
String
>>
get
expectations
=>
_expectations
;
Map
<
String
,
List
<
String
>>
_expectations
;
/// The local [Directory] where the Flutter repository is hosted.
///
/// Uses the [fs] file system.
Directory
get
_flutterRoot
=>
fs
.
directory
(
platform
.
environment
[
_kFlutterRootKey
]);
/// The path to the local [Directory] where the goldctl tool is hosted.
///
...
...
@@ -56,9 +90,6 @@ class SkiaGoldClient extends GoldensClient {
/// Uses the [platform] environment in this implementation.
String
get
_serviceAccount
=>
platform
.
environment
[
_kServiceAccountKey
];
@override
Directory
get
comparisonRoot
=>
flutterRoot
.
childDirectory
(
fs
.
path
.
join
(
'bin'
,
'cache'
,
'pkg'
,
'skia_goldens'
));
/// Prepares the local work space for golden file testing and calls the
/// goldctl `auth` command.
///
...
...
@@ -69,39 +100,31 @@ class SkiaGoldClient extends GoldensClient {
/// The [workDirectory] parameter specifies the current directory that golden
/// tests are executing in, relative to the library of the given test. It is
/// informed by the basedir of the [FlutterSkiaGoldFileComparator].
Future
<
void
>
auth
(
Directory
workDirectory
)
async
{
assert
(
workDirectory
!=
null
);
_workDirectory
=
workDirectory
;
Future
<
void
>
auth
()
async
{
if
(
_clientIsAuthorized
())
return
;
if
(
_serviceAccount
.
isEmpty
)
{
final
StringBuffer
buf
=
StringBuffer
()..
writeln
(
'Gold service account is unavailable.'
);
final
StringBuffer
buf
=
StringBuffer
()
..
writeln
(
'Gold service account is unavailable.'
);
throw
NonZeroExitCode
(
1
,
buf
.
toString
());
}
final
File
authorization
=
_
workDirectory
.
childFile
(
'serviceAccount.json'
);
final
File
authorization
=
workDirectory
.
childFile
(
'serviceAccount.json'
);
await
authorization
.
writeAsString
(
_serviceAccount
);
final
List
<
String
>
authArguments
=
<
String
>[
'auth'
,
'--service-account'
,
authorization
.
path
,
'--work-dir'
,
_workDirectory
.
childDirectory
(
'temp'
).
path
,
'--work-dir'
,
workDirectory
.
childDirectory
(
'temp'
)
.
path
,
];
// final io.ProcessResult authResults =
await
io
.
Process
.
run
(
_goldctl
,
authArguments
,
);
// TODO(Piinks): Re-enable after Gold flakes are resolved, https://github.com/flutter/flutter/pull/36103
// if (authResults.exitCode != 0) {
// final StringBuffer buf = StringBuffer()
// ..writeln('Flutter + Skia Gold auth failed.')
// ..writeln('stdout: ${authResults.stdout}')
// ..writeln('stderr: ${authResults.stderr}');
// throw NonZeroExitCode(authResults.exitCode, buf.toString());
// }
}
/// Executes the `imgtest init` command in the goldctl tool.
...
...
@@ -109,8 +132,8 @@ class SkiaGoldClient extends GoldensClient {
/// The `imgtest` command collects and uploads test results to the Skia Gold
/// backend, the `init` argument initializes the current test.
Future
<
void
>
imgtestInit
()
async
{
final
File
keys
=
_
workDirectory
.
childFile
(
'keys.json'
);
final
File
failures
=
_
workDirectory
.
childFile
(
'failures.json'
);
final
File
keys
=
workDirectory
.
childFile
(
'keys.json'
);
final
File
failures
=
workDirectory
.
childFile
(
'failures.json'
);
await
keys
.
writeAsString
(
_getKeysJSON
());
await
failures
.
create
();
...
...
@@ -119,7 +142,9 @@ class SkiaGoldClient extends GoldensClient {
final
List
<
String
>
imgtestInitArguments
=
<
String
>[
'imgtest'
,
'init'
,
'--instance'
,
'flutter'
,
'--work-dir'
,
_workDirectory
.
childDirectory
(
'temp'
).
path
,
'--work-dir'
,
workDirectory
.
childDirectory
(
'temp'
)
.
path
,
'--commit'
,
commitHash
,
'--keys-file'
,
keys
.
path
,
'--failure-file'
,
failures
.
path
,
...
...
@@ -127,26 +152,16 @@ class SkiaGoldClient extends GoldensClient {
];
if
(
imgtestInitArguments
.
contains
(
null
))
{
final
StringBuffer
buf
=
StringBuffer
()
;
buf
.
writeln
(
'Null argument for Skia Gold imgtest init:'
);
final
StringBuffer
buf
=
StringBuffer
()
.
.
writeln
(
'Null argument for Skia Gold imgtest init:'
);
imgtestInitArguments
.
forEach
(
buf
.
writeln
);
throw
NonZeroExitCode
(
1
,
buf
.
toString
());
}
// final io.ProcessResult imgtestInitResult =
await
io
.
Process
.
run
(
_goldctl
,
imgtestInitArguments
,
);
// TODO(Piinks): Re-enable after Gold flakes are resolved, https://github.com/flutter/flutter/pull/36103
// if (imgtestInitResult.exitCode != 0) {
// final StringBuffer buf = StringBuffer()
// ..writeln('Flutter + Skia Gold imgtest init failed.')
// ..writeln('stdout: ${imgtestInitResult.stdout}')
// ..writeln('stderr: ${imgtestInitResult.stderr}');
// throw NonZeroExitCode(imgtestInitResult.exitCode, buf.toString());
// }
}
/// Executes the `imgtest add` command in the goldctl tool.
...
...
@@ -164,8 +179,10 @@ class SkiaGoldClient extends GoldensClient {
final
List
<
String
>
imgtestArguments
=
<
String
>[
'imgtest'
,
'add'
,
'--work-dir'
,
_workDirectory
.
childDirectory
(
'temp'
).
path
,
'--test-name'
,
testName
.
split
(
path
.
extension
(
testName
.
toString
()))[
0
],
'--work-dir'
,
workDirectory
.
childDirectory
(
'temp'
)
.
path
,
'--test-name'
,
cleanTestName
(
testName
),
'--png-file'
,
goldenFile
.
path
,
];
...
...
@@ -173,25 +190,145 @@ class SkiaGoldClient extends GoldensClient {
_goldctl
,
imgtestArguments
,
);
// TODO(Piinks): Comment on PR if triage is needed, https://github.com/flutter/flutter/issues/34673
// So as not to turn the tree red in this initial implementation, this will
// return true for now.
// The ProcessResult that returns from line 157 contains the pass/fail
// result of the test & links to the dashboard and diffs.
return
true
;
}
/// Requests and sets the [_expectations] known to Flutter Gold at head.
Future
<
void
>
getExpectations
()
async
{
_expectations
=
<
String
,
List
<
String
>>{};
await
io
.
HttpOverrides
.
runWithHttpOverrides
<
Future
<
void
>>(()
async
{
final
Uri
requestForExpectations
=
Uri
.
parse
(
'https://flutter-gold.skia.org/json/expectations/commit/HEAD'
);
String
rawResponse
;
try
{
final
io
.
HttpClientRequest
request
=
await
httpClient
.
getUrl
(
requestForExpectations
);
final
io
.
HttpClientResponse
response
=
await
request
.
close
();
rawResponse
=
await
utf8
.
decodeStream
(
response
);
final
Map
<
String
,
dynamic
>
skiaJson
=
json
.
decode
(
rawResponse
)[
'master'
];
skiaJson
.
forEach
((
String
key
,
dynamic
value
)
{
final
Map
<
String
,
dynamic
>
hashesMap
=
value
;
_expectations
[
key
]
=
hashesMap
.
keys
.
toList
();
});
}
on
FormatException
catch
(
_
)
{
print
(
'Formatting error detected requesting expectations from Flutter Gold.
\n
'
'rawResponse:
$rawResponse
'
);
rethrow
;
}
},
SkiaGoldHttpOverrides
(),
);
}
/// Returns a list of bytes representing the golden image retrieved from the
/// Flutter Gold dashboard.
///
/// The provided image hash represents an expectation from Flutter Gold.
Future
<
List
<
int
>>
getImageBytes
(
String
imageHash
)
async
{
final
List
<
int
>
imageBytes
=
<
int
>[];
await
io
.
HttpOverrides
.
runWithHttpOverrides
<
Future
<
void
>>(()
async
{
final
Uri
requestForImage
=
Uri
.
parse
(
'https://flutter-gold.skia.org/img/images/
$imageHash
.png'
,
);
try
{
final
io
.
HttpClientRequest
request
=
await
httpClient
.
getUrl
(
requestForImage
);
final
io
.
HttpClientResponse
response
=
await
request
.
close
();
await
response
.
forEach
((
List
<
int
>
bytes
)
=>
imageBytes
.
addAll
(
bytes
));
}
catch
(
e
)
{
rethrow
;
}
},
SkiaGoldHttpOverrides
(),
);
return
imageBytes
;
}
/// Returns a boolean value for whether or not the given test and current pull
/// request are ignored on Flutter Gold.
///
/// This is only relevant when used by the [FlutterPreSubmitFileComparator].
/// In order to land a change to an exiting golden file, an ignore must be set
/// up in Flutter Gold. This will serve as a flag to permit the change to
/// land, and protect against any unwanted changes.
Future
<
bool
>
testIsIgnoredForPullRequest
(
String
pullRequest
,
String
testName
)
async
{
bool
ignoreIsActive
=
false
;
testName
=
cleanTestName
(
testName
);
String
rawResponse
;
await
io
.
HttpOverrides
.
runWithHttpOverrides
<
Future
<
void
>>(()
async
{
final
Uri
requestForIgnores
=
Uri
.
parse
(
'https://flutter-gold.skia.org/json/ignores'
);
try
{
final
io
.
HttpClientRequest
request
=
await
httpClient
.
getUrl
(
requestForIgnores
);
final
io
.
HttpClientResponse
response
=
await
request
.
close
();
rawResponse
=
await
utf8
.
decodeStream
(
response
);
final
List
<
dynamic
>
ignores
=
json
.
decode
(
rawResponse
);
for
(
Map
<
String
,
dynamic
>
ignore
in
ignores
)
{
final
List
<
String
>
ignoredQueries
=
ignore
[
'query'
].
split
(
'&'
);
final
String
ignoredPullRequest
=
ignore
[
'note'
].
split
(
'/'
).
last
;
if
(
ignoredQueries
.
contains
(
'name=
$testName
'
)
&&
ignoredPullRequest
==
pullRequest
)
{
ignoreIsActive
=
true
;
break
;
}
}
}
on
FormatException
catch
(
_
)
{
print
(
'Formatting error detected requesting ignores from Flutter Gold.
\n
'
'rawResponse:
$rawResponse
'
);
rethrow
;
}
},
SkiaGoldHttpOverrides
(),
);
return
ignoreIsActive
;
}
/// The [_expectations] retrieved from Flutter Gold do not include the
/// parameters of the given test. This function queries the Flutter Gold
/// details api to determine if the given expectation for a test matches the
/// configuration of the executing machine.
Future
<
bool
>
isValidDigestForExpectation
(
String
expectation
,
String
testName
)
async
{
bool
isValid
=
false
;
testName
=
cleanTestName
(
testName
);
String
rawResponse
;
await
io
.
HttpOverrides
.
runWithHttpOverrides
<
Future
<
void
>>(()
async
{
final
Uri
requestForDigest
=
Uri
.
parse
(
'https://flutter-gold.skia.org/json/details?test=
$testName
&digest=
$expectation
'
);
try
{
final
io
.
HttpClientRequest
request
=
await
httpClient
.
getUrl
(
requestForDigest
);
final
io
.
HttpClientResponse
response
=
await
request
.
close
();
rawResponse
=
await
utf8
.
decodeStream
(
response
);
final
Map
<
String
,
dynamic
>
skiaJson
=
json
.
decode
(
rawResponse
);
final
SkiaGoldDigest
digest
=
SkiaGoldDigest
.
fromJson
(
skiaJson
[
'digest'
]);
isValid
=
digest
.
isValid
(
platform
,
testName
,
expectation
);
}
on
FormatException
catch
(
_
)
{
print
(
'Formatting error detected requesting digest from Flutter Gold.
\n
'
'rawResponse:
$rawResponse
'
);
rethrow
;
}
},
SkiaGoldHttpOverrides
(),
);
return
isValid
;
}
/// Returns the current commit hash of the Flutter repository.
Future
<
String
>
_getCurrentCommit
()
async
{
if
(!
flutterRoot
.
existsSync
())
{
if
(!
_
flutterRoot
.
existsSync
())
{
final
StringBuffer
buf
=
StringBuffer
()
..
writeln
(
'Flutter root could not be found:
$flutterRoot
'
);
..
writeln
(
'Flutter root could not be found:
$
_
flutterRoot
'
);
throw
NonZeroExitCode
(
1
,
buf
.
toString
());
}
else
{
final
io
.
ProcessResult
revParse
=
await
process
.
run
(
<
String
>[
'git'
,
'rev-parse'
,
'HEAD'
],
workingDirectory:
flutterRoot
.
path
,
workingDirectory:
_
flutterRoot
.
path
,
);
return
revParse
.
exitCode
==
0
?
revParse
.
stdout
.
trim
()
:
null
;
}
...
...
@@ -210,13 +347,85 @@ class SkiaGoldClient extends GoldensClient {
);
}
/// Removes the file extension from the [fileName] to represent the test name
/// properly.
String
cleanTestName
(
String
fileName
)
{
return
fileName
.
split
(
path
.
extension
(
fileName
.
toString
()))[
0
];
}
/// Returns a boolean value to prevent the client from re-authorizing itself
/// for multiple tests.
bool
_clientIsAuthorized
()
{
final
File
authFile
=
_workDirectory
?.
childFile
(
super
.
fs
.
path
.
join
(
final
File
authFile
=
workDirectory
?.
childFile
(
fs
.
path
.
join
(
'temp'
,
'auth_opt.json'
,
));
return
authFile
.
existsSync
();
}
}
/// Used to make HttpRequests during testing.
class
SkiaGoldHttpOverrides
extends
io
.
HttpOverrides
{}
/// A digest returned from a request to the Flutter Gold dashboard.
class
SkiaGoldDigest
{
const
SkiaGoldDigest
({
this
.
imageHash
,
this
.
paramSet
,
this
.
testName
,
this
.
status
,
});
/// Create a digest from requested json.
factory
SkiaGoldDigest
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
if
(
json
==
null
)
return
null
;
return
SkiaGoldDigest
(
imageHash:
json
[
'digest'
],
paramSet:
Map
<
String
,
dynamic
>.
from
(
json
[
'paramset'
]
??
<
String
,
String
>{
'Platform'
:
'none'
}),
testName:
json
[
'test'
],
status:
json
[
'status'
],
);
}
/// Unique identifier for the image associated with the digest.
final
String
imageHash
;
/// Parameter set for the given test, e.g. Platform : Windows.
final
Map
<
String
,
dynamic
>
paramSet
;
/// Test name associated with the digest, e.g. positive or untriaged.
final
String
testName
;
/// Status of the given digest, e.g. positive or untriaged.
final
String
status
;
/// Validates a given digest against the current testing conditions.
bool
isValid
(
Platform
platform
,
String
name
,
String
expectation
)
{
return
imageHash
==
expectation
&&
paramSet
[
'Platform'
].
contains
(
platform
.
operatingSystem
)
&&
testName
==
name
&&
status
==
'positive'
;
}
}
/// Exception that signals a process' exit with a non-zero exit code.
class
NonZeroExitCode
implements
Exception
{
/// Create an exception that represents a non-zero exit code.
///
/// The first argument must be non-zero.
const
NonZeroExitCode
(
this
.
exitCode
,
this
.
stderr
)
:
assert
(
exitCode
!=
0
);
/// The code that the process will signal to the operating system.
///
/// By definition, this is not zero.
final
int
exitCode
;
/// The message to show on standard error.
final
String
stderr
;
@override
String
toString
()
=>
'Exit code
$exitCode
:
$stderr
'
;
}
packages/flutter_test/lib/src/_goldens_io.dart
View file @
7bc02037
...
...
@@ -15,8 +15,8 @@ import 'goldens.dart';
/// The default [GoldenFileComparator] implementation for `flutter test`.
///
/// The term __golden file__ refers to a master image that is considered the
true
/// rendering of a given widget, state, application, or other visual
/// The term __golden file__ refers to a master image that is considered the
///
true
rendering of a given widget, state, application, or other visual
/// representation you have chosen to capture. This comparator loads golden
/// files from the local file system, treating the golden key as a relative
/// path from the test file's directory.
...
...
@@ -53,7 +53,7 @@ import 'goldens.dart';
/// implements.
/// * [matchesGoldenFile], the function from [flutter_test] that invokes the
/// comparator.
class
LocalFileComparator
extends
GoldenFileComparator
{
class
LocalFileComparator
extends
GoldenFileComparator
with
LocalComparisonOutput
{
/// Creates a new [LocalFileComparator] for the specified [testFile].
///
/// Golden file keys will be interpreted as file paths relative to the
...
...
@@ -90,24 +90,18 @@ class LocalFileComparator extends GoldenFileComparator {
Future
<
bool
>
compare
(
Uint8List
imageBytes
,
Uri
golden
)
async
{
final
File
goldenFile
=
_getGoldenFile
(
golden
);
if
(!
goldenFile
.
existsSync
())
{
throw
test_package
.
TestFailure
(
'Could not be compared against non-existent file: "
$golden
"'
);
throw
test_package
.
TestFailure
(
'Could not be compared against non-existent file: "
$golden
"'
);
}
final
List
<
int
>
goldenBytes
=
await
goldenFile
.
readAsBytes
();
final
ComparisonResult
result
=
GoldenFileComparator
.
compareLists
(
imageBytes
,
goldenBytes
);
final
ComparisonResult
result
=
GoldenFileComparator
.
compareLists
(
imageBytes
,
goldenBytes
,
);
if
(!
result
.
passed
)
{
String
additionalFeedback
=
''
;
if
(
result
.
diffs
!=
null
)
{
additionalFeedback
=
'
\n
Failure feedback can be found at
${path.join(basedir.path, 'failures')}
'
;
final
Map
<
String
,
Object
>
diffs
=
result
.
diffs
;
diffs
.
forEach
((
String
name
,
Object
untypedImage
)
{
final
Image
image
=
untypedImage
;
final
File
output
=
_getFailureFile
(
name
,
golden
);
output
.
parent
.
createSync
(
recursive:
true
);
output
.
writeAsBytesSync
(
encodePng
(
image
));
});
}
throw
test_package
.
TestFailure
(
'Golden "
$golden
":
${result.error}$additionalFeedback
'
);
generateFailureOutput
(
result
,
golden
,
basedir
);
}
return
result
.
passed
;
}
...
...
@@ -122,16 +116,50 @@ class LocalFileComparator extends GoldenFileComparator {
File
_getGoldenFile
(
Uri
golden
)
{
return
File
(
_path
.
join
(
_path
.
fromUri
(
basedir
),
_path
.
fromUri
(
golden
.
path
)));
}
}
/// A class for use in golden file comparators that run locally and provide
/// output.
class
LocalComparisonOutput
{
/// Writes out diffs from the [ComparisonResult] of a golden file test.
///
/// Will throw an error if a null result is provided.
void
generateFailureOutput
(
ComparisonResult
result
,
Uri
golden
,
Uri
basedir
,
{
String
key
=
''
,
})
{
String
additionalFeedback
=
''
;
if
(
result
.
diffs
!=
null
)
{
additionalFeedback
=
'
\n
Failure feedback can be found at '
'
${path.join(basedir.path, 'failures')}
'
;
final
Map
<
String
,
Image
>
diffs
=
result
.
diffs
;
diffs
.
forEach
((
String
name
,
Image
image
)
{
final
File
output
=
getFailureFile
(
key
.
isEmpty
?
name
:
name
+
'_'
+
key
,
golden
,
basedir
,
);
output
.
parent
.
createSync
(
recursive:
true
);
output
.
writeAsBytesSync
(
encodePng
(
image
));
});
}
throw
test_package
.
TestFailure
(
'Golden "
$golden
":
${result.error}$additionalFeedback
'
);
}
File
_getFailureFile
(
String
failure
,
Uri
golden
)
{
/// Returns the appropriate file for a given diff from a [ComparisonResult].
File
getFailureFile
(
String
failure
,
Uri
golden
,
Uri
basedir
)
{
final
String
fileName
=
golden
.
pathSegments
[
0
];
final
String
testName
=
fileName
.
split
(
path
.
extension
(
fileName
))[
0
]
+
'_'
+
failure
+
'.png'
;
return
File
(
_
path
.
join
(
_
path
.
fromUri
(
basedir
),
_
path
.
fromUri
(
Uri
.
parse
(
'failures/
$testName
'
)),
return
File
(
path
.
join
(
path
.
fromUri
(
basedir
),
path
.
fromUri
(
Uri
.
parse
(
'failures/
$testName
'
)),
));
}
}
...
...
@@ -203,7 +231,9 @@ ComparisonResult compareLists(List<int> test, List<int> master) {
if
(
pixelDiffCount
>
0
)
{
return
ComparisonResult
(
passed:
false
,
error:
'Pixel test failed,
${((pixelDiffCount/totalPixels) * 100).toStringAsFixed(2)}
% diff detected.'
,
error:
'Pixel test failed, '
'
${((pixelDiffCount/totalPixels) * 100).toStringAsFixed(2)}
% '
'diff detected.'
,
diffs:
diffs
,
);
}
...
...
packages/flutter_test/lib/src/matchers.dart
View file @
7bc02037
...
...
@@ -303,9 +303,7 @@ Matcher coversSameAreaAs(Path expectedPath, { @required Rect areaToCompare, int
/// The [key] may be either a [Uri] or a [String] representation of a URI.
///
/// The [version] is a number that can be used to differentiate historical
/// golden files. This parameter is optional. Version numbers are used in golden
/// file tests for package:flutter. You can learn more about these tests
/// [here](https://github.com/flutter/flutter/wiki/Writing-a-golden-file-test-for-package:flutter).
/// golden files. This parameter is optional.
///
/// This is an asynchronous matcher, meaning that callers should use
/// [expectLater] when using this matcher and await the future returned by
...
...
@@ -336,7 +334,10 @@ Matcher coversSameAreaAs(Path expectedPath, { @required Rect areaToCompare, int
///
/// await expectLater(
/// imageFuture,
/// matchesGoldenFile('save.png'),
/// matchesGoldenFile(
/// 'save.png',
/// version: 2,
/// ),
/// );
///
/// await expectLater(
...
...
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