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
8df0d655
Unverified
Commit
8df0d655
authored
Oct 21, 2019
by
Kate Lovett
Committed by
GitHub
Oct 21, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Skia Gold Support for Local & PreSubmit Testing in package:flutter (#40710)
parent
31a4ff80
Changes
40
Show whitespace changes
Inline
Side-by-side
Showing
40 changed files
with
1434 additions
and
1062 deletions
+1434
-1062
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
+294
-139
flutter_goldens_test.dart
packages/flutter_goldens/test/flutter_goldens_test.dart
+480
-129
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 @
31a4ff80
65bd07204149c4f7612bbf179cf088a2d69ca549
packages/flutter/test/cupertino/activity_indicator_test.dart
View file @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -375,10 +375,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.ClipRect.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.ClipRect.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -418,10 +415,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.ClipRectOverlay.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.ClipRectOverlay.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -470,10 +464,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.ClipRRect.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.ClipRRect.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -516,10 +507,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.ClipOval.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.ClipOval.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -567,10 +555,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.ClipPath.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.ClipPath.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -615,10 +600,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
);
...
...
@@ -626,10 +608,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
);
...
...
@@ -639,10 +618,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
);
...
...
@@ -684,10 +660,7 @@ void main() {
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
).
first
,
matchesGoldenFile
(
'clip.PhysicalModel.default.png'
,
version:
1
,
),
matchesGoldenFile
(
'clip.PhysicalModel.default.png'
),
);
},
skip:
isBrowser
);
...
...
@@ -736,10 +709,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
);
...
...
@@ -747,10 +717,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
);
...
...
@@ -758,10 +725,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
);
...
...
@@ -807,10 +771,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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -11,49 +11,93 @@ 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
(
);
if
(
FlutterSkiaGoldFileComparator
.
isAvailable
ForEnvironment
(
platform
))
{
goldenFileComparator
=
await
FlutterSkiaGoldFileComparator
.
fromDefaultComparator
(
platform
);
}
else
if
(
Flutter
PreSubmitFileComparator
.
isAvailableForEnvironment
(
platform
))
{
goldenFileComparator
=
await
Flutter
PreSubmitFileComparator
.
fromDefaultComparator
(
platform
);
}
else
{
goldenFileComparator
=
FlutterSkippingGoldenFileComparator
.
fromDefaultComparator
(
);
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.
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,222 +113,333 @@ 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
;
}
/// 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
());
return
cirrusCI
.
isNotEmpty
&&
cirrusPR
.
isNotEmpty
;
}
}
/// A [FlutterGoldenFileComparator] for skipping golden image tests when Skia
/// Gold is unavailable or the current platform that is executing tests is not
/// Linux.
/// 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:
///
/// * [FlutterGoldensRepositoryFileComparator], another
/// [FlutterGoldenFileComparator] that tests golden images using the
/// flutter/goldens repository.
/// * [FlutterSkiaGoldFileComparator], another [FlutterGoldenFileComparator]
/// that tests golden images through Skia Gold.
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
);
/// Creates a new [FlutterSkippingGoldenFileComparator] that mirrors the relative
/// path resolution of the default [goldenFileComparator].
static
FlutterSkippingGoldenFileComparator
fromDefaultComparator
({
/// * [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
;
return
FlutterSkippingGoldenFileComparator
(
defaultComparator
.
basedir
);
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
{
print
(
'Skipping "
$golden
" test : Skia Gold is not available in this testing '
'environment and flutter/goldens repository comparison is only available '
'on Linux machines.'
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
;
}
@override
Future
<
void
>
update
(
Uri
golden
,
Uint8List
imageBytes
)
=>
null
;
ComparisonResult
result
;
final
Map
<
String
,
ComparisonResult
>
validFailures
=
<
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
;
}
else
if
(
await
skiaClient
.
isValidDigestForExpectation
(
expectation
,
golden
.
path
))
{
validFailures
[
expectation
]
=
result
;
}
}
validFailures
.
forEach
((
String
expectation
,
ComparisonResult
result
)
{
generateFailureOutput
(
result
,
golden
,
basedir
,
key:
expectation
);
});
return
false
;
}
}
packages/flutter_goldens/test/flutter_goldens_test.dart
View file @
8df0d655
...
...
@@ -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,544 @@ 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
,
);
});
});
});
}
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 @
8df0d655
// 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 @
31a4ff80
// 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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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 @
8df0d655
...
...
@@ -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