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
a4e3f933
Unverified
Commit
a4e3f933
authored
Sep 22, 2023
by
Kostia Sokolovskyi
Committed by
GitHub
Sep 22, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix memory leak in _MatchesReferenceImage (#135150)
parent
579e1960
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
161 additions
and
35 deletions
+161
-35
matchers.dart
packages/flutter_test/lib/src/matchers.dart
+28
-16
pubspec.yaml
packages/flutter_test/pubspec.yaml
+39
-1
reference_image_test.dart
packages/flutter_test/test/reference_image_test.dart
+94
-18
No files found.
packages/flutter_test/lib/src/matchers.dart
View file @
a4e3f933
...
...
@@ -576,7 +576,9 @@ AsyncMatcher matchesGoldenFile(Object key, {int? version}) {
/// final ui.Canvas pictureCanvas = ui.Canvas(recorder);
/// pictureCanvas.drawCircle(Offset.zero, 20.0, paint);
/// final ui.Picture picture = recorder.endRecording();
/// addTearDown(picture.dispose);
/// ui.Image referenceImage = await picture.toImage(50, 50);
/// addTearDown(referenceImage.dispose);
///
/// await expectLater(find.text('Save'), matchesReferenceImage(referenceImage));
/// await expectLater(image, matchesReferenceImage(referenceImage));
...
...
@@ -2139,10 +2141,13 @@ class _MatchesReferenceImage extends AsyncMatcher {
@override
Future
<
String
?>
matchAsync
(
dynamic
item
)
async
{
Future
<
ui
.
Image
>
imageFuture
;
final
bool
disposeImage
;
// set to true if the matcher created and owns the image and must therefore dispose it.
if
(
item
is
Future
<
ui
.
Image
>)
{
imageFuture
=
item
;
disposeImage
=
false
;
}
else
if
(
item
is
ui
.
Image
)
{
imageFuture
=
Future
<
ui
.
Image
>.
value
(
item
);
disposeImage
=
false
;
}
else
{
final
Finder
finder
=
item
as
Finder
;
final
Iterable
<
Element
>
elements
=
finder
.
evaluate
();
...
...
@@ -2152,30 +2157,37 @@ class _MatchesReferenceImage extends AsyncMatcher {
return
'matched too many widgets'
;
}
imageFuture
=
captureImage
(
elements
.
single
);
disposeImage
=
true
;
}
final
TestWidgetsFlutterBinding
binding
=
TestWidgetsFlutterBinding
.
instance
;
return
binding
.
runAsync
<
String
?>(()
async
{
final
ui
.
Image
image
=
await
imageFuture
;
final
ByteData
?
bytes
=
await
image
.
toByteData
();
if
(
bytes
==
null
)
{
return
'could not be encoded.'
;
}
try
{
final
ByteData
?
bytes
=
await
image
.
toByteData
();
if
(
bytes
==
null
)
{
return
'could not be encoded.'
;
}
final
ByteData
?
referenceBytes
=
await
referenceImage
.
toByteData
();
if
(
referenceBytes
==
null
)
{
return
'could not have its reference image encoded.'
;
}
final
ByteData
?
referenceBytes
=
await
referenceImage
.
toByteData
();
if
(
referenceBytes
==
null
)
{
return
'could not have its reference image encoded.'
;
}
if
(
referenceImage
.
height
!=
image
.
height
||
referenceImage
.
width
!=
image
.
width
)
{
return
'does not match as width or height do not match.
$image
!=
$referenceImage
'
;
}
if
(
referenceImage
.
height
!=
image
.
height
||
referenceImage
.
width
!=
image
.
width
)
{
return
'does not match as width or height do not match.
$image
!=
$referenceImage
'
;
}
final
int
countDifferentPixels
=
_countDifferentPixels
(
Uint8List
.
view
(
bytes
.
buffer
),
Uint8List
.
view
(
referenceBytes
.
buffer
),
);
return
countDifferentPixels
==
0
?
null
:
'does not match on
$countDifferentPixels
pixels'
;
final
int
countDifferentPixels
=
_countDifferentPixels
(
Uint8List
.
view
(
bytes
.
buffer
),
Uint8List
.
view
(
referenceBytes
.
buffer
),
);
return
countDifferentPixels
==
0
?
null
:
'does not match on
$countDifferentPixels
pixels'
;
}
finally
{
if
(
disposeImage
)
{
image
.
dispose
();
}
}
});
}
...
...
packages/flutter_test/pubspec.yaml
View file @
a4e3f933
...
...
@@ -44,5 +44,43 @@ dependencies:
dev_dependencies
:
file
:
6.1.4
# Used to detect memory leaks.
leak_tracker_flutter_testing
:
1.0.5
# PUBSPEC CHECKSUM: 9e5d
_fe_analyzer_shared
:
64.0.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
analyzer
:
6.2.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
args
:
2.4.2
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
convert
:
3.1.1
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
coverage
:
1.6.3
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
crypto
:
3.0.3
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
frontend_server_client
:
3.2.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
glob
:
2.1.2
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
http_multi_server
:
3.2.1
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
http_parser
:
4.0.2
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
intl
:
0.18.1
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
io
:
1.0.4
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
js
:
0.6.7
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
leak_tracker
:
9.0.7
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
leak_tracker_testing
:
1.0.4
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
logging
:
1.2.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
mime
:
1.0.4
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
node_preamble
:
2.0.2
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
package_config
:
2.1.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pool
:
1.5.1
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pub_semver
:
2.1.4
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf
:
1.4.1
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf_packages_handler
:
3.0.2
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf_static
:
1.1.2
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf_web_socket
:
1.0.4
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_map_stack_trace
:
2.1.1
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_maps
:
0.10.12
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
test
:
1.24.6
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
test_core
:
0.5.6
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
typed_data
:
1.3.2
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vm_service
:
11.10.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
watcher
:
1.1.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web_socket_channel
:
2.4.0
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
webkit_inspection_protocol
:
1.2.1
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
yaml
:
3.1.2
# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
# PUBSPEC CHECKSUM: aa8d
packages/flutter_test/test/reference_image_test.dart
View file @
a4e3f933
...
...
@@ -4,9 +4,12 @@
import
'dart:ui'
as
ui
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'
;
Future
<
ui
.
Image
>
createTestImage
(
int
width
,
int
height
,
ui
.
Color
color
)
{
Future
<
ui
.
Image
>
createTestImage
(
int
width
,
int
height
,
ui
.
Color
color
)
async
{
final
ui
.
Paint
paint
=
ui
.
Paint
()
..
style
=
ui
.
PaintingStyle
.
stroke
..
strokeWidth
=
1.0
...
...
@@ -15,7 +18,9 @@ Future<ui.Image> createTestImage(int width, int height, ui.Color color) {
final
ui
.
Canvas
pictureCanvas
=
ui
.
Canvas
(
recorder
);
pictureCanvas
.
drawCircle
(
Offset
.
zero
,
20.0
,
paint
);
final
ui
.
Picture
picture
=
recorder
.
endRecording
();
return
picture
.
toImage
(
width
,
height
);
final
ui
.
Image
image
=
await
picture
.
toImage
(
width
,
height
);
picture
.
dispose
();
return
image
;
}
void
main
(
)
{
...
...
@@ -24,50 +29,121 @@ void main() {
const
ui
.
Color
transparentRed
=
ui
.
Color
.
fromARGB
(
128
,
255
,
0
,
0
);
group
(
'succeeds'
,
()
{
testWidgets
(
'when images have the same content'
,
(
WidgetTester
tester
)
async
{
await
expectLater
(
await
createTestImage
(
100
,
100
,
red
),
matchesReferenceImage
(
await
createTestImage
(
100
,
100
,
red
)),
);
await
expectLater
(
await
createTestImage
(
100
,
100
,
green
),
matchesReferenceImage
(
await
createTestImage
(
100
,
100
,
green
)),
);
testWidgetsWithLeakTracking
(
'when images have the same content'
,
(
WidgetTester
tester
)
async
{
final
ui
.
Image
image1
=
await
createTestImage
(
100
,
100
,
red
);
addTearDown
(
image1
.
dispose
);
final
ui
.
Image
referenceImage1
=
await
createTestImage
(
100
,
100
,
red
);
addTearDown
(
referenceImage1
.
dispose
);
await
expectLater
(
await
createTestImage
(
100
,
100
,
transparentRed
),
matchesReferenceImage
(
await
createTestImage
(
100
,
100
,
transparentRed
)),
);
await
expectLater
(
image1
,
matchesReferenceImage
(
referenceImage1
));
final
ui
.
Image
image2
=
await
createTestImage
(
100
,
100
,
green
);
addTearDown
(
image2
.
dispose
);
final
ui
.
Image
referenceImage2
=
await
createTestImage
(
100
,
100
,
green
);
addTearDown
(
referenceImage2
.
dispose
);
await
expectLater
(
image2
,
matchesReferenceImage
(
referenceImage2
));
final
ui
.
Image
image3
=
await
createTestImage
(
100
,
100
,
transparentRed
);
addTearDown
(
image3
.
dispose
);
final
ui
.
Image
referenceImage3
=
await
createTestImage
(
100
,
100
,
transparentRed
);
addTearDown
(
referenceImage3
.
dispose
);
await
expectLater
(
image3
,
matchesReferenceImage
(
referenceImage3
));
});
testWidgets
(
'when images are identical'
,
(
WidgetTester
tester
)
async
{
testWidgets
WithLeakTracking
(
'when images are identical'
,
(
WidgetTester
tester
)
async
{
final
ui
.
Image
image
=
await
createTestImage
(
100
,
100
,
red
);
addTearDown
(
image
.
dispose
);
await
expectLater
(
image
,
matchesReferenceImage
(
image
));
});
testWidgetsWithLeakTracking
(
'when widget looks the same'
,
(
WidgetTester
tester
)
async
{
addTearDown
(
tester
.
view
.
reset
);
tester
.
view
..
physicalSize
=
const
Size
(
10
,
10
)
..
devicePixelRatio
=
1
;
const
ValueKey
<
String
>
repaintBoundaryKey
=
ValueKey
<
String
>(
'boundary'
);
await
tester
.
pumpWidget
(
const
RepaintBoundary
(
key:
repaintBoundaryKey
,
child:
ColoredBox
(
color:
red
),
),
);
final
ui
.
Image
referenceImage
=
(
tester
.
renderObject
(
find
.
byKey
(
repaintBoundaryKey
))
as
RenderRepaintBoundary
).
toImageSync
();
addTearDown
(
referenceImage
.
dispose
);
await
expectLater
(
find
.
byKey
(
repaintBoundaryKey
),
matchesReferenceImage
(
referenceImage
));
});
});
group
(
'fails'
,
()
{
testWidgets
(
'when image sizes do not match'
,
(
WidgetTester
tester
)
async
{
testWidgets
WithLeakTracking
(
'when image sizes do not match'
,
(
WidgetTester
tester
)
async
{
final
ui
.
Image
red50
=
await
createTestImage
(
50
,
50
,
red
);
addTearDown
(
red50
.
dispose
);
final
ui
.
Image
red100
=
await
createTestImage
(
100
,
100
,
red
);
addTearDown
(
red100
.
dispose
);
expect
(
await
matchesReferenceImage
(
red50
).
matchAsync
(
red100
),
equals
(
'does not match as width or height do not match. [100×100] != [50×50]'
),
);
});
testWidgets
(
'when image pixels do not match'
,
(
WidgetTester
tester
)
async
{
testWidgets
WithLeakTracking
(
'when image pixels do not match'
,
(
WidgetTester
tester
)
async
{
final
ui
.
Image
red100
=
await
createTestImage
(
100
,
100
,
red
);
addTearDown
(
red100
.
dispose
);
final
ui
.
Image
transparentRed100
=
await
createTestImage
(
100
,
100
,
transparentRed
);
addTearDown
(
transparentRed100
.
dispose
);
expect
(
await
matchesReferenceImage
(
red100
).
matchAsync
(
transparentRed100
),
equals
(
'does not match on 57 pixels'
),
);
final
ui
.
Image
green100
=
await
createTestImage
(
100
,
100
,
green
);
addTearDown
(
green100
.
dispose
);
expect
(
await
matchesReferenceImage
(
red100
).
matchAsync
(
green100
),
equals
(
'does not match on 57 pixels'
),
);
});
testWidgetsWithLeakTracking
(
'when widget does not look the same'
,
(
WidgetTester
tester
)
async
{
addTearDown
(
tester
.
view
.
reset
);
tester
.
view
..
physicalSize
=
const
Size
(
10
,
10
)
..
devicePixelRatio
=
1
;
const
ValueKey
<
String
>
repaintBoundaryKey
=
ValueKey
<
String
>(
'boundary'
);
await
tester
.
pumpWidget
(
const
RepaintBoundary
(
key:
repaintBoundaryKey
,
child:
ColoredBox
(
color:
red
),
),
);
final
ui
.
Image
referenceImage
=
(
tester
.
renderObject
(
find
.
byKey
(
repaintBoundaryKey
))
as
RenderRepaintBoundary
).
toImageSync
();
addTearDown
(
referenceImage
.
dispose
);
await
tester
.
pumpWidget
(
const
RepaintBoundary
(
key:
repaintBoundaryKey
,
child:
ColoredBox
(
color:
green
),
),
);
expect
(
await
matchesReferenceImage
(
referenceImage
).
matchAsync
(
find
.
byKey
(
repaintBoundaryKey
),
),
equals
(
'does not match on 100 pixels'
),
);
});
});
}
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