Skip to content
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

Merged
merged 55 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
f8ffe3f
Update model.dart
polina-c Dec 11, 2023
abed3f6
Merge branch 'main' of github.com:dart-lang/leak_tracker into ignoreF…
polina-c Dec 11, 2023
4fe85fc
-
polina-c Dec 17, 2023
d7c50dd
Merge branch 'main' of github.com:dart-lang/leak_tracker into ignoreF…
polina-c Dec 17, 2023
6f00362
Create test_helpers_test.dart
polina-c Dec 18, 2023
4b8e35f
-
polina-c Dec 18, 2023
b57b706
-
polina-c Dec 19, 2023
04a4a8e
Update test_helpers_test.dart
polina-c Dec 19, 2023
ac83246
Update leak_testing.dart
polina-c Dec 19, 2023
b1f96fe
Update _test_helper_detector.dart
polina-c Dec 19, 2023
8c1a473
Update _test_helper_detector.dart
polina-c Dec 20, 2023
d801df7
-
polina-c Dec 22, 2023
f02b51a
Update testing.dart
polina-c Dec 22, 2023
5c8cf7d
Update testing_test.dart
polina-c Dec 22, 2023
e1f3ef1
Update testing.dart
polina-c Dec 22, 2023
717055e
Merge branch 'switch' into ignoreFrame
polina-c Dec 22, 2023
5826392
-
polina-c Dec 23, 2023
ed4b61e
Merge branch 'main' of github.com:dart-lang/leak_tracker into switch
polina-c Dec 23, 2023
000e93c
-
polina-c Dec 23, 2023
331b852
Merge branch 'main' of github.com:dart-lang/leak_tracker into ignoreF…
polina-c Dec 23, 2023
f3af854
Merge branch 'switch' into ignoreFrame
polina-c Dec 23, 2023
d77c650
Update leak_tracker_flutter_testing.dart
polina-c Dec 23, 2023
378ee00
Update test_helpers_test.dart
polina-c Dec 23, 2023
8ec30cf
-
polina-c Dec 23, 2023
688da78
Update _test_helper_detector.dart
polina-c Dec 23, 2023
bbb5cbc
Update _test_helper_detector.dart
polina-c Dec 23, 2023
0612782
Update _test_helper_detector.dart
polina-c Dec 23, 2023
aab7120
-
polina-c Dec 23, 2023
01c05a6
-
polina-c Dec 25, 2023
e779e7e
-
polina-c Dec 25, 2023
277fed3
Update leak_testing.dart
polina-c Dec 25, 2023
2603191
Update leak_testing.dart
polina-c Dec 25, 2023
9e22673
Merge branch 'switch-1' into ignoreFrame-2
polina-c Dec 25, 2023
2dedfa2
Update test_case.dart
polina-c Dec 25, 2023
2f1d091
-
polina-c Dec 25, 2023
fda50bf
-
polina-c Dec 26, 2023
8762c46
Update test_settings.dart
polina-c Dec 26, 2023
4e2dae6
-
polina-c Dec 26, 2023
c6f2ece
Update test_case.dart
polina-c Dec 26, 2023
9d77ef8
Update test_settings.dart
polina-c Dec 30, 2023
64d0ebf
Merge branch 'main' of github.com:dart-lang/leak_tracker into ignoreF…
polina-c Dec 30, 2023
13d5003
Merge branch 'main' of github.com:dart-lang/leak_tracker into ignoreF…
polina-c Jan 4, 2024
8cd9920
-
polina-c Jan 5, 2024
d8617a5
-
polina-c Jan 5, 2024
8ae0506
-
polina-c Jan 5, 2024
1697ca0
Update test_settings.dart
polina-c Jan 5, 2024
7d7779f
Update _test_helper_detector.dart
polina-c Jan 5, 2024
fba6c60
-
polina-c Jan 6, 2024
229d5d4
Update _test_helper_detector.dart
polina-c Jan 6, 2024
6cedace
-
polina-c Jan 6, 2024
fddae97
Update test_helpers_test.dart
polina-c Jan 7, 2024
e12a1af
-
polina-c Jan 7, 2024
7789fbd
-
polina-c Jan 8, 2024
22ee0e5
Update model.dart
polina-c Jan 8, 2024
10311d7
-
polina-c Jan 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ jobs:
run: dart test
working-directory: pkgs/leak_tracker

- name: dart test
run: dart test
working-directory: pkgs/leak_tracker_testing

- name: flutter test
run: flutter test --enable-vmservice
working-directory: pkgs/leak_tracker_flutter_testing
Expand Down
4 changes: 4 additions & 0 deletions pkgs/leak_tracker/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 10.0.1

* Allow to ignore objects created by test helpers.

## 10.0.0

* Remove `memory_usage`, as it is moved to https://github.com/dart-lang/leak_tracker/tree/main/pkgs/memory_usage.
Expand Down
3 changes: 3 additions & 0 deletions pkgs/leak_tracker/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ analyzer:
linter:
rules:
- avoid_print
- comment_references
- only_throw_errors
- unawaited_futures
16 changes: 14 additions & 2 deletions pkgs/leak_tracker/lib/src/leak_tracking/_object_tracker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'primitives/_finalizer.dart';
import 'primitives/_gc_counter.dart';
import 'primitives/_retaining_path/_connection.dart';
import 'primitives/_retaining_path/_retaining_path.dart';
import 'primitives/_test_helper_detector.dart';
import 'primitives/model.dart';

/// Keeps collection of object records until
Expand Down Expand Up @@ -62,11 +63,22 @@ class ObjectTracker implements LeakProvider {
throwIfDisposed();
if (phase.ignoreLeaks) return;

StackTrace? stackTrace;

if (phase.ignoredLeaks.createdByTestHelpers) {
stackTrace = StackTrace.current;
if (isCreatedByTestHelper(
stackTrace.toString(),
phase.ignoredLeaks.testHelperExceptions,
)) return;
}

final record =
_objects.notGCed.putIfAbsent(object, context, phase, trackedClass);

if (phase.leakDiagnosticConfig.collectStackTraceOnStart) {
record.setContext(ContextKeys.startCallstack, StackTrace.current);
stackTrace ??= StackTrace.current;
record.setContext(ContextKeys.startCallstack, stackTrace);
}

_finalizer.attach(object, record);
Expand All @@ -89,7 +101,7 @@ class ObjectTracker implements LeakProvider {
void _declareNotDisposedLeak(ObjectRecord record) {
if (record.isGCedLateLeak(disposalTime, numberOfGcCycles)) {
_objects.gcedLateLeaks.add(record);
} else if (record.isNotDisposedLeak) {
} else if (!record.isDisposed) {
_objects.gcedNotDisposedLeaks.add(record);
}

Expand Down
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;
}
30 changes: 28 additions & 2 deletions pkgs/leak_tracker/lib/src/leak_tracking/primitives/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment on lines +38 to +39

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?

Copy link
Contributor Author

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


/// Classes to ignore during leak tracking.
///
Expand Down Expand Up @@ -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.
Expand All @@ -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

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

@polina-c polina-c Jan 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm...
Indeed.
I did not know I can use test only members in other packages too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand 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,
);
}

Expand Down
17 changes: 17 additions & 0 deletions pkgs/leak_tracker/lib/src/shared/shared_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ class LeakSummary {
class Leaks {
Leaks(this.byType);

Leaks.empty() : this({});

factory Leaks.fromJson(Map<String, dynamic> json) => Leaks(
json.map(
(key, value) => MapEntry(
Expand All @@ -116,6 +118,21 @@ class Leaks {

int get total => byType.values.map((e) => e.length).sum;

late final Map<String?, Leaks> byPhase = () {
final leaks = <String?, Map<LeakType, List<LeakReport>>>{};
for (final entry in byType.entries) {
for (final leak in entry.value) {
leaks
.putIfAbsent(leak.phase, () => {})
.putIfAbsent(entry.key, () => <LeakReport>[])
.add(leak);
}
}
return {
for (final entry in leaks.entries) entry.key: Leaks(entry.value),
};
}();

String toYaml({required bool phasesAreTests}) {
if (total == 0) return '';
final leaks = LeakType.values
Expand Down
2 changes: 1 addition & 1 deletion pkgs/leak_tracker/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: leak_tracker
version: 10.0.0
version: 10.0.1
description: A framework for memory leak tracking for Dart and Flutter applications.
repository: https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker

Expand Down
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);
});
}
}
5 changes: 5 additions & 0 deletions pkgs/leak_tracker_flutter_testing/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.0.3

* Allow to ignore objects created by test helpers.
* Upgrade to leak_tracker 10.0.1 and leak_tracker_testing 2.0.2.

## 2.0.2

* Replaced depracated `MemoryAllocations` with `FlutterMemoryAllocations`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ export 'package:leak_tracker_testing/leak_tracker_testing.dart'
export 'src/matchers.dart';
export 'src/model.dart';
export 'src/testing.dart';
export 'src/testing_for_testing/leaking_classes.dart';
export 'src/testing_for_testing/test_case.dart';
export 'src/testing_for_testing/test_settings.dart';
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).
Loading