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
3ed03996
Unverified
Commit
3ed03996
authored
Jan 04, 2024
by
Polina Cherkasova
Committed by
GitHub
Jan 04, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Reland "integrate testWidgets with leak tracking" (#140521) (#140928)
parent
c9f23d05
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
320 additions
and
14 deletions
+320
-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
+30
-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 @
3ed03996
...
@@ -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 @
3ed03996
...
@@ -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
,
})
{
})
{
_maybeConfigureTearDownForTestFile
();
_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
})
{
_maybeConfigureTearDownForTestFile
();
_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
)
{
_maybeConfigureTearDownForTestFile
();
_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
)
{
_maybeConfigureTearDownForTestFile
();
_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
)
{
_maybeConfigureTearDownForTestFile
();
_declarer
.
setUpAll
(
body
);
_declarer
.
setUpAll
(
body
);
}
}
...
@@ -250,9 +256,33 @@ void setUpAll(dynamic Function() body) {
...
@@ -250,9 +256,33 @@ 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
)
{
_maybeConfigureTearDownForTestFile
();
_declarer
.
tearDownAll
(
body
);
_declarer
.
tearDownAll
(
body
);
}
}
bool
_isTearDownForTestFileConfigured
=
false
;
/// If needed, 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
_maybeConfigureTearDownForTestFile
(
)
{
if
(
_isTearDownForTestFileConfigured
||
!
_shouldConfigureTearDownForTestFile
())
{
return
;
}
_declarer
.
tearDownAll
(
_tearDownForTestFile
);
_isTearDownForTestFileConfigured
=
true
;
}
/// Returns true if tear down for the test file needs to be configured.
bool
_shouldConfigureTearDownForTestFile
(
)
{
return
LeakTesting
.
enabled
;
}
/// 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 @
3ed03996
...
@@ -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 @
3ed03996
// 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 @
3ed03996
// 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