Skip to content

Commit

Permalink
Merge branch 'main' of github.com:dart-lang/leak_tracker into publish
Browse files Browse the repository at this point in the history
  • Loading branch information
polina-c committed Jan 9, 2024
2 parents 695ab2f + e7094f4 commit caa51bc
Show file tree
Hide file tree
Showing 35 changed files with 745 additions and 91 deletions.
15 changes: 10 additions & 5 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
# Configuration for .github/workflows/pull_request_label.yml.

'type-infra':
- '.github/**'
- changed-files:
- any-glob-to-any-file: '.github/**'

'package:leak_tracker':
- 'pkgs/leak_tracker/**'
- changed-files:
- any-glob-to-any-file: 'pkgs/leak_tracker/**'

'package:leak_tracker_flutter_testing':
- 'pkgs/leak_tracker_flutter_testing/**'
- changed-files:
- any-glob-to-any-file: 'pkgs/leak_tracker_flutter_testing/**'

'package:leak_tracker_testing':
- 'pkgs/leak_tracker_testing/**'
- changed-files:
- any-glob-to-any-file: 'pkgs/leak_tracker_testing/**'

'package:memory_usage':
- 'pkgs/memory_usage/**'
- changed-files:
- any-glob-to-any-file: 'pkgs/memory_usage/**'
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
2 changes: 1 addition & 1 deletion .github/workflows/pull_request_label.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true
3 changes: 2 additions & 1 deletion examples/leak_tracking/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import 'package:leak_tracker/leak_tracker.dart';

void main() {
LeakTracking.start();
MemoryAllocations.instance.addListener(
// Dispatch memory events from the Flutter engine to LeakTracking.
FlutterMemoryAllocations.instance.addListener(
(ObjectEvent event) => LeakTracking.dispatchObjectEvent(event.toMap()),
);

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);

/// 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.
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
Loading

0 comments on commit caa51bc

Please sign in to comment.