Unverified Commit eb0b1790 authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

Part 1: Skia Gold Testing (#33688)

* Fresh PR for Gold integration.

* Nits

* WIP

* Artifacts from merge

* Changed some platform dependencies for web, added library prefix notation for Skia Gold test names.

* Updating for CI implementation

* Write out service account

* Writing to skip out

* WIP

* ++

* Fixing depot tools deps

* Windows depot_tools

* Fixing setup scripts

* ++

* depot tools

* ++

* WIP

* Tracing depot_tools clone

* WIP

* ++

* analyzer

* WIP

* chrome typo

* copy artifact

* Working on tests

* Code cleanup

* ++

* Code cleanup, updated tests

* ++ review feedback

* Review

* Analyzer

* Review feedback

* Nits from review

* PRogress

* ++

* Fixing tests

* ++

* Testing repo route

* Just needing documention around new structures.

* cleanup

* Analyzer

* Documentation updates

* Documentation updates

* Cirrus updates

* cirrus nit

* Review feedback

* Review feedback

* Fixing skip comparator

* Fix base directory for Skia Gold case

* ++

* Feedback

* ++

* Fixed uri assertion

* Made GoldensClient abstract, altered SkiaGoldClient constructor

* Analyzer
parent fc7bd6ad
...@@ -72,7 +72,11 @@ task: ...@@ -72,7 +72,11 @@ task:
env: env:
GCLOUD_SERVICE_ACCOUNT_KEY: ENCRYPTED[f12abe60f5045d619ef4c79b83dd1e0722a0b0b13dbea95fbe334e2db7fffbcd841a5a92da8824848b539a19afe0c9fb] GCLOUD_SERVICE_ACCOUNT_KEY: ENCRYPTED[f12abe60f5045d619ef4c79b83dd1e0722a0b0b13dbea95fbe334e2db7fffbcd841a5a92da8824848b539a19afe0c9fb]
SHARD: tests SHARD: tests
DEPOT_TOOLS: "tmp/depot_tools"
GOLDCTL: "$DEPOT_TOOLS/goldctl"
GOLD_SERVICE_ACCOUNT: ENCRYPTED[3afeea5ac7201151c3d0dc9648862f0462b5e4f55dc600ca8b692319622f7c3eda3d577b1b16cc2ef0311b7314c1c095]
SUBSHARD: framework_other SUBSHARD: framework_other
goldctl_script: ./dev/bots/download_goldctl.sh
test_script: test_script:
- dart --enable-asserts ./dev/bots/test.dart - dart --enable-asserts ./dev/bots/test.dart
container: container:
...@@ -317,6 +321,9 @@ task: ...@@ -317,6 +321,9 @@ task:
GCLOUD_SERVICE_ACCOUNT_KEY: ENCRYPTED[f12abe60f5045d619ef4c79b83dd1e0722a0b0b13dbea95fbe334e2db7fffbcd841a5a92da8824848b539a19afe0c9fb] GCLOUD_SERVICE_ACCOUNT_KEY: ENCRYPTED[f12abe60f5045d619ef4c79b83dd1e0722a0b0b13dbea95fbe334e2db7fffbcd841a5a92da8824848b539a19afe0c9fb]
SHARD: tests SHARD: tests
SUBSHARD: framework_other SUBSHARD: framework_other
GOLDCTL: "C:\\Windows\\Temp\\goldctl_tool\\goldctl.exe"
GOLD_SERVICE_ACCOUNT: ENCRYPTED[3afeea5ac7201151c3d0dc9648862f0462b5e4f55dc600ca8b692319622f7c3eda3d577b1b16cc2ef0311b7314c1c095]
goldctl_script: powershell dev\bots\download_goldctl.ps1
test_all_script: test_all_script:
- dart --enable-asserts dev\bots\test.dart - dart --enable-asserts dev\bots\test.dart
- name: tests_extras-windows - name: tests_extras-windows
...@@ -493,6 +500,10 @@ task: ...@@ -493,6 +500,10 @@ task:
GCLOUD_SERVICE_ACCOUNT_KEY: ENCRYPTED[f12abe60f5045d619ef4c79b83dd1e0722a0b0b13dbea95fbe334e2db7fffbcd841a5a92da8824848b539a19afe0c9fb] GCLOUD_SERVICE_ACCOUNT_KEY: ENCRYPTED[f12abe60f5045d619ef4c79b83dd1e0722a0b0b13dbea95fbe334e2db7fffbcd841a5a92da8824848b539a19afe0c9fb]
SHARD: tests SHARD: tests
SUBSHARD: extras SUBSHARD: extras
DEPOT_TOOLS: "tmp/depot_tools"
GOLDCTL: "$DEPOT_TOOLS/goldctl"
GOLD_SERVICE_ACCOUNT: ENCRYPTED[3afeea5ac7201151c3d0dc9648862f0462b5e4f55dc600ca8b692319622f7c3eda3d577b1b16cc2ef0311b7314c1c095]
goldctl_script: ./dev/bots/download_goldctl.sh
test_all_script: test_all_script:
- ulimit -S -n 2048 # https://github.com/flutter/flutter/issues/2976 - ulimit -S -n 2048 # https://github.com/flutter/flutter/issues/2976
- dart --enable-asserts dev/bots/test.dart - dart --enable-asserts dev/bots/test.dart
......
$url= "https://chrome-infra-packages.appspot.com/p/skia/tools/goldctl/windows-amd64/+/"
$path = "c:\Windows\Temp\goldctl.zip"
(New-Object System.Net.WebClient).DownloadFile($path, $output)
Expand-Archive -LiteralPath $path -DestinationPath "C:\Windows\Temp\goldctl_tool"
#!/usr/bin/env bash
git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git ./tmp/depot_tools
cd tmp/depot_tools
echo -e '# Ensure File\n$ServiceURL https://chrome-infra-packages.appspot.com\n\n# Skia Gold Client goldctl\nskia/tools/goldctl/${platform} latest' > ensure.txt
./cipd ensure -ensure-file ensure.txt -root .
...@@ -847,7 +847,6 @@ void main() { ...@@ -847,7 +847,6 @@ void main() {
'date_picker_test.datetime.initial.png', 'date_picker_test.datetime.initial.png',
version: 1, version: 1,
), ),
skip: !isLinux
); );
// Slightly drag the hour component to make the current hour off-center. // Slightly drag the hour component to make the current hour off-center.
...@@ -860,7 +859,6 @@ void main() { ...@@ -860,7 +859,6 @@ void main() {
'date_picker_test.datetime.drag.png', 'date_picker_test.datetime.drag.png',
version: 1, version: 1,
), ),
skip: !isLinux
); );
}); });
}); });
......
...@@ -805,9 +805,6 @@ void main() { ...@@ -805,9 +805,6 @@ void main() {
), ),
); );
}, },
// TODO(xster): remove once https://github.com/flutter/flutter/issues/17483
// is fixed.
skip: !isLinux,
); );
testWidgets( testWidgets(
...@@ -842,10 +839,7 @@ void main() { ...@@ -842,10 +839,7 @@ void main() {
), ),
); );
}, },
// TODO(xster): remove once https://github.com/flutter/flutter/issues/17483 );
// is fixed.
skip: !isLinux,
);
testWidgets('NavBar draws a light system bar for a dark background', (WidgetTester tester) async { testWidgets('NavBar draws a light system bar for a dark background', (WidgetTester tester) async {
......
...@@ -1417,7 +1417,7 @@ void main() { ...@@ -1417,7 +1417,7 @@ void main() {
version: 0, version: 0,
), ),
); );
}, skip: !isLinux); });
testWidgets('Golden Test Pressed State', (WidgetTester tester) async { testWidgets('Golden Test Pressed State', (WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{}; final Map<int, Widget> children = <int, Widget>{};
...@@ -1458,5 +1458,5 @@ void main() { ...@@ -1458,5 +1458,5 @@ void main() {
version: 0, version: 0,
), ),
); );
}, skip: !isLinux); });
} }
...@@ -481,7 +481,7 @@ void main() { ...@@ -481,7 +481,7 @@ void main() {
version: 2, version: 2,
), ),
); );
}, skip: !isLinux); });
testWidgets('Cupertino cursor iOS golden', (WidgetTester tester) async { testWidgets('Cupertino cursor iOS golden', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS; debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
...@@ -514,7 +514,7 @@ void main() { ...@@ -514,7 +514,7 @@ void main() {
version: 2, version: 2,
), ),
); );
}, skip: !isLinux); });
testWidgets( testWidgets(
'can control text content via controller', 'can control text content via controller',
...@@ -2901,7 +2901,6 @@ void main() { ...@@ -2901,7 +2901,6 @@ void main() {
'text_field_test.disabled.png', 'text_field_test.disabled.png',
version: 0, version: 0,
), ),
skip: !isLinux,
); );
}); });
......
...@@ -75,7 +75,6 @@ void main() { ...@@ -75,7 +75,6 @@ void main() {
'bottom_app_bar.custom_shape.1.png', 'bottom_app_bar.custom_shape.1.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
await pump(FloatingActionButtonLocation.centerDocked); await pump(FloatingActionButtonLocation.centerDocked);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -85,7 +84,6 @@ void main() { ...@@ -85,7 +84,6 @@ void main() {
'bottom_app_bar.custom_shape.2.png', 'bottom_app_bar.custom_shape.2.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
......
...@@ -84,7 +84,6 @@ void main() { ...@@ -84,7 +84,6 @@ void main() {
'bottom_app_bar_theme.custom_shape.png', 'bottom_app_bar_theme.custom_shape.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
......
...@@ -1438,7 +1438,6 @@ void main() { ...@@ -1438,7 +1438,6 @@ void main() {
'bottom_navigation_bar.shifting_transition.$pump.png', 'bottom_navigation_bar.shifting_transition.$pump.png',
version: 2, version: 2,
), ),
skip: !isLinux,
); );
} }
}, skip: isBrowser); }, skip: isBrowser);
......
...@@ -141,7 +141,6 @@ void main() { ...@@ -141,7 +141,6 @@ void main() {
'card_theme.custom_shape.png', 'card_theme.custom_shape.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
} }
......
...@@ -134,7 +134,6 @@ void main() { ...@@ -134,7 +134,6 @@ void main() {
'dialog_theme.dialog_with_custom_border.png', 'dialog_theme.dialog_with_custom_border.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
......
...@@ -144,7 +144,6 @@ void main() { ...@@ -144,7 +144,6 @@ void main() {
'dropdown_test.default.png', 'dropdown_test.default.png',
version: 0, version: 0,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
...@@ -160,7 +159,6 @@ void main() { ...@@ -160,7 +159,6 @@ void main() {
'dropdown_test.expanded.png', 'dropdown_test.expanded.png',
version: 0, version: 0,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
......
...@@ -740,7 +740,6 @@ void main() { ...@@ -740,7 +740,6 @@ void main() {
'floating_action_button_test.clip.png', 'floating_action_button_test.clip.png',
version: 2, version: 2,
), ),
skip: !isLinux,
); );
}); });
......
...@@ -2844,7 +2844,6 @@ void main() { ...@@ -2844,7 +2844,6 @@ void main() {
'input_decorator.outline_icon_label.ltr.png', 'input_decorator.outline_icon_label.ltr.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
await tester.pumpWidget(buildFrame(TextDirection.rtl)); await tester.pumpWidget(buildFrame(TextDirection.rtl));
...@@ -2854,10 +2853,8 @@ void main() { ...@@ -2854,10 +2853,8 @@ void main() {
'input_decorator.outline_icon_label.rtl.png', 'input_decorator.outline_icon_label.rtl.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, },
skip: !isLinux,
); );
testWidgets('InputDecorator draws and animates hoverColor', (WidgetTester tester) async { testWidgets('InputDecorator draws and animates hoverColor', (WidgetTester tester) async {
......
...@@ -620,7 +620,6 @@ void main() { ...@@ -620,7 +620,6 @@ void main() {
'material.border_paint_above.png', 'material.border_paint_above.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
...@@ -664,7 +663,6 @@ void main() { ...@@ -664,7 +663,6 @@ void main() {
'material.border_paint_below.png', 'material.border_paint_below.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
}); });
......
...@@ -280,7 +280,6 @@ void main() { ...@@ -280,7 +280,6 @@ void main() {
'radio.ink_ripple.png', 'radio.ink_ripple.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
} }
......
...@@ -271,7 +271,6 @@ void main() { ...@@ -271,7 +271,6 @@ void main() {
'tab_bar_theme.tab_indicator_size_tab.png', 'tab_bar_theme.tab_indicator_size_tab.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
...@@ -286,7 +285,6 @@ void main() { ...@@ -286,7 +285,6 @@ void main() {
'tab_bar_theme.tab_indicator_size_label.png', 'tab_bar_theme.tab_indicator_size_label.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
...@@ -306,7 +304,6 @@ void main() { ...@@ -306,7 +304,6 @@ void main() {
'tab_bar_theme.custom_tab_indicator.png', 'tab_bar_theme.custom_tab_indicator.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
...@@ -326,7 +323,6 @@ void main() { ...@@ -326,7 +323,6 @@ void main() {
'tab_bar_theme.beveled_rect_indicator.png', 'tab_bar_theme.beveled_rect_indicator.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
} }
...@@ -416,7 +416,7 @@ void main() { ...@@ -416,7 +416,7 @@ void main() {
version: 0, version: 0,
), ),
); );
}, skip: !isLinux); });
testWidgets('Material cursor iOS golden', (WidgetTester tester) async { testWidgets('Material cursor iOS golden', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS; debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
...@@ -448,7 +448,7 @@ void main() { ...@@ -448,7 +448,7 @@ void main() {
version: 0, version: 0,
), ),
); );
}, skip: !isLinux); });
testWidgets('text field selection toolbar renders correctly inside opacity', (WidgetTester tester) async { testWidgets('text field selection toolbar renders correctly inside opacity', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -501,7 +501,6 @@ void main() { ...@@ -501,7 +501,6 @@ void main() {
'text_field_opacity_test.0.png', 'text_field_opacity_test.0.png',
version: 2, version: 2,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
......
...@@ -75,7 +75,6 @@ void main() { ...@@ -75,7 +75,6 @@ void main() {
'continuous_rectangle_border.golden_test_even_radii.png', 'continuous_rectangle_border.golden_test_even_radii.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
...@@ -100,7 +99,6 @@ void main() { ...@@ -100,7 +99,6 @@ void main() {
'continuous_rectangle_border.golden_test_varying_radii.png', 'continuous_rectangle_border.golden_test_varying_radii.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
...@@ -122,7 +120,6 @@ void main() { ...@@ -122,7 +120,6 @@ void main() {
'continuous_rectangle_border.golden_test_large_radii.png', 'continuous_rectangle_border.golden_test_large_radii.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
......
...@@ -55,7 +55,6 @@ void main() { ...@@ -55,7 +55,6 @@ void main() {
), ),
); );
}, },
skip: !isLinux,
); );
testWidgets( testWidgets(
...@@ -110,7 +109,6 @@ void main() { ...@@ -110,7 +109,6 @@ void main() {
), ),
); );
}, },
skip: !isLinux,
); );
testWidgets( testWidgets(
...@@ -157,7 +155,6 @@ void main() { ...@@ -157,7 +155,6 @@ void main() {
), ),
); );
}, },
skip: !isLinux,
); );
} }
...@@ -47,7 +47,6 @@ void main() { ...@@ -47,7 +47,6 @@ void main() {
'backdrop_filter_test.cull_rect.png', 'backdrop_filter_test.cull_rect.png',
version: 1, version: 1,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
} }
...@@ -95,7 +95,7 @@ void main() { ...@@ -95,7 +95,7 @@ void main() {
version: 3, version: 3,
), ),
); );
}, skip: !isLinux); });
testWidgets('cursor layout has correct radius', (WidgetTester tester) async { testWidgets('cursor layout has correct radius', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>(); final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
...@@ -149,7 +149,7 @@ void main() { ...@@ -149,7 +149,7 @@ void main() {
version: 3, version: 3,
), ),
); );
}, skip: !isLinux); });
testWidgets('Cursor animates on iOS', (WidgetTester tester) async { testWidgets('Cursor animates on iOS', (WidgetTester tester) async {
final Widget widget = MaterialApp( final Widget widget = MaterialApp(
...@@ -759,6 +759,5 @@ void main() { ...@@ -759,6 +759,5 @@ void main() {
), ),
); );
debugDefaultTargetPlatformOverride = null; debugDefaultTargetPlatformOverride = null;
}, skip: !isLinux); });
} }
...@@ -24,7 +24,6 @@ void main() { ...@@ -24,7 +24,6 @@ void main() {
'invert_colors_test.0.png', 'invert_colors_test.0.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
...@@ -46,7 +45,6 @@ void main() { ...@@ -46,7 +45,6 @@ void main() {
'invert_colors_test.1.png', 'invert_colors_test.1.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
} }
......
...@@ -539,7 +539,6 @@ void main() { ...@@ -539,7 +539,6 @@ void main() {
'list_wheel_scroll_view.center_child.magnified.png', 'list_wheel_scroll_view.center_child.magnified.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
...@@ -597,7 +596,6 @@ void main() { ...@@ -597,7 +596,6 @@ void main() {
'list_wheel_scroll_view.curved_wheel.left.png', 'list_wheel_scroll_view.curved_wheel.left.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
......
...@@ -181,7 +181,6 @@ void main() { ...@@ -181,7 +181,6 @@ void main() {
'opacity_test.offset.png', 'opacity_test.offset.png',
version: 1, version: 1,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
......
...@@ -114,7 +114,6 @@ void main() { ...@@ -114,7 +114,6 @@ void main() {
'physical_model_overflow.png', 'physical_model_overflow.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
......
...@@ -37,8 +37,7 @@ void main() { ...@@ -37,8 +37,7 @@ void main() {
'shadow.BoxDecoration.enabled.png', 'shadow.BoxDecoration.enabled.png',
version: null, version: null,
), ),
skip: !isLinux );
); // shadows render differently on different platforms
debugDisableShadows = true; debugDisableShadows = true;
}, skip: isBrowser); }, skip: isBrowser);
...@@ -70,7 +69,7 @@ void main() { ...@@ -70,7 +69,7 @@ void main() {
); );
} }
debugDisableShadows = true; debugDisableShadows = true;
}, skip: !isLinux); // shadows render differently on different platforms });
testWidgets('Shadows with PhysicalLayer', (WidgetTester tester) async { testWidgets('Shadows with PhysicalLayer', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -107,8 +106,7 @@ void main() { ...@@ -107,8 +106,7 @@ void main() {
'shadow.PhysicalModel.enabled.png', 'shadow.PhysicalModel.enabled.png',
version: null, version: null,
), ),
skip: !isLinux, );
); // shadows render differently on different platforms
debugDisableShadows = true; debugDisableShadows = true;
}, skip: isBrowser); }, skip: isBrowser);
...@@ -144,5 +142,5 @@ void main() { ...@@ -144,5 +142,5 @@ void main() {
); );
} }
debugDisableShadows = true; debugDisableShadows = true;
}, skip: !isLinux); // shadows render differently on different platforms });
} }
...@@ -62,7 +62,7 @@ void main() { ...@@ -62,7 +62,7 @@ void main() {
version: null, version: null,
), ),
); );
}, skip: !isLinux); });
testWidgets('Text Foreground', (WidgetTester tester) async { testWidgets('Text Foreground', (WidgetTester tester) async {
...@@ -147,7 +147,7 @@ void main() { ...@@ -147,7 +147,7 @@ void main() {
version: null, version: null,
), ),
); );
}, skip: !isLinux); });
// TODO(garyq): This test requires an update when the background // TODO(garyq): This test requires an update when the background
// drawing from the beginning of the line bug is fixed. The current // drawing from the beginning of the line bug is fixed. The current
...@@ -200,7 +200,7 @@ void main() { ...@@ -200,7 +200,7 @@ void main() {
version: null, version: null,
), ),
); );
}, skip: !isLinux); });
testWidgets('Text Fade', (WidgetTester tester) async { testWidgets('Text Fade', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -239,7 +239,7 @@ void main() { ...@@ -239,7 +239,7 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: !isLinux); });
testWidgets('Default Strut text', (WidgetTester tester) async { testWidgets('Default Strut text', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -267,8 +267,7 @@ void main() { ...@@ -267,8 +267,7 @@ void main() {
version: null, version: null,
), ),
); );
}, skip: true); // Should only be on linux (skip: !isLinux). });
// Disabled for now until font inconsistency is resolved.
testWidgets('Strut text 1', (WidgetTester tester) async { testWidgets('Strut text 1', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -298,8 +297,7 @@ void main() { ...@@ -298,8 +297,7 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: true); // Should only be on linux (skip: !isLinux). });
// Disabled for now until font inconsistency is resolved.
testWidgets('Strut text 2', (WidgetTester tester) async { testWidgets('Strut text 2', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -330,8 +328,7 @@ void main() { ...@@ -330,8 +328,7 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: true); // Should only be on linux (skip: !isLinux). });
// Disabled for now until font inconsistency is resolved.
testWidgets('Strut text rich', (WidgetTester tester) async { testWidgets('Strut text rich', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -385,8 +382,7 @@ void main() { ...@@ -385,8 +382,7 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: true); // Should only be on linux (skip: !isLinux). });
// Disabled for now until font inconsistency is resolved.
testWidgets('Strut text font fallback', (WidgetTester tester) async { testWidgets('Strut text font fallback', (WidgetTester tester) async {
// Font Fallback // Font Fallback
...@@ -424,8 +420,7 @@ void main() { ...@@ -424,8 +420,7 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: true); // Should only be on linux (skip: !isLinux). });
// Disabled for now until font inconsistency is resolved.
testWidgets('Strut text rich forceStrutHeight', (WidgetTester tester) async { testWidgets('Strut text rich forceStrutHeight', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -479,8 +474,7 @@ void main() { ...@@ -479,8 +474,7 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: true); // Should only be on linux (skip: !isLinux). });
// Disabled for now until font inconsistency is resolved.
testWidgets('Decoration thickness', (WidgetTester tester) async { testWidgets('Decoration thickness', (WidgetTester tester) async {
final TextDecoration allDecorations = TextDecoration.combine( final TextDecoration allDecorations = TextDecoration.combine(
...@@ -521,7 +515,7 @@ void main() { ...@@ -521,7 +515,7 @@ void main() {
version: 0, version: 0,
), ),
); );
}, skip: !isLinux); // Coretext uses different thicknesses for decoration });
testWidgets('Decoration thickness', (WidgetTester tester) async { testWidgets('Decoration thickness', (WidgetTester tester) async {
final TextDecoration allDecorations = TextDecoration.combine( final TextDecoration allDecorations = TextDecoration.combine(
...@@ -563,7 +557,7 @@ void main() { ...@@ -563,7 +557,7 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: !isLinux); // Coretext uses different thicknesses for decoration });
testWidgets('Text Inline widget', (WidgetTester tester) async { testWidgets('Text Inline widget', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -660,7 +654,7 @@ void main() { ...@@ -660,7 +654,7 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: !isLinux); // Coretext uses different thicknesses for decoration });
testWidgets('Text Inline widget textfield', (WidgetTester tester) async { testWidgets('Text Inline widget textfield', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -708,7 +702,7 @@ void main() { ...@@ -708,7 +702,7 @@ void main() {
version: 2, version: 2,
), ),
); );
}, skip: !isLinux); // Coretext uses different thicknesses for decoration });
// This tests if multiple Text.rich widgets are able to inline nest within each other. // This tests if multiple Text.rich widgets are able to inline nest within each other.
testWidgets('Text Inline widget nesting', (WidgetTester tester) async { testWidgets('Text Inline widget nesting', (WidgetTester tester) async {
...@@ -840,7 +834,7 @@ void main() { ...@@ -840,7 +834,7 @@ void main() {
version: 2, version: 2,
), ),
); );
}, skip: !isLinux); // Coretext uses different thicknesses for decoration });
testWidgets('Text Inline widget baseline', (WidgetTester tester) async { testWidgets('Text Inline widget baseline', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -950,7 +944,7 @@ void main() { ...@@ -950,7 +944,7 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: !isLinux); // Coretext uses different thicknesses for decoration });
testWidgets('Text Inline widget aboveBaseline', (WidgetTester tester) async { testWidgets('Text Inline widget aboveBaseline', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -1060,7 +1054,7 @@ void main() { ...@@ -1060,7 +1054,7 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: !isLinux); // Coretext uses different thicknesses for decoration });
testWidgets('Text Inline widget belowBaseline', (WidgetTester tester) async { testWidgets('Text Inline widget belowBaseline', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -1170,7 +1164,7 @@ void main() { ...@@ -1170,7 +1164,7 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: !isLinux); // Coretext uses different thicknesses for decoration });
testWidgets('Text Inline widget top', (WidgetTester tester) async { testWidgets('Text Inline widget top', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -1280,7 +1274,7 @@ void main() { ...@@ -1280,7 +1274,7 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: !isLinux); // Coretext uses different thicknesses for decoration });
testWidgets('Text Inline widget middle', (WidgetTester tester) async { testWidgets('Text Inline widget middle', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -1390,5 +1384,5 @@ void main() { ...@@ -1390,5 +1384,5 @@ void main() {
version: 1, version: 1,
), ),
); );
}, skip: !isLinux); // Coretext uses different thicknesses for decoration });
} }
...@@ -2028,7 +2028,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2028,7 +2028,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.repaint_boundary_margin.png', 'inspector.repaint_boundary_margin.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
// Regression test for how rendering with a pixel scale other than 1.0 // Regression test for how rendering with a pixel scale other than 1.0
...@@ -2042,7 +2041,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2042,7 +2041,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.repaint_boundary_margin_small.png', 'inspector.repaint_boundary_margin_small.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
await expectLater( await expectLater(
...@@ -2054,7 +2052,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2054,7 +2052,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.repaint_boundary_margin_large.png', 'inspector.repaint_boundary_margin_large.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
final Layer layerParent = layer.parent; final Layer layerParent = layer.parent;
...@@ -2073,7 +2070,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2073,7 +2070,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.repaint_boundary.png', 'inspector.repaint_boundary.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
// Verify that taking a screenshot didn't change the layers associated with // Verify that taking a screenshot didn't change the layers associated with
...@@ -2094,7 +2090,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2094,7 +2090,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.repaint_boundary_margin.png', 'inspector.repaint_boundary_margin.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
// Verify that taking a screenshot didn't change the layers associated with // Verify that taking a screenshot didn't change the layers associated with
...@@ -2118,7 +2113,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2118,7 +2113,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.repaint_boundary_debugPaint.png', 'inspector.repaint_boundary_debugPaint.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
// Verify that taking a screenshot with debug paint on did not change // Verify that taking a screenshot with debug paint on did not change
// the number of children the layer has. // the number of children the layer has.
...@@ -2132,7 +2126,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2132,7 +2126,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.repaint_boundary.png', 'inspector.repaint_boundary.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
expect(renderObject.debugLayer, equals(layer)); expect(renderObject.debugLayer, equals(layer));
...@@ -2149,7 +2142,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2149,7 +2142,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.container.png', 'inspector.container.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
await expectLater( await expectLater(
...@@ -2163,7 +2155,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2163,7 +2155,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.container_debugPaint.png', 'inspector.container_debugPaint.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
{ {
...@@ -2187,7 +2178,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2187,7 +2178,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.container_debugPaint.png', 'inspector.container_debugPaint.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
expect(container.debugNeedsLayout, isFalse); expect(container.debugNeedsLayout, isFalse);
} }
...@@ -2203,7 +2193,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2203,7 +2193,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.container_small.png', 'inspector.container_small.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
await expectLater( await expectLater(
...@@ -2217,7 +2206,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2217,7 +2206,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.container_large.png', 'inspector.container_large.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
// This screenshot will show the clip rect debug paint but no other // This screenshot will show the clip rect debug paint but no other
...@@ -2233,7 +2221,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2233,7 +2221,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.clipRect_debugPaint.png', 'inspector.clipRect_debugPaint.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
final Element clipRect = find.byType(ClipRRect).evaluate().single; final Element clipRect = find.byType(ClipRRect).evaluate().single;
...@@ -2253,7 +2240,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2253,7 +2240,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.clipRect_debugPaint_margin.png', 'inspector.clipRect_debugPaint_margin.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
// Verify we get the same image if we go through the service extension // Verify we get the same image if we go through the service extension
...@@ -2296,7 +2282,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2296,7 +2282,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.padding_debugPaint.png', 'inspector.padding_debugPaint.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
// The bounds for this box crop its rendered content. // The bounds for this box crop its rendered content.
...@@ -2311,7 +2296,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2311,7 +2296,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.sizedBox_debugPaint.png', 'inspector.sizedBox_debugPaint.png',
version: 1, version: 1,
), ),
skip: !isLinux,
); );
// Verify that setting a margin includes the previously cropped content. // Verify that setting a margin includes the previously cropped content.
...@@ -2327,7 +2311,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2327,7 +2311,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.sizedBox_debugPaint_margin.png', 'inspector.sizedBox_debugPaint_margin.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
...@@ -2402,7 +2385,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2402,7 +2385,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.composited_transform.only_offsets.png', 'inspector.composited_transform.only_offsets.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
await expectLater( await expectLater(
...@@ -2415,7 +2397,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2415,7 +2397,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.composited_transform.only_offsets_follower.png', 'inspector.composited_transform.only_offsets_follower.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
await expectLater( await expectLater(
...@@ -2424,7 +2405,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2424,7 +2405,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.composited_transform.only_offsets_small.png', 'inspector.composited_transform.only_offsets_small.png',
version: 1, version: 1,
), ),
skip: !isLinux,
); );
await expectLater( await expectLater(
...@@ -2437,7 +2417,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2437,7 +2417,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.composited_transform.only_offsets_target.png', 'inspector.composited_transform.only_offsets_target.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
}, skip: isBrowser); }, skip: isBrowser);
...@@ -2513,7 +2492,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2513,7 +2492,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.composited_transform.with_rotations.png', 'inspector.composited_transform.with_rotations.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
await expectLater( await expectLater(
...@@ -2526,7 +2504,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2526,7 +2504,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.composited_transform.with_rotations_small.png', 'inspector.composited_transform.with_rotations_small.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
await expectLater( await expectLater(
...@@ -2539,7 +2516,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2539,7 +2516,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.composited_transform.with_rotations_target.png', 'inspector.composited_transform.with_rotations_target.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
await expectLater( await expectLater(
...@@ -2552,7 +2528,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { ...@@ -2552,7 +2528,6 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
'inspector.composited_transform.with_rotations_follower.png', 'inspector.composited_transform.with_rotations_follower.png',
version: null, version: null,
), ),
skip: !isLinux,
); );
// Make sure taking screenshots hasn't modified the positions of the // Make sure taking screenshots hasn't modified the positions of the
......
...@@ -34,13 +34,13 @@ void main() { ...@@ -34,13 +34,13 @@ void main() {
}); });
group('GoldensClient', () { group('GoldensClient', () {
GoldensClient goldens; GoldensRepositoryClient goldens;
setUp(() { setUp(() {
goldens = GoldensClient( goldens = GoldensRepositoryClient(
fs: fs, fs: fs,
platform: platform,
process: process, process: process,
platform: platform,
); );
}); });
...@@ -60,32 +60,65 @@ void main() { ...@@ -60,32 +60,65 @@ void main() {
}); });
}); });
group('SkiaGoldClient', () {
SkiaGoldClient goldens;
setUp(() {
goldens = SkiaGoldClient(
fs: fs,
process: process,
platform: platform,
);
});
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);
// Verify that we spawned no process calls
final VerificationResult verifyProcessRun =
verifyNever(process.run(captureAny, workingDirectory: captureAnyNamed('workingDirectory')));
expect(verifyProcessRun.callCount, 0);
});
});
});
group('FlutterGoldenFileComparator', () { 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);
});
});
group('FlutterGoldensRepositoryFileComparator', () {
MemoryFileSystem fs; MemoryFileSystem fs;
FlutterGoldenFileComparator comparator; FlutterGoldensRepositoryFileComparator comparator;
setUp(() { setUp(() {
fs = MemoryFileSystem(); fs = MemoryFileSystem();
platform = FakePlatform(
operatingSystem: 'linux',
environment: <String, String>{'FLUTTER_ROOT': _kFlutterRoot},
);
final Directory flutterRoot = fs.directory('/path/to/flutter')..createSync(recursive: true); final Directory flutterRoot = fs.directory('/path/to/flutter')..createSync(recursive: true);
final Directory goldensRoot = flutterRoot.childDirectory('bin/cache/goldens')..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 testDirectory = goldensRoot.childDirectory('test/foo/bar')..createSync(recursive: true);
comparator = FlutterGoldenFileComparator(testDirectory.uri, fs: fs); comparator = FlutterGoldensRepositoryFileComparator(
}); testDirectory.uri,
fs: fs,
group('fromDefaultComparator', () { platform: platform,
test('calculates the basedir correctly', () async { );
final MockGoldensClient goldens = MockGoldensClient();
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.repositoryRoot).thenReturn(goldensRoot);
when(defaultComparator.basedir).thenReturn(flutterRoot.childDirectory('baz').uri);
comparator = await FlutterGoldenFileComparator.fromDefaultComparator(
goldens: goldens, defaultComparator: defaultComparator);
expect(comparator.basedir, fs.directory('/foo/bar/baz').uri);
});
}); });
group('compare', () { group('compare', () {
...@@ -132,9 +165,44 @@ void main() { ...@@ -132,9 +165,44 @@ void main() {
expect(goldenFile.readAsBytesSync(), <int>[1, 2, 3]); expect(goldenFile.readAsBytesSync(), <int>[1, 2, 3]);
}); });
}); });
group('getTestUri', () {
test('incorporates version number', () {
final Uri key = comparator.getTestUri(Uri.parse('foo.png'), 1);
expect(key, Uri.parse('foo.1.png'));
});
test('ignores null version number', () {
final Uri key = comparator.getTestUri(Uri.parse('foo.png'), null);
expect(key, Uri.parse('foo.png'));
});
});
});
group('FlutterSkiaGoldFileComparator', () {
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);
comparator = FlutterSkiaGoldFileComparator(
testDirectory.uri,
MockSkiaGoldClient(),
fs: fs,
platform: platform,
);
});
group('getTestUri', () {
test('ignores version number', () {
final Uri key = comparator.getTestUri(Uri.parse('foo.png'), 1);
expect(key, Uri.parse('foo.png'));
});
});
}); });
} }
class MockProcessManager extends Mock implements ProcessManager {} class MockProcessManager extends Mock implements ProcessManager {}
class MockGoldensClient extends Mock implements GoldensClient {} class MockGoldensRepositoryClient extends Mock implements GoldensRepositoryClient {}
class MockSkiaGoldClient extends Mock implements SkiaGoldClient {}
class MockLocalFileComparator extends Mock implements LocalFileComparator {} class MockLocalFileComparator extends Mock implements LocalFileComparator {}
...@@ -16,11 +16,11 @@ import 'package:process/process.dart'; ...@@ -16,11 +16,11 @@ import 'package:process/process.dart';
const String _kFlutterRootKey = 'FLUTTER_ROOT'; const String _kFlutterRootKey = 'FLUTTER_ROOT';
/// A class that represents a clone of the https://github.com/flutter/goldens /// An base class that provides shared information to the
/// repository, nested within the `bin/cache` directory of the caller's Flutter /// [FlutterGoldenFileComparator] as well as the [SkiaGoldClient] and
/// repository. /// [GoldensRepositoryClient].
class GoldensClient { abstract class GoldensClient {
/// Create a handle to a local clone of the goldens repository. /// Creates a handle to the local environment of golden file images.
GoldensClient({ GoldensClient({
this.fs = const LocalFileSystem(), this.fs = const LocalFileSystem(),
this.platform = const LocalPlatform(), this.platform = const LocalPlatform(),
...@@ -46,17 +46,32 @@ class GoldensClient { ...@@ -46,17 +46,32 @@ class GoldensClient {
/// subprocesses. /// subprocesses.
final ProcessManager process; final ProcessManager process;
RandomAccessFile _lock;
/// The local [Directory] where the Flutter repository is hosted. /// The local [Directory] where the Flutter repository is hosted.
/// ///
/// Uses the [fs] file system. /// Uses the [fs] file system.
Directory get flutterRoot => fs.directory(platform.environment[_kFlutterRootKey]); Directory get flutterRoot => fs.directory(platform.environment[_kFlutterRootKey]);
/// The local [Directory] where the goldens repository is hosted. /// The local [Directory] where the goldens files are located.
/// ///
/// Uses the [fs] file system. /// Uses the [fs] file system.
Directory get repositoryRoot => flutterRoot.childDirectory(fs.path.join('bin', 'cache', 'pkg', 'goldens')); 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 /// Prepares the local clone of the `flutter/goldens` repository for golden
/// file testing. /// file testing.
...@@ -89,46 +104,46 @@ class GoldensClient { ...@@ -89,46 +104,46 @@ class GoldensClient {
} }
} }
Future<String> _getGoldensCommit() async {
final File versionFile = flutterRoot.childFile(fs.path.join('bin', 'internal', 'goldens.version'));
return (await versionFile.readAsString()).trim();
}
Future<String> _getCurrentCommit() async { Future<String> _getCurrentCommit() async {
if (!repositoryRoot.existsSync()) { if (!comparisonRoot.existsSync()) {
return null; return null;
} else { } else {
final io.ProcessResult revParse = await process.run( final io.ProcessResult revParse = await process.run(
<String>['git', 'rev-parse', 'HEAD'], <String>['git', 'rev-parse', 'HEAD'],
workingDirectory: repositoryRoot.path, workingDirectory: comparisonRoot.path,
); );
return revParse.exitCode == 0 ? revParse.stdout.trim() : null; 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 { Future<void> _initRepository() async {
await repositoryRoot.create(recursive: true); await comparisonRoot.create(recursive: true);
await _runCommands( await _runCommands(
<String>[ <String>[
'git init', 'git init',
'git remote add upstream https://github.com/flutter/goldens.git', 'git remote add upstream https://github.com/flutter/goldens.git',
'git remote set-url --push upstream git@github.com:flutter/goldens.git', 'git remote set-url --push upstream git@github.com:flutter/goldens.git',
], ],
workingDirectory: repositoryRoot, workingDirectory: comparisonRoot,
); );
} }
Future<void> _checkCanSync() async { Future<void> _checkCanSync() async {
final io.ProcessResult result = await process.run( final io.ProcessResult result = await process.run(
<String>['git', 'status', '--porcelain'], <String>['git', 'status', '--porcelain'],
workingDirectory: repositoryRoot.path, workingDirectory: comparisonRoot.path,
); );
if (result.stdout.trim().isNotEmpty) { if (result.stdout.trim().isNotEmpty) {
final StringBuffer buf = StringBuffer(); final StringBuffer buf = StringBuffer();
buf buf
..writeln('flutter_goldens git checkout at ${repositoryRoot.path} has local changes and cannot be synced.') ..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('To reset your client to a clean state, and lose any local golden test changes:')
..writeln('cd ${repositoryRoot.path}') ..writeln('cd ${comparisonRoot.path}')
..writeln('git reset --hard HEAD') ..writeln('git reset --hard HEAD')
..writeln('git clean -x -d -f -f'); ..writeln('git clean -x -d -f -f');
throw NonZeroExitCode(1, buf.toString()); throw NonZeroExitCode(1, buf.toString());
...@@ -142,7 +157,7 @@ class GoldensClient { ...@@ -142,7 +157,7 @@ class GoldensClient {
'git fetch upstream $commit', 'git fetch upstream $commit',
'git reset --hard FETCH_HEAD', 'git reset --hard FETCH_HEAD',
], ],
workingDirectory: repositoryRoot, workingDirectory: comparisonRoot,
); );
} }
...@@ -174,6 +189,7 @@ class GoldensClient { ...@@ -174,6 +189,7 @@ class GoldensClient {
_lock = null; _lock = null;
} }
} }
/// Exception that signals a process' exit with a non-zero exit code. /// Exception that signals a process' exit with a non-zero exit code.
class NonZeroExitCode implements Exception { class NonZeroExitCode implements Exception {
/// Create an exception that represents a non-zero exit code. /// Create an exception that represents a non-zero exit code.
......
// 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io' as io;
import 'package:file/file.dart';
import 'package:file/local.dart';
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 _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,
);
/// The local [Directory] within the [comparisonRoot] for the current test
/// context. In this directory, the client will create image and json files
/// for the goldctl tool to use.
///
/// This is informed by the [FlutterGoldenFileComparator] [basedir]. It cannot
/// be null.
Directory _workDirectory;
/// The path to the local [Directory] where the goldctl tool is hosted.
///
/// Uses the [platform] environment in this implementation.
String get _goldctl => platform.environment[_kGoldctlKey];
/// The path to the local [Directory] where the service account key is
/// hosted.
///
/// Uses the [platform] environment in this implementation.
String get _serviceAccount => platform.environment[_kServiceAccountKey];
/// Prepares the local work space for golden file testing and calls the
/// goldctl `auth` command.
///
/// This ensures that the goldctl tool is authorized and ready for testing. It
/// will only be called once for each instance of
/// [FlutterSkiaGoldFileComparator].
///
/// 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;
if (_clientIsAuthorized())
return;
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,
];
final io.ProcessResult authResults = await io.Process.run(
_goldctl,
authArguments,
);
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.
///
/// The `imgtest` command collects and uploads test results to the Skia Gold
/// backend, the `init` argument initializes the testing environment.
Future<void> imgtestInit() async {
final String commitHash = await _getCurrentCommit();
final File keys = _workDirectory.childFile('keys.json');
final File failures = _workDirectory.childFile('failures.json');
await keys.writeAsString(_getKeysJSON());
await failures.create();
final List<String> imgtestInitArguments = <String>[
'imgtest', 'init',
'--instance', 'flutter',
'--work-dir', _workDirectory.childDirectory('temp').path,
'--commit', commitHash,
'--keys-file', keys.path,
'--failure-file', failures.path,
'--passfail',
];
if (imgtestInitArguments.contains(null)) {
final StringBuffer buf = StringBuffer();
buf.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,
);
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.
///
/// The `imgtest` command collects and uploads test results to the Skia Gold
/// backend, the `add` argument uploads the current image test. A response is
/// returned from the invocation of this command that indicates a pass or fail
/// result.
///
/// The testName and goldenFile parameters reference the current comparison
/// being evaluated by the [FlutterSkiaGoldFileComparator].
Future<bool> imgtestAdd(String testName, File goldenFile) async {
assert(testName != null);
assert(goldenFile != null);
final List<String> imgtestArguments = <String>[
'imgtest', 'add',
'--work-dir', _workDirectory.childDirectory('temp').path,
'--test-name', testName.split(path.extension(testName.toString()))[0],
'--png-file', goldenFile.path,
];
await io.Process.run(
_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;
}
/// Returns the current commit hash of the Flutter repository.
Future<String> _getCurrentCommit() async {
if (!flutterRoot.existsSync()) {
final StringBuffer buf = StringBuffer()
..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,
);
return revParse.exitCode == 0 ? revParse.stdout.trim() : null;
}
}
/// Returns a JSON String with keys value pairs used to uniquely identify the
/// configuration that generated the given golden file.
///
/// Currently, the only key value pair being tracked is the platform the image
/// was rendered on.
String _getKeysJSON() {
return json.encode(
<String, dynamic>{
'Platform' : platform.operatingSystem,
}
);
}
/// 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(
'temp',
'auth_opt.json',
));
return authFile.existsSync();
}
}
...@@ -45,6 +45,26 @@ abstract class GoldenFileComparator { ...@@ -45,6 +45,26 @@ abstract class GoldenFileComparator {
/// The method by which [golden] is located and by which its bytes are written /// The method by which [golden] is located and by which its bytes are written
/// is left up to the implementation class. /// is left up to the implementation class.
Future<void> update(Uri golden, Uint8List imageBytes); Future<void> update(Uri golden, Uint8List imageBytes);
/// Returns a new golden file [Uri] to incorporate any [version] number with
/// the [key].
///
/// The [version] is an optional int that can be used to differentiate
/// historical golden files.
///
/// 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).
Uri getTestUri(Uri key, int version) {
if (version == null)
return key;
final String keyString = key.toString();
final String extension = path.extension(keyString);
return Uri.parse(
keyString
.split(extension)
.join() + '.' + version.toString() + extension
);
}
} }
/// Compares rasterized image bytes against a golden image file. /// Compares rasterized image bytes against a golden image file.
...@@ -126,6 +146,11 @@ class TrivialComparator implements GoldenFileComparator { ...@@ -126,6 +146,11 @@ class TrivialComparator implements GoldenFileComparator {
Future<void> update(Uri golden, Uint8List imageBytes) { Future<void> update(Uri golden, Uint8List imageBytes) {
throw StateError('goldenFileComparator has not been initialized'); throw StateError('goldenFileComparator has not been initialized');
} }
@override
Uri getTestUri(Uri key, int version) {
return key;
}
} }
/// The default [GoldenFileComparator] implementation for `flutter test`. /// The default [GoldenFileComparator] implementation for `flutter test`.
...@@ -140,7 +165,7 @@ class TrivialComparator implements GoldenFileComparator { ...@@ -140,7 +165,7 @@ class TrivialComparator implements GoldenFileComparator {
/// ///
/// When using `flutter test --update-goldens`, [LocalFileComparator] /// When using `flutter test --update-goldens`, [LocalFileComparator]
/// updates the files on disk to match the rendering. /// updates the files on disk to match the rendering.
class LocalFileComparator implements GoldenFileComparator { class LocalFileComparator extends GoldenFileComparator {
/// Creates a new [LocalFileComparator] for the specified [testFile]. /// Creates a new [LocalFileComparator] for the specified [testFile].
/// ///
/// Golden file keys will be interpreted as file paths relative to the /// Golden file keys will be interpreted as file paths relative to the
......
...@@ -1712,7 +1712,7 @@ class _MatchesGoldenFile extends AsyncMatcher { ...@@ -1712,7 +1712,7 @@ class _MatchesGoldenFile extends AsyncMatcher {
imageFuture = _captureImage(elements.single); imageFuture = _captureImage(elements.single);
} }
final Uri testNameUri = _getTestNameUri(key, version); final Uri testNameUri = goldenFileComparator.getTestUri(key, version);
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
return binding.runAsync<String>(() async { return binding.runAsync<String>(() async {
...@@ -1734,19 +1734,9 @@ class _MatchesGoldenFile extends AsyncMatcher { ...@@ -1734,19 +1734,9 @@ class _MatchesGoldenFile extends AsyncMatcher {
} }
@override @override
Description describe(Description description) => Description describe(Description description) {
description.add('one widget whose rasterized image matches golden image "${_getTestNameUri(key, version)}"'); final Uri testNameUri = goldenFileComparator.getTestUri(key, version);
return description.add('one widget whose rasterized image matches golden image "$testNameUri"');
Uri _getTestNameUri(Uri key, int version) {
return version == null ? key : Uri.parse(
key
.toString()
.splitMapJoin(
RegExp(r'.png'),
onMatch: (Match m) => '${'.' + version.toString() + m.group(0)}',
onNonMatch: (String n) => '$n'
)
);
} }
} }
......
...@@ -182,5 +182,18 @@ void main() { ...@@ -182,5 +182,18 @@ void main() {
expect(fs.file(fix('/foo.png')).readAsBytesSync(), newBytes); expect(fs.file(fix('/foo.png')).readAsBytesSync(), newBytes);
}); });
}); });
group('getTestUri', () {
test('updates file name with version number', () {
final Uri key = Uri.parse('foo.png');
final Uri key1 = comparator.getTestUri(key, 1);
expect(key1, Uri.parse('foo.1.png'));
});
test('does nothing for null version number', () {
final Uri key = Uri.parse('foo.png');
final Uri keyNull = comparator.getTestUri(key, null);
expect(keyNull, Uri.parse('foo.png'));
});
});
}); });
} }
...@@ -335,30 +335,6 @@ void main() { ...@@ -335,30 +335,6 @@ void main() {
expect(comparator.imageBytes, hasLength(greaterThan(0))); expect(comparator.imageBytes, hasLength(greaterThan(0)));
expect(comparator.golden, Uri.parse('foo.png')); expect(comparator.golden, Uri.parse('foo.png'));
}); });
testWidgets('Comparator succeeds incorporating version number', (WidgetTester tester) async {
await tester.pumpWidget(boilerplate(const Text('hello')));
final Finder finder = find.byType(Text);
await expectLater(finder, matchesGoldenFile(
'foo.png',
version: 1,
));
expect(comparator.invocation, _ComparatorInvocation.compare);
expect(comparator.imageBytes, hasLength(greaterThan(0)));
expect(comparator.golden, Uri.parse('foo.1.png'));
});
testWidgets('Comparator succeeds with null version number', (WidgetTester tester) async {
await tester.pumpWidget(boilerplate(const Text('hello')));
final Finder finder = find.byType(Text);
await expectLater(finder, matchesGoldenFile(
'foo.png',
version: null,
));
expect(comparator.invocation, _ComparatorInvocation.compare);
expect(comparator.imageBytes, hasLength(greaterThan(0)));
expect(comparator.golden, Uri.parse('foo.png'));
});
}); });
group('does not match', () { group('does not match', () {
...@@ -413,40 +389,6 @@ void main() { ...@@ -413,40 +389,6 @@ void main() {
expect(error.message, contains('too many widgets')); expect(error.message, contains('too many widgets'));
} }
}); });
testWidgets('Comparator failure incorporates version number', (WidgetTester tester) async {
comparator.behavior = _ComparatorBehavior.returnFalse;
await tester.pumpWidget(boilerplate(const Text('hello')));
final Finder finder = find.byType(Text);
try {
await expectLater(finder, matchesGoldenFile(
'foo.png',
version: 1,
));
fail('TestFailure expected but not thrown');
} on TestFailure catch (error) {
expect(comparator.invocation, _ComparatorInvocation.compare);
expect(error.message, contains('does not match'));
expect(error.message, contains('foo.1.png'));
}
});
testWidgets('Comparator failure with null version number', (WidgetTester tester) async {
comparator.behavior = _ComparatorBehavior.returnFalse;
await tester.pumpWidget(boilerplate(const Text('hello')));
final Finder finder = find.byType(Text);
try {
await expectLater(finder, matchesGoldenFile(
'foo.png',
version: null,
));
fail('TestFailure expected but not thrown');
} on TestFailure catch (error) {
expect(comparator.invocation, _ComparatorInvocation.compare);
expect(error.message, contains('does not match'));
expect(error.message, contains('foo.png'));
}
});
}); });
testWidgets('calls update on comparator if autoUpdateGoldenFiles is true', (WidgetTester tester) async { testWidgets('calls update on comparator if autoUpdateGoldenFiles is true', (WidgetTester tester) async {
...@@ -708,6 +650,11 @@ class _FakeComparator implements GoldenFileComparator { ...@@ -708,6 +650,11 @@ class _FakeComparator implements GoldenFileComparator {
this.imageBytes = imageBytes; this.imageBytes = imageBytes;
return Future<void>.value(); return Future<void>.value();
} }
@override
Uri getTestUri(Uri key, int version) {
return key;
}
} }
class _FakeSemanticsNode extends SemanticsNode { class _FakeSemanticsNode extends SemanticsNode {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment