-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ignore test helpers. #196
Ignore test helpers. #196
Changes from all commits
f8ffe3f
abed3f6
4fe85fc
d7c50dd
6f00362
4b8e35f
b57b706
04a4a8e
ac83246
b1f96fe
8c1a473
d801df7
f02b51a
5c8cf7d
e1f3ef1
717055e
5826392
ed4b61e
000e93c
331b852
f3af854
d77c650
378ee00
8ec30cf
688da78
bbb5cbc
0612782
aab7120
01c05a6
e779e7e
277fed3
2603191
9e22673
2dedfa2
2f1d091
fda50bf
8762c46
4e2dae6
c6f2ece
9d77ef8
64d0ebf
13d5003
8cd9920
d8617a5
8ae0506
1697ca0
7d7779f
fba6c60
229d5d4
6cedace
fddae97
e12a1af
7789fbd
22ee0e5
10311d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,6 @@ analyzer: | |
linter: | ||
rules: | ||
- avoid_print | ||
- comment_references | ||
- only_throw_errors | ||
- unawaited_futures |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
/// Frames pointing the folder `test` or the package `flutter_test`. | ||
final _testHelperFrame = RegExp( | ||
r'(?:' + | ||
RegExp.escape(r'/test/') + | ||
r'|' + | ||
RegExp.escape(r'(package:flutter_test/') + | ||
r')', | ||
); | ||
|
||
/// Frames that match [_testHelperFrame], but are not test helpers. | ||
final _exceptions = RegExp( | ||
r'(?:' | ||
r'AutomatedTestWidgetsFlutterBinding.\w|' | ||
r'WidgetTester.\w' | ||
')', | ||
); | ||
|
||
/// Test body or closure inside test body. | ||
final _startFrame = RegExp( | ||
r'(?:' | ||
r'TestAsyncUtils.guard.<anonymous closure>|' | ||
r' main.<anonymous closure>' | ||
r')', | ||
); | ||
|
||
/// Returns whether the leak reported by [objectCreationTrace] | ||
/// was created by a test helper. | ||
/// | ||
/// Frames, that match [exceptions] will be ignored. | ||
/// | ||
/// See details on what means to be created by a test helper | ||
/// in doc for `LeakTesting.createdByTestHelpers`. | ||
bool isCreatedByTestHelper( | ||
String objectCreationTrace, | ||
List<RegExp> exceptions, | ||
) { | ||
final frames = objectCreationTrace.split('\n'); | ||
for (final frame in frames) { | ||
if (_startFrame.hasMatch(frame)) { | ||
return false; | ||
} | ||
if (_testHelperFrame.hasMatch(frame)) { | ||
if (exceptions.any((exception) => exception.hasMatch(frame)) || | ||
_exceptions.hasMatch(frame)) { | ||
continue; | ||
} | ||
return true; | ||
} | ||
} | ||
return false; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,7 +35,8 @@ class IgnoredLeaksSet { | |
|
||
const IgnoredLeaksSet.ignore() : this(ignoreAll: true, byClass: const {}); | ||
|
||
const IgnoredLeaksSet.byClass(this.byClass) : ignoreAll = false; | ||
const IgnoredLeaksSet.byClass(Map<String, int?> byClass) | ||
: this(byClass: byClass); | ||
|
||
/// Classes to ignore during leak tracking. | ||
/// | ||
|
@@ -127,6 +128,8 @@ class IgnoredLeaks { | |
const IgnoredLeaks({ | ||
this.notGCed = const IgnoredLeaksSet(), | ||
this.notDisposed = const IgnoredLeaksSet(), | ||
this.createdByTestHelpers = false, | ||
this.testHelperExceptions = const [], | ||
}); | ||
|
||
/// Ignore list for notGCed leaks. | ||
|
@@ -135,6 +138,22 @@ class IgnoredLeaks { | |
/// Ignore list for notDisposed leaks. | ||
final IgnoredLeaksSet notDisposed; | ||
|
||
/// If true, leaking objects created by test helpers will be ignored. | ||
/// | ||
/// An object counts as created by a test helper if the stack trace of | ||
/// start of leak tracking contains a frame, located after the test body | ||
/// frame, that points to the folder `test` or the package `flutter_test`, | ||
/// except: | ||
/// * methods intended to be called from test body like `runAsunc` or `pump` | ||
/// * frames that match [testHelperExceptions] | ||
final bool createdByTestHelpers; | ||
|
||
/// Stack frames that match this pattern will not be treated as test helpers. | ||
/// | ||
/// Is used to test functionality of | ||
/// the leak tracker. | ||
Comment on lines
+153
to
+154
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this really only used for testing? if so, should this be marked visibleForTesting? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ups. It is used for read in production code of leak tracker. And it is complecsity to make just 'write' test-only. |
||
final List<RegExp> testHelperExceptions; | ||
|
||
/// Returns true if the class is ignored. | ||
/// | ||
/// If [leakType] is null, returns true if the class is ignored for all | ||
|
@@ -161,13 +180,20 @@ class IgnoredLeaks { | |
} | ||
return other is IgnoredLeaks && | ||
other.notGCed == notGCed && | ||
other.notDisposed == notDisposed; | ||
other.notDisposed == notDisposed && | ||
other.createdByTestHelpers == createdByTestHelpers && | ||
const DeepCollectionEquality().equals( | ||
other.testHelperExceptions, | ||
testHelperExceptions, | ||
); | ||
} | ||
|
||
@override | ||
int get hashCode => Object.hash( | ||
notGCed, | ||
notDisposed, | ||
createdByTestHelpers, | ||
testHelperExceptions, | ||
); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | ||
// for details. 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:leak_tracker/src/leak_tracking/primitives/_test_helper_detector.dart'; | ||
polina-c marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import 'package:test/test.dart'; | ||
|
||
class _Test { | ||
final String name; | ||
final String stackTrace; | ||
final bool isHelper; | ||
|
||
_Test({required this.stackTrace, required this.isHelper, required this.name}); | ||
} | ||
|
||
final _tests = [ | ||
_Test( | ||
name: 'empty', | ||
isHelper: false, | ||
stackTrace: '', | ||
), | ||
_Test( | ||
name: 'no test helper', | ||
isHelper: false, | ||
stackTrace: ''' | ||
#0 ObjectTracker.startTracking (package:leak_tracker/src/leak_tracking/_object_tracker.dart:69:31) | ||
#1 LeakTracking.dispatchObjectCreated.<anonymous closure> (package:leak_tracker/src/leak_tracking/leak_tracking.dart:124:35) | ||
#2 LeakTracking.dispatchObjectCreated (package:leak_tracker/src/leak_tracking/leak_tracking.dart:133:6) | ||
#3 new LeakTrackedClass (package:leak_tracker/src/leak_tracking/leak_tracking.dart:10:18) | ||
#4 new StatelessLeakingWidget (package:leak_tracker/src/leak_tracking/leak_tracking.dart:39:27) | ||
#5 main.<anonymous closure> (file:///Users/polinach/_/flutter_dev/packages/flutter_test/test/widget_tester_leaks_test.dart:58:35) | ||
#6 testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:183:29) | ||
<asynchronous suspension> | ||
#7 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1017:5) | ||
<asynchronous suspension> | ||
#8 StackZoneSpecification._registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:114:42) | ||
<asynchronous suspension> | ||
''', | ||
), | ||
_Test( | ||
name: 'test helper, inside runAsync and pumpWidget', | ||
isHelper: true, | ||
stackTrace: ''' | ||
#0 ObjectTracker.startTracking (package:leak_tracker/src/leak_tracking/_object_tracker.dart:69:31) | ||
#1 LeakTracking.dispatchObjectCreated.<anonymous closure> (package:leak_tracker/src/leak_tracking/leak_tracking.dart:126:35) | ||
#2 LeakTracking.dispatchObjectCreated (package:leak_tracker/src/leak_tracking/leak_tracking.dart:135:6) | ||
#3 dispatchObjectEvent (package:leak_tracker/src/leak_tracking/primitives/_dispatcher.dart:48:20) | ||
#4 LeakTracking.dispatchObjectEvent.<anonymous closure> (package:leak_tracker/src/leak_tracking/leak_tracking.dart:103:18) | ||
#5 LeakTracking.dispatchObjectEvent (package:leak_tracker/src/leak_tracking/leak_tracking.dart:109:6) | ||
#6 _dispatchFlutterEventToLeakTracker (package:leak_tracker_flutter_testing/src/testing.dart:73:23) | ||
#7 MemoryAllocations.dispatchObjectEvent (package:flutter/src/foundation/memory_allocations.dart:241:23) | ||
#8 MemoryAllocations._imageOnCreate (package:flutter/src/foundation/memory_allocations.dart:315:5) | ||
#9 new Image._ (dart:ui/painting.dart:1688:15) | ||
#10 Image.clone (dart:ui/painting.dart:1895:18) | ||
#11 createTestImage.<anonymous closure> (package:flutter_test/src/image.dart:42:30) | ||
<asynchronous suspension> | ||
#12 TestAsyncUtils.guard.<anonymous closure> (package:flutter_test/src/test_async_utils.dart:117:7) | ||
<asynchronous suspension> | ||
#13 _AsyncCompleter.complete (dart:async/future_impl.dart:41:3) | ||
<asynchronous suspension> | ||
''', | ||
), | ||
_Test( | ||
name: 'no test helper, inside runAsync and pumpWidget', | ||
isHelper: false, | ||
stackTrace: ''' | ||
#0 ObjectTracker.startTracking (package:leak_tracker/src/leak_tracking/_object_tracker.dart:69:31) | ||
#1 LeakTracking.dispatchObjectCreated.<anonymous closure> (package:leak_tracker/src/leak_tracking/leak_tracking.dart:126:35) | ||
#2 LeakTracking.dispatchObjectCreated (package:leak_tracker/src/leak_tracking/leak_tracking.dart:135:6) | ||
#3 new InstrumentedDisposable (package:leak_tracker_flutter_testing/src/examples.dart:40:18) | ||
#4 new StatelessLeakingWidget (package:leak_tracker_flutter_testing/src/examples.dart:20:27) | ||
#5 main.<anonymous closure>.<anonymous closure> (file:///Users/polinach/_/flutter_dev/packages/flutter_test/test/widget_tester_leaks_test.dart:93:31) | ||
#6 AutomatedTestWidgetsFlutterBinding.runAsync.<anonymous closure> (package:flutter_test/src/binding.dart:1308:17) | ||
#7 _rootRun (dart:async/zone.dart:1399:13) | ||
#8 _CustomZone.run (dart:async/zone.dart:1301:19) | ||
#9 AutomatedTestWidgetsFlutterBinding.runAsync (package:flutter_test/src/binding.dart:1304:26) | ||
#10 WidgetTester.runAsync (package:flutter_test/src/widget_tester.dart:831:17) | ||
#11 main.<anonymous closure> (file:///Users/polinach/_/flutter_dev/packages/flutter_test/test/widget_tester_leaks_test.dart:92:18) | ||
#12 testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:183:29) | ||
<asynchronous suspension> | ||
#13 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1017:5) | ||
<asynchronous suspension> | ||
#14 StackZoneSpecification._registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:114:42) | ||
<asynchronous suspension> | ||
''', | ||
), | ||
_Test( | ||
name: 'test-only GestureRecognizer', | ||
isHelper: true, | ||
stackTrace: ''' | ||
#0 ObjectTracker.startTracking (package:leak_tracker/src/leak_tracking/_object_tracker.dart:69:31) | ||
#1 LeakTracking.dispatchObjectCreated.<anonymous closure> (package:leak_tracker/src/leak_tracking/leak_tracking.dart:126:35) | ||
#2 LeakTracking.dispatchObjectCreated (package:leak_tracker/src/leak_tracking/leak_tracking.dart:135:6) | ||
#3 dispatchObjectEvent (package:leak_tracker/src/leak_tracking/primitives/_dispatcher.dart:48:20) | ||
#4 LeakTracking.dispatchObjectEvent.<anonymous closure> (package:leak_tracker/src/leak_tracking/leak_tracking.dart:103:18) | ||
#5 LeakTracking.dispatchObjectEvent (package:leak_tracker/src/leak_tracking/leak_tracking.dart:109:6) | ||
#6 _dispatchFlutterEventToLeakTracker (package:leak_tracker_flutter_testing/src/testing.dart:73:23) | ||
#7 MemoryAllocations.dispatchObjectEvent (package:flutter/src/foundation/memory_allocations.dart:241:23) | ||
#8 MemoryAllocations.dispatchObjectCreated (package:flutter/src/foundation/memory_allocations.dart:275:5) | ||
#9 new GestureRecognizer (package:flutter/src/gestures/recognizer.dart:111:34) | ||
#10 new IndefiniteGestureRecognizer (file:///Users/polinach/_/flutter_dev/packages/flutter/test/gestures/recognizer_test.dart) | ||
#11 main.<anonymous closure>.<anonymous closure> (file:///Users/polinach/_/flutter_dev/packages/flutter/test/gestures/recognizer_test.dart:122:54) | ||
#12 testGesture.<anonymous closure>.<anonymous closure> (file:///Users/polinach/_/flutter_dev/packages/flutter/test/gestures/gesture_tester.dart:31:15) | ||
#13 FakeAsync.run.<anonymous closure>.<anonymous closure> (package:fake_async/fake_async.dart:182:54) | ||
''', | ||
), | ||
_Test( | ||
name: 'test-only factory method', | ||
isHelper: true, | ||
stackTrace: ''' | ||
#0 ObjectTracker.startTracking (package:leak_tracker/src/leak_tracking/_object_tracker.dart:69:31) | ||
#1 LeakTracking.dispatchObjectCreated.<anonymous closure> (package:leak_tracker/src/leak_tracking/leak_tracking.dart:124:35) | ||
#2 LeakTracking.dispatchObjectCreated (package:leak_tracker/src/leak_tracking/leak_tracking.dart:133:6) | ||
#3 new LeakTrackedClass (package:leak_tracker_flutter_testing/src/test_classes.dart:40:18) | ||
#4 new StatelessLeakingWidget (package:leak_tracker_flutter_testing/src/test_classes.dart:20:27) | ||
#5 createTestWidget (file:///Users/polinach/_/leak_tracker/pkgs/leak_tracker_flutter_testing/test/tests/end_to_end/test_helpers_test.dart:46:46) | ||
#6 main.<anonymous closure> (file:///Users/polinach/_/leak_tracker/pkgs/leak_tracker_flutter_testing/test/tests/end_to_end/test_helpers_test.dart:38:5) | ||
#7 Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:215:19) | ||
<asynchronous suspension> | ||
#8 Declarer.test.<anonymous closure> (package:test_api/src/backend/declarer.dart:213:7) | ||
<asynchronous suspension> | ||
#9 Invoker._waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:258:9) | ||
<asynchronous suspension> | ||
''', | ||
), | ||
]; | ||
|
||
void main() { | ||
for (final t in _tests) { | ||
test('isCreatedByTestHelper: ${t.name}', () { | ||
expect(isCreatedByTestHelper(t.stackTrace, []), t.isHelper); | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
This folder contains model and tests to test that methods like `testWidgets`, | ||
that use API of this package, detect leaks as expected. | ||
|
||
See example of usage in [the test](../../../test/tests/end_to_end/testing_test.dart). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not use
this.byClass
directly like before?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not want to duplicate default for this.ignoreAll