blob: 523aaac268f507bc51cc366feba6148e5014c567 [file]
// Copyright (c) 2023, 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:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker/leak_tracker.dart';
import 'package:leak_tracker_testing/leak_tracker_testing.dart';
import 'package:meta/meta.dart';
// TODO: remove this library
// https://github.com/flutter/flutter/issues/135856
void _flutterEventToLeakTracker(ObjectEvent event) {
return LeakTracking.dispatchObjectEvent(event.toMap());
}
void _setUpTestingWithLeakTracking() {
_printPlatformWarningIfNeeded();
if (!_isPlatformSupported) return;
LeakTracking.phase = const PhaseSettings.ignored();
LeakTracking.start(config: LeakTrackingConfig.passive());
MemoryAllocations.instance.addListener(_flutterEventToLeakTracker);
}
bool _stopConfiguringTearDown = false;
/// Sets [tearDownAll] to tear down leak tracking if it is started.
///
/// [configureOnce] is true tear down will be created just once,
/// not for every test.
/// Multiple [tearDownAll] is needed to handle test groups that have
/// own [tearDownAll].
///
/// Single tear down is needed to test leak tracking.
@visibleForTesting
void configureLeakTrackingTearDown({
LeaksCallback? onLeaks,
bool configureOnce = false,
}) {
if (_isPlatformSupported && !_stopConfiguringTearDown) {
tearDownAll(() async {
if (LeakTracking.isStarted) {
await _tearDownTestingWithLeakTracking(onLeaks);
}
});
}
if (configureOnce) _stopConfiguringTearDown = true;
}
Future<void> _tearDownTestingWithLeakTracking(LeaksCallback? onLeaks) async {
if (!LeakTracking.isStarted) return;
if (!_isPlatformSupported) return;
MemoryAllocations.instance.removeListener(_flutterEventToLeakTracker);
LeakTracking.declareNotDisposedObjectsAsLeaks();
await forceGC(fullGcCycles: defaultNumberOfGcCycles);
final leaks = await LeakTracking.collectLeaks();
LeakTracking.stop();
if (leaks.total == 0) return;
if (onLeaks == null) {
expect(leaks, isLeakFree);
} else {
onLeaks(leaks);
}
}
// TODO(polina-c): retire this function after migration to `testWidgets`.
// https://github.com/flutter/flutter/issues/135856
/// Wrapper for [testWidgets] with memory leak tracking.
///
/// The test will fail if instrumented objects in [callback] are
/// garbage collected without being disposed or not garbage
/// collected soon after disposal.
///
/// More about leak tracking: https://github.com/dart-lang/leak_tracker.
@isTest
void testWidgetsWithLeakTracking(
String description,
WidgetTesterCallback callback, {
bool? skip,
Timeout? timeout,
bool semanticsEnabled = true,
TestVariant<Object?> variant = const DefaultTestVariant(),
dynamic tags,
LeakTesting? leakTesting,
}) {
configureLeakTrackingTearDown(onLeaks: leakTesting?.onLeaks);
Future<void> wrappedCallBack(WidgetTester tester) async {
final settings = leakTesting ?? LeakTesting.settings;
final phase = PhaseSettings(
name: description,
leakDiagnosticConfig: settings.leakDiagnosticConfig,
ignoredLeaks: settings.ignoredLeaks,
baselining: const MemoryBaselining.none(),
ignoreLeaks: settings.ignore,
);
if (!LeakTracking.isStarted) _setUpTestingWithLeakTracking();
LeakTracking.phase = phase;
await callback(tester);
LeakTracking.phase = const PhaseSettings.ignored();
}
// Temporarily turn off leak tracking, so that `testWidgets` does not track leaks in addition.
final originalGlobalSettings = LeakTesting.settings;
LeakTesting.settings = LeakTesting.settings.withIgnoredAll();
try {
testWidgets(
description,
wrappedCallBack,
skip: skip,
timeout: timeout,
semanticsEnabled: semanticsEnabled,
variant: variant,
tags: tags,
);
} finally {
LeakTesting.settings = originalGlobalSettings;
}
}
bool _notSupportedWarningPrinted = false;
bool get _isPlatformSupported => !kIsWeb;
void _printPlatformWarningIfNeeded() {
if (kIsWeb) {
final bool shouldPrintWarning = !_notSupportedWarningPrinted &&
LeakTracking.warnForUnsupportedPlatforms;
if (shouldPrintWarning) {
_notSupportedWarningPrinted = true;
debugPrint(
'Leak tracking is not supported on web platform.\nTo turn off this message, set `LeakTracking.warnForNotSupportedPlatforms` to false.',
);
}
return;
}
assert(_isPlatformSupported);
}