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
baf6ba13
Unverified
Commit
baf6ba13
authored
Jan 03, 2024
by
Polina Cherkasova
Committed by
GitHub
Jan 03, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Re-land integrate testWidgets with leak tracking. (#140521)
parent
eb195da7
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
314 additions
and
14 deletions
+314
-14
flutter_test_config.dart
packages/flutter/test/flutter_test_config.dart
+15
-14
test_compat.dart
packages/flutter_test/lib/src/test_compat.dart
+24
-0
widget_tester.dart
packages/flutter_test/lib/src/widget_tester.dart
+16
-0
leaking_classes.dart
packages/flutter_test/test/utils/leaking_classes.dart
+54
-0
widget_tester_leaks_test.dart
packages/flutter_test/test/widget_tester_leaks_test.dart
+205
-0
No files found.
packages/flutter/test/flutter_test_config.dart
View file @
baf6ba13
...
@@ -34,23 +34,24 @@ Future<void> testExecutable(FutureOr<void> Function() testMain) {
...
@@ -34,23 +34,24 @@ Future<void> testExecutable(FutureOr<void> Function() testMain) {
// receive the event.
// receive the event.
WidgetController
.
hitTestWarningShouldBeFatal
=
true
;
WidgetController
.
hitTestWarningShouldBeFatal
=
true
;
LeakTracking
.
warnForUnsupportedPlatforms
=
false
;
LeakTesting
.
settings
=
LeakTesting
.
settings
// TODO(polina-c): clean up leaks and stop ignoring them.
// https://github.com/flutter/flutter/issues/137311
.
withIgnored
(
allNotGCed:
true
,
notDisposed:
<
String
,
int
?>{
'OverlayEntry'
:
null
,
},
);
// Leak tracking is off by default.
// Leak tracking is off by default.
// To enable it, follow doc for [_kLeakTracking].
// To enable it, follow doc for [_kLeakTracking].
if
(
_kLeakTracking
)
{
if
(
_kLeakTracking
)
{
LeakTesting
.
settings
=
LeakTesting
.
settings
.
withTrackedAll
();
LeakTesting
.
enable
();
LeakTracking
.
warnForUnsupportedPlatforms
=
false
;
LeakTesting
.
settings
=
LeakTesting
.
settings
.
withTrackedAll
()
// TODO(polina-c): clean up leaks and stop ignoring them.
// https://github.com/flutter/flutter/issues/137311
.
withIgnored
(
allNotGCed:
true
,
notDisposed:
<
String
,
int
?>{
'OverlayEntry'
:
null
,
},
);
}
}
// Enable golden file testing using Skia Gold.
// Enable golden file testing using Skia Gold.
...
...
packages/flutter_test/lib/src/test_compat.dart
View file @
baf6ba13
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
import
'dart:async'
;
import
'dart:async'
;
import
'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'
;
import
'package:meta/meta.dart'
;
import
'package:meta/meta.dart'
;
import
'package:test_api/scaffolding.dart'
show
Timeout
;
import
'package:test_api/scaffolding.dart'
show
Timeout
;
import
'package:test_api/src/backend/declarer.dart'
;
// ignore: implementation_imports
import
'package:test_api/src/backend/declarer.dart'
;
// ignore: implementation_imports
...
@@ -163,6 +164,7 @@ void test(
...
@@ -163,6 +164,7 @@ void test(
Map
<
String
,
dynamic
>?
onPlatform
,
Map
<
String
,
dynamic
>?
onPlatform
,
int
?
retry
,
int
?
retry
,
})
{
})
{
_configureTearDownForTestFile
();
_declarer
.
test
(
_declarer
.
test
(
description
.
toString
(),
description
.
toString
(),
body
,
body
,
...
@@ -186,6 +188,7 @@ void test(
...
@@ -186,6 +188,7 @@ void test(
/// of running the group's tests.
/// of running the group's tests.
@isTestGroup
@isTestGroup
void
group
(
Object
description
,
void
Function
()
body
,
{
dynamic
skip
,
int
?
retry
})
{
void
group
(
Object
description
,
void
Function
()
body
,
{
dynamic
skip
,
int
?
retry
})
{
_configureTearDownForTestFile
();
_declarer
.
group
(
description
.
toString
(),
body
,
skip:
skip
,
retry:
retry
);
_declarer
.
group
(
description
.
toString
(),
body
,
skip:
skip
,
retry:
retry
);
}
}
...
@@ -201,6 +204,7 @@ void group(Object description, void Function() body, { dynamic skip, int? retry
...
@@ -201,6 +204,7 @@ void group(Object description, void Function() body, { dynamic skip, int? retry
/// Each callback at the top level or in a given group will be run in the order
/// Each callback at the top level or in a given group will be run in the order
/// they were declared.
/// they were declared.
void
setUp
(
dynamic
Function
()
body
)
{
void
setUp
(
dynamic
Function
()
body
)
{
_configureTearDownForTestFile
();
_declarer
.
setUp
(
body
);
_declarer
.
setUp
(
body
);
}
}
...
@@ -218,6 +222,7 @@ void setUp(dynamic Function() body) {
...
@@ -218,6 +222,7 @@ void setUp(dynamic Function() body) {
///
///
/// See also [addTearDown], which adds tear-downs to a running test.
/// See also [addTearDown], which adds tear-downs to a running test.
void
tearDown
(
dynamic
Function
()
body
)
{
void
tearDown
(
dynamic
Function
()
body
)
{
_configureTearDownForTestFile
();
_declarer
.
tearDown
(
body
);
_declarer
.
tearDown
(
body
);
}
}
...
@@ -235,6 +240,7 @@ void tearDown(dynamic Function() body) {
...
@@ -235,6 +240,7 @@ void tearDown(dynamic Function() body) {
/// prefer [setUp], and only use [setUpAll] if the callback is prohibitively
/// prefer [setUp], and only use [setUpAll] if the callback is prohibitively
/// slow.
/// slow.
void
setUpAll
(
dynamic
Function
()
body
)
{
void
setUpAll
(
dynamic
Function
()
body
)
{
_configureTearDownForTestFile
();
_declarer
.
setUpAll
(
body
);
_declarer
.
setUpAll
(
body
);
}
}
...
@@ -250,9 +256,27 @@ void setUpAll(dynamic Function() body) {
...
@@ -250,9 +256,27 @@ void setUpAll(dynamic Function() body) {
/// prefer [tearDown], and only use [tearDownAll] if the callback is
/// prefer [tearDown], and only use [tearDownAll] if the callback is
/// prohibitively slow.
/// prohibitively slow.
void
tearDownAll
(
dynamic
Function
()
body
)
{
void
tearDownAll
(
dynamic
Function
()
body
)
{
_configureTearDownForTestFile
();
_declarer
.
tearDownAll
(
body
);
_declarer
.
tearDownAll
(
body
);
}
}
bool
_isTearDownForTestFileConfigured
=
false
;
/// Configures `tearDownAll` after all user defined `tearDownAll` in the test file.
///
/// This function should be invoked in all functions, that may be invoked by user in the test file,
/// to be invoked before any other `tearDownAll`.
void
_configureTearDownForTestFile
(
)
{
if
(
_isTearDownForTestFileConfigured
)
{
return
;
}
_declarer
.
tearDownAll
(
_tearDownForTestFile
);
_isTearDownForTestFileConfigured
=
true
;
}
/// Tear down that should happen after all user defined tear down.
Future
<
void
>
_tearDownForTestFile
()
async
{
await
maybeTearDownLeakTrackingForAll
();
}
/// A reporter that prints each test on its own line.
/// A reporter that prints each test on its own line.
///
///
...
...
packages/flutter_test/lib/src/widget_tester.dart
View file @
baf6ba13
...
@@ -9,6 +9,7 @@ import 'package:flutter/material.dart' show Tooltip;
...
@@ -9,6 +9,7 @@ import 'package:flutter/material.dart' show Tooltip;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/services.dart'
;
import
'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'
;
import
'package:matcher/expect.dart'
as
matcher_expect
;
import
'package:matcher/expect.dart'
as
matcher_expect
;
import
'package:meta/meta.dart'
;
import
'package:meta/meta.dart'
;
import
'package:test_api/scaffolding.dart'
as
test_package
;
import
'package:test_api/scaffolding.dart'
as
test_package
;
...
@@ -116,6 +117,18 @@ E? _lastWhereOrNull<E>(Iterable<E> list, bool Function(E) test) {
...
@@ -116,6 +117,18 @@ E? _lastWhereOrNull<E>(Iterable<E> list, bool Function(E) test) {
/// If the [tags] are passed, they declare user-defined tags that are implemented by
/// If the [tags] are passed, they declare user-defined tags that are implemented by
/// the `test` package.
/// the `test` package.
///
///
/// The argument [experimentalLeakTesting] is experimental and is not recommended
/// for use outside of the Flutter framework.
/// When [experimentalLeakTesting] is set, it is used to leak track objects created
/// during test execution.
/// Otherwise [LeakTesting.settings] is used.
/// Adjust [LeakTesting.settings] in flutter_test_config.dart
/// (see https://github.com/flutter/flutter/blob/master/packages/flutter_test/lib/flutter_test.dart)
/// for the entire package or folder, or in the test's main for a test file
/// (don't use [setUp] or [setUpAll]).
/// To turn off leak tracking just for one test, set [experimentalLeakTesting] to
/// `LeakTrackingForTests.ignore()`.
///
/// ## Sample code
/// ## Sample code
///
///
/// ```dart
/// ```dart
...
@@ -135,6 +148,7 @@ void testWidgets(
...
@@ -135,6 +148,7 @@ void testWidgets(
TestVariant
<
Object
?>
variant
=
const
DefaultTestVariant
(),
TestVariant
<
Object
?>
variant
=
const
DefaultTestVariant
(),
dynamic
tags
,
dynamic
tags
,
int
?
retry
,
int
?
retry
,
LeakTesting
?
experimentalLeakTesting
,
})
{
})
{
assert
(
variant
.
values
.
isNotEmpty
,
'There must be at least one value to test in the testing variant.'
);
assert
(
variant
.
values
.
isNotEmpty
,
'There must be at least one value to test in the testing variant.'
);
final
TestWidgetsFlutterBinding
binding
=
TestWidgetsFlutterBinding
.
ensureInitialized
();
final
TestWidgetsFlutterBinding
binding
=
TestWidgetsFlutterBinding
.
ensureInitialized
();
...
@@ -165,9 +179,11 @@ void testWidgets(
...
@@ -165,9 +179,11 @@ void testWidgets(
Object
?
memento
;
Object
?
memento
;
try
{
try
{
memento
=
await
variant
.
setUp
(
value
);
memento
=
await
variant
.
setUp
(
value
);
maybeSetupLeakTrackingForTest
(
experimentalLeakTesting
,
combinedDescription
);
await
callback
(
tester
);
await
callback
(
tester
);
}
finally
{
}
finally
{
await
variant
.
tearDown
(
value
,
memento
);
await
variant
.
tearDown
(
value
,
memento
);
maybeTearDownLeakTrackingForTest
();
}
}
semanticsHandle
?.
dispose
();
semanticsHandle
?.
dispose
();
},
},
...
...
packages/flutter_test/test/utils/leaking_classes.dart
0 → 100644
View file @
baf6ba13
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/widgets.dart'
;
import
'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'
;
class
LeakTrackedClass
{
LeakTrackedClass
()
{
LeakTracking
.
dispatchObjectCreated
(
library
:
library
,
className:
'
$LeakTrackedClass
'
,
object:
this
,
);
}
static
const
String
library
=
'package:my_package/lib/src/my_lib.dart'
;
void
dispose
()
{
LeakTracking
.
dispatchObjectDisposed
(
object:
this
);
}
}
final
List
<
LeakTrackedClass
>
_notGCedObjects
=
<
LeakTrackedClass
>[];
class
LeakingClass
{
LeakingClass
()
{
_notGCedObjects
.
add
(
LeakTrackedClass
()..
dispose
());
}
}
class
StatelessLeakingWidget
extends
StatelessWidget
{
StatelessLeakingWidget
({
super
.
key
,
this
.
notGCed
=
true
,
this
.
notDisposed
=
true
,
})
{
if
(
notGCed
)
{
_notGCedObjects
.
add
(
LeakTrackedClass
()..
dispose
());
}
if
(
notDisposed
)
{
// ignore: unused_local_variable, it is unused intentionally, to illustrate not disposed object.
final
LeakTrackedClass
notDisposedObject
=
LeakTrackedClass
();
}
}
final
bool
notGCed
;
final
bool
notDisposed
;
@override
Widget
build
(
BuildContext
context
)
{
return
const
Placeholder
();
}
}
packages/flutter_test/test/widget_tester_leaks_test.dart
0 → 100644
View file @
baf6ba13
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'
;
import
'utils/leaking_classes.dart'
;
late
final
String
_test1TrackingOnNoLeaks
;
late
final
String
_test2TrackingOffLeaks
;
late
final
String
_test3TrackingOnLeaks
;
late
final
String
_test4TrackingOnWithCreationStackTrace
;
late
final
String
_test5TrackingOnWithDisposalStackTrace
;
late
final
String
_test6TrackingOnNoLeaks
;
late
final
String
_test7TrackingOnNoLeaks
;
late
final
String
_test8TrackingOnNotDisposed
;
void
main
(
)
{
LeakTesting
.
enable
();
LeakTesting
.
collectedLeaksReporter
=
(
Leaks
leaks
)
=>
verifyLeaks
(
leaks
);
LeakTesting
.
settings
=
LeakTesting
.
settings
.
copyWith
(
ignore:
false
);
// It is important that the test file starts with group, to test that leaks are collected for all tests after group too.
group
(
'Group'
,
()
{
testWidgets
(
'test'
,
(
_
)
async
{
StatelessLeakingWidget
();
});
});
testWidgets
(
_test1TrackingOnNoLeaks
=
'test1, tracking-on, no leaks'
,
(
WidgetTester
widgetTester
)
async
{
expect
(
LeakTracking
.
isStarted
,
true
);
expect
(
LeakTracking
.
phase
.
name
,
_test1TrackingOnNoLeaks
);
expect
(
LeakTracking
.
phase
.
ignoreLeaks
,
false
);
await
widgetTester
.
pumpWidget
(
Container
());
});
testWidgets
(
_test2TrackingOffLeaks
=
'test2, tracking-off, leaks'
,
experimentalLeakTesting:
LeakTesting
.
settings
.
withIgnoredAll
(),
(
WidgetTester
widgetTester
)
async
{
expect
(
LeakTracking
.
isStarted
,
true
);
expect
(
LeakTracking
.
phase
.
name
,
null
);
expect
(
LeakTracking
.
phase
.
ignoreLeaks
,
true
);
await
widgetTester
.
pumpWidget
(
StatelessLeakingWidget
());
});
testWidgets
(
_test3TrackingOnLeaks
=
'test3, tracking-on, leaks'
,
(
WidgetTester
widgetTester
)
async
{
expect
(
LeakTracking
.
isStarted
,
true
);
expect
(
LeakTracking
.
phase
.
name
,
_test3TrackingOnLeaks
);
expect
(
LeakTracking
.
phase
.
ignoreLeaks
,
false
);
await
widgetTester
.
pumpWidget
(
StatelessLeakingWidget
());
});
testWidgets
(
_test4TrackingOnWithCreationStackTrace
=
'test4, tracking-on, with creation stack trace'
,
experimentalLeakTesting:
LeakTesting
.
settings
.
withCreationStackTrace
(),
(
WidgetTester
widgetTester
)
async
{
expect
(
LeakTracking
.
isStarted
,
true
);
expect
(
LeakTracking
.
phase
.
name
,
_test4TrackingOnWithCreationStackTrace
);
expect
(
LeakTracking
.
phase
.
ignoreLeaks
,
false
);
await
widgetTester
.
pumpWidget
(
StatelessLeakingWidget
());
},
);
testWidgets
(
_test5TrackingOnWithDisposalStackTrace
=
'test5, tracking-on, with disposal stack trace'
,
experimentalLeakTesting:
LeakTesting
.
settings
.
withDisposalStackTrace
(),
(
WidgetTester
widgetTester
)
async
{
expect
(
LeakTracking
.
isStarted
,
true
);
expect
(
LeakTracking
.
phase
.
name
,
_test5TrackingOnWithDisposalStackTrace
);
expect
(
LeakTracking
.
phase
.
ignoreLeaks
,
false
);
await
widgetTester
.
pumpWidget
(
StatelessLeakingWidget
());
},
);
testWidgets
(
_test6TrackingOnNoLeaks
=
'test6, tracking-on, no leaks'
,
(
_
)
async
{
LeakTrackedClass
().
dispose
();
});
testWidgets
(
_test7TrackingOnNoLeaks
=
'test7, tracking-on, tear down, no leaks'
,
(
_
)
async
{
final
LeakTrackedClass
myClass
=
LeakTrackedClass
();
addTearDown
(
myClass
.
dispose
);
});
testWidgets
(
_test8TrackingOnNotDisposed
=
'test8, tracking-on, not disposed leak'
,
(
_
)
async
{
expect
(
LeakTracking
.
isStarted
,
true
);
expect
(
LeakTracking
.
phase
.
name
,
_test8TrackingOnNotDisposed
);
expect
(
LeakTracking
.
phase
.
ignoreLeaks
,
false
);
LeakTrackedClass
();
});
}
int
_leakReporterInvocationCount
=
0
;
void
verifyLeaks
(
Leaks
leaks
)
{
_leakReporterInvocationCount
+=
1
;
expect
(
_leakReporterInvocationCount
,
1
);
try
{
expect
(
leaks
,
isLeakFree
);
}
on
TestFailure
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'https://github.com/dart-lang/leak_tracker'
));
expect
(
e
.
message
,
isNot
(
contains
(
_test1TrackingOnNoLeaks
)));
expect
(
e
.
message
,
isNot
(
contains
(
_test2TrackingOffLeaks
)));
expect
(
e
.
message
,
contains
(
'test:
$_test3TrackingOnLeaks
'
));
expect
(
e
.
message
,
contains
(
'test:
$_test4TrackingOnWithCreationStackTrace
'
));
expect
(
e
.
message
,
contains
(
'test:
$_test5TrackingOnWithDisposalStackTrace
'
));
expect
(
e
.
message
,
isNot
(
contains
(
_test6TrackingOnNoLeaks
)));
expect
(
e
.
message
,
isNot
(
contains
(
_test7TrackingOnNoLeaks
)));
expect
(
e
.
message
,
contains
(
'test:
$_test8TrackingOnNotDisposed
'
));
}
_verifyLeaks
(
leaks
,
_test3TrackingOnLeaks
,
notDisposed:
1
,
notGCed:
1
,
expectedContextKeys:
<
LeakType
,
List
<
String
>>{
LeakType
.
notGCed
:
<
String
>[],
LeakType
.
notDisposed
:
<
String
>[],
},
);
_verifyLeaks
(
leaks
,
_test4TrackingOnWithCreationStackTrace
,
notDisposed:
1
,
notGCed:
1
,
expectedContextKeys:
<
LeakType
,
List
<
String
>>{
LeakType
.
notGCed
:
<
String
>[
'start'
],
LeakType
.
notDisposed
:
<
String
>[
'start'
],
},
);
_verifyLeaks
(
leaks
,
_test5TrackingOnWithDisposalStackTrace
,
notDisposed:
1
,
notGCed:
1
,
expectedContextKeys:
<
LeakType
,
List
<
String
>>{
LeakType
.
notGCed
:
<
String
>[
'disposal'
],
LeakType
.
notDisposed
:
<
String
>[],
},
);
_verifyLeaks
(
leaks
,
_test8TrackingOnNotDisposed
,
notDisposed:
1
,
expectedContextKeys:
<
LeakType
,
List
<
String
>>{},
);
}
/// Verifies [allLeaks] contain expected number of leaks for the test [testDescription].
///
/// [notDisposed] and [notGCed] set number for expected leaks by leak type.
/// The method will fail if the leaks context does not contain [expectedContextKeys].
void
_verifyLeaks
(
Leaks
allLeaks
,
String
testDescription
,
{
int
notDisposed
=
0
,
int
notGCed
=
0
,
Map
<
LeakType
,
List
<
String
>>
expectedContextKeys
=
const
<
LeakType
,
List
<
String
>>{},
})
{
final
Leaks
testLeaks
=
Leaks
(
allLeaks
.
byType
.
map
(
(
LeakType
key
,
List
<
LeakReport
>
value
)
=>
MapEntry
<
LeakType
,
List
<
LeakReport
>>(
key
,
value
.
where
((
LeakReport
leak
)
=>
leak
.
phase
==
testDescription
).
toList
()),
),
);
for
(
final
LeakType
type
in
expectedContextKeys
.
keys
)
{
final
List
<
LeakReport
>
leaks
=
testLeaks
.
byType
[
type
]!;
final
List
<
String
>
expectedKeys
=
expectedContextKeys
[
type
]!..
sort
();
for
(
final
LeakReport
leak
in
leaks
)
{
final
List
<
String
>
actualKeys
=
leak
.
context
?.
keys
.
toList
()
??
<
String
>[];
expect
(
actualKeys
..
sort
(),
equals
(
expectedKeys
),
reason:
'
$testDescription
,
$type
'
);
}
}
_verifyLeakList
(
testLeaks
.
notDisposed
,
notDisposed
,
testDescription
,
);
_verifyLeakList
(
testLeaks
.
notGCed
,
notGCed
,
testDescription
,
);
}
void
_verifyLeakList
(
List
<
LeakReport
>
list
,
int
expectedCount
,
String
testDescription
,
)
{
expect
(
list
.
length
,
expectedCount
,
reason:
testDescription
);
for
(
final
LeakReport
leak
in
list
)
{
expect
(
leak
.
trackedClass
,
contains
(
LeakTrackedClass
.
library
));
expect
(
leak
.
trackedClass
,
contains
(
'
$LeakTrackedClass
'
));
}
}
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