Unverified Commit 5a80f8d6 authored by Polina Cherkasova's avatar Polina Cherkasova Committed by GitHub

Define testWidgetsWithLeakTracking. (#125063)

parent fde717da
...@@ -16,6 +16,8 @@ void main() { ...@@ -16,6 +16,8 @@ void main() {
* because [matchesGoldenFile] does not use Skia Gold in its native package. * because [matchesGoldenFile] does not use Skia Gold in its native package.
*/ */
// TODO(polina-c): fix ValueNotifier not disposed and switch to testWidgetsWithLeakTracking.
// https://github.com/flutter/devtools/issues/3951
testWidgets('correctly records frames using display', (WidgetTester tester) async { testWidgets('correctly records frames using display', (WidgetTester tester) async {
final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size); final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size);
...@@ -52,6 +54,8 @@ void main() { ...@@ -52,6 +54,8 @@ void main() {
await expectLater(find.byWidget(display), matchesGoldenFile('test.animation_sheet_builder.records.png')); await expectLater(find.byWidget(display), matchesGoldenFile('test.animation_sheet_builder.records.png'));
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
// TODO(polina-c): fix ValueNotifier not disposed and switch to testWidgetsWithLeakTracking.
// https://github.com/flutter/devtools/issues/3951
testWidgets('correctly wraps a row', (WidgetTester tester) async { testWidgets('correctly wraps a row', (WidgetTester tester) async {
final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size); final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size);
...@@ -70,6 +74,8 @@ void main() { ...@@ -70,6 +74,8 @@ void main() {
await expectLater(find.byWidget(display), matchesGoldenFile('test.animation_sheet_builder.wraps.png')); await expectLater(find.byWidget(display), matchesGoldenFile('test.animation_sheet_builder.wraps.png'));
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
// TODO(polina-c): fix Picture and Image not disposed and and switch to testWidgetsWithLeakTracking.
// https://github.com/flutter/devtools/issues/3951
testWidgets('correctly records frames using collate', (WidgetTester tester) async { testWidgets('correctly records frames using collate', (WidgetTester tester) async {
final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size); final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size);
...@@ -104,6 +110,8 @@ void main() { ...@@ -104,6 +110,8 @@ void main() {
); );
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
// TODO(polina-c): fix Picture and Image not disposed and switch to testWidgetsWithLeakTracking.
// https://github.com/flutter/devtools/issues/3951
testWidgets('use allLayers to record out-of-subtree contents', (WidgetTester tester) async { testWidgets('use allLayers to record out-of-subtree contents', (WidgetTester tester) async {
final AnimationSheetBuilder builder = AnimationSheetBuilder( final AnimationSheetBuilder builder = AnimationSheetBuilder(
frameSize: const Size(8, 2), frameSize: const Size(8, 2),
......
...@@ -6,8 +6,10 @@ import 'package:flutter/animation.dart'; ...@@ -6,8 +6,10 @@ import 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
void main() { void main() {
testWidgets('awaiting animation controllers - using direct future', (WidgetTester tester) async { testWidgetsWithLeakTracking('awaiting animation controllers - using direct future', (WidgetTester tester) async {
final AnimationController controller1 = AnimationController( final AnimationController controller1 = AnimationController(
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
vsync: const TestVSync(), vsync: const TestVSync(),
...@@ -56,7 +58,7 @@ void main() { ...@@ -56,7 +58,7 @@ void main() {
expect(log, <String>['start', 'a', 'b', 'c', 'd', 'end']); expect(log, <String>['start', 'a', 'b', 'c', 'd', 'end']);
}); });
testWidgets('awaiting animation controllers - using orCancel', (WidgetTester tester) async { testWidgetsWithLeakTracking('awaiting animation controllers - using orCancel', (WidgetTester tester) async {
final AnimationController controller1 = AnimationController( final AnimationController controller1 = AnimationController(
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
vsync: const TestVSync(), vsync: const TestVSync(),
...@@ -105,7 +107,7 @@ void main() { ...@@ -105,7 +107,7 @@ void main() {
expect(log, <String>['start', 'a', 'b', 'c', 'd', 'end']); expect(log, <String>['start', 'a', 'b', 'c', 'd', 'end']);
}); });
testWidgets('awaiting animation controllers and failing', (WidgetTester tester) async { testWidgetsWithLeakTracking('awaiting animation controllers and failing', (WidgetTester tester) async {
final AnimationController controller1 = AnimationController( final AnimationController controller1 = AnimationController(
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
vsync: const TestVSync(), vsync: const TestVSync(),
...@@ -133,7 +135,7 @@ void main() { ...@@ -133,7 +135,7 @@ void main() {
expect(log, <String>['start', 'caught', 'end']); expect(log, <String>['start', 'caught', 'end']);
}); });
testWidgets('creating orCancel future later', (WidgetTester tester) async { testWidgetsWithLeakTracking('creating orCancel future later', (WidgetTester tester) async {
final AnimationController controller1 = AnimationController( final AnimationController controller1 = AnimationController(
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
vsync: const TestVSync(), vsync: const TestVSync(),
...@@ -146,7 +148,7 @@ void main() { ...@@ -146,7 +148,7 @@ void main() {
expect(true, isTrue); // should reach here expect(true, isTrue); // should reach here
}); });
testWidgets('creating orCancel future later', (WidgetTester tester) async { testWidgetsWithLeakTracking('creating orCancel future later', (WidgetTester tester) async {
final AnimationController controller1 = AnimationController( final AnimationController controller1 = AnimationController(
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
vsync: const TestVSync(), vsync: const TestVSync(),
...@@ -163,7 +165,7 @@ void main() { ...@@ -163,7 +165,7 @@ void main() {
expect(ok, isTrue); // should reach here expect(ok, isTrue); // should reach here
}); });
testWidgets('TickerFuture is a Future', (WidgetTester tester) async { testWidgetsWithLeakTracking('TickerFuture is a Future', (WidgetTester tester) async {
final AnimationController controller1 = AnimationController( final AnimationController controller1 = AnimationController(
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
vsync: const TestVSync(), vsync: const TestVSync(),
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
void main() { void main() {
setUp(() { setUp(() {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
...@@ -79,7 +81,7 @@ void main() { ...@@ -79,7 +81,7 @@ void main() {
controller.dispose(); controller.dispose();
}); });
testWidgets('AnimationController with throwing listener', (WidgetTester tester) async { testWidgetsWithLeakTracking('AnimationController with throwing listener', (WidgetTester tester) async {
final AnimationController controller = AnimationController( final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
vsync: const TestVSync(), vsync: const TestVSync(),
...@@ -102,7 +104,7 @@ void main() { ...@@ -102,7 +104,7 @@ void main() {
log.clear(); log.clear();
}); });
testWidgets('AnimationController with throwing status listener', (WidgetTester tester) async { testWidgetsWithLeakTracking('AnimationController with throwing status listener', (WidgetTester tester) async {
final AnimationController controller = AnimationController( final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
vsync: const TestVSync(), vsync: const TestVSync(),
......
...@@ -10,6 +10,8 @@ library; ...@@ -10,6 +10,8 @@ library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
void main() { void main() {
/* /*
* Here lies golden tests for packages/flutter_test/lib/src/binding.dart * Here lies golden tests for packages/flutter_test/lib/src/binding.dart
...@@ -18,7 +20,7 @@ void main() { ...@@ -18,7 +20,7 @@ void main() {
LiveTestWidgetsFlutterBinding().framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.onlyPumps; LiveTestWidgetsFlutterBinding().framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.onlyPumps;
testWidgets('Should show event indicator for pointer events', (WidgetTester tester) async { testWidgetsWithLeakTracking('Should show event indicator for pointer events', (WidgetTester tester) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(200, 200), allLayers: true); final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(200, 200), allLayers: true);
final List<Offset> taps = <Offset>[]; final List<Offset> taps = <Offset>[];
Widget target({bool recording = true}) => Container( Widget target({bool recording = true}) => Container(
...@@ -76,6 +78,8 @@ void main() { ...@@ -76,6 +78,8 @@ void main() {
// Currently skipped due to daily flake: https://github.com/flutter/flutter/issues/87588 // Currently skipped due to daily flake: https://github.com/flutter/flutter/issues/87588
}, skip: true); // Typically skip: isBrowser https://github.com/flutter/flutter/issues/42767 }, skip: true); // Typically skip: isBrowser https://github.com/flutter/flutter/issues/42767
// TODO(polina-c): fix ValueNotifier not disposed and switch to testWidgetsWithLeakTracking.
// https://github.com/flutter/devtools/issues/3951
testWidgets('Should show event indicator for pointer events with setSurfaceSize', (WidgetTester tester) async { testWidgets('Should show event indicator for pointer events with setSurfaceSize', (WidgetTester tester) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(200, 200), allLayers: true); final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(200, 200), allLayers: true);
final List<Offset> taps = <Offset>[]; final List<Offset> taps = <Offset>[];
......
...@@ -6,10 +6,14 @@ import 'dart:async'; ...@@ -6,10 +6,14 @@ import 'dart:async';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker/leak_tracker.dart';
import '_goldens_io.dart' import '_goldens_io.dart'
if (dart.library.html) '_goldens_web.dart' as flutter_goldens; if (dart.library.html) '_goldens_web.dart' as flutter_goldens;
/// Test configuration for each test library in this directory.
///
/// See https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html.
Future<void> testExecutable(FutureOr<void> Function() testMain) { Future<void> testExecutable(FutureOr<void> Function() testMain) {
// Enable checks because there are many implementations of [RenderBox] in this // Enable checks because there are many implementations of [RenderBox] in this
// package can benefit from the additional validations. // package can benefit from the additional validations.
...@@ -19,6 +23,8 @@ Future<void> testExecutable(FutureOr<void> Function() testMain) { ...@@ -19,6 +23,8 @@ Future<void> testExecutable(FutureOr<void> Function() testMain) {
// receive the event. // receive the event.
WidgetController.hitTestWarningShouldBeFatal = true; WidgetController.hitTestWarningShouldBeFatal = true;
LeakTrackingTestConfig.warnForNonSupportedPlatforms = false;
// Enable golden file testing using Skia Gold. // Enable golden file testing using Skia Gold.
return flutter_goldens.testExecutable(testMain); return flutter_goldens.testExecutable(testMain);
} }
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'leak_tracking.dart';
class TestNotifier extends ChangeNotifier { class TestNotifier extends ChangeNotifier {
void notify() { void notify() {
notifyListeners(); notifyListeners();
...@@ -49,23 +51,25 @@ class Counter with ChangeNotifier { ...@@ -49,23 +51,25 @@ class Counter with ChangeNotifier {
} }
void main() { void main() {
testWidgets('ChangeNotifier can not dispose in callback', (WidgetTester tester) async { testWidgetsWithLeakTracking('ChangeNotifier can not dispose in callback', (WidgetTester tester) async {
final TestNotifier test = TestNotifier(); final TestNotifier test = TestNotifier();
bool callbackDidFinish = false; bool callbackDidFinish = false;
void foo() { void foo() {
test.dispose(); test.dispose();
callbackDidFinish = true; callbackDidFinish = true;
} }
test.addListener(foo); test.addListener(foo);
test.notify(); test.notify();
final AssertionError error = tester.takeException() as AssertionError; final AssertionError error = tester.takeException() as AssertionError;
expect(error.toString().contains('dispose()'), isTrue); expect(error.toString().contains('dispose()'), isTrue);
// Make sure it crashes during dispose call. // Make sure it crashes during dispose call.
expect(callbackDidFinish, isFalse); expect(callbackDidFinish, isFalse);
test.dispose();
}); });
testWidgets('ChangeNotifier', (WidgetTester tester) async { testWidgetsWithLeakTracking('ChangeNotifier', (WidgetTester tester) async {
final List<String> log = <String>[]; final List<String> log = <String>[];
void listener() { void listener() {
log.add('listener'); log.add('listener');
...@@ -147,6 +151,7 @@ void main() { ...@@ -147,6 +151,7 @@ void main() {
expect(log, <String>['badListener', 'listener1', 'listener2']); expect(log, <String>['badListener', 'listener1', 'listener2']);
expect(tester.takeException(), isArgumentError); expect(tester.takeException(), isArgumentError);
log.clear(); log.clear();
test.dispose();
}); });
test('ChangeNotifier with mutating listener', () { test('ChangeNotifier with mutating listener', () {
......
...@@ -5,9 +5,75 @@ ...@@ -5,9 +5,75 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker/leak_tracker.dart'; import 'package:leak_tracker/leak_tracker.dart';
import 'package:meta/meta.dart';
typedef LeaksObtainer = void Function(Leaks foundLeaks); /// Set of objects, that does not hold the objects from garbage collection.
///
/// The objects are referenced by hash codes and can duplicate with low probability.
@visibleForTesting
class WeakSet {
final Set<String> _objectCodes = <String>{};
String _toCode(int hashCode, String type) => '$type-$hashCode';
void add(Object object) {
_objectCodes.add(_toCode(identityHashCode(object), object.runtimeType.toString()));
}
void addByCode(int hashCode, String type) {
_objectCodes.add(_toCode(hashCode, type));
}
bool contains(int hashCode, String type) {
final bool result = _objectCodes.contains(_toCode(hashCode, type));
return result;
}
}
/// Wrapper for [testWidgets] with memory leak tracking.
///
/// The method will fail if instrumented objects in [callback] are
/// garbage collected without being disposed.
///
/// More about leak tracking:
/// https://github.com/dart-lang/leak_tracker.
///
/// See https://github.com/flutter/devtools/issues/3951 for plans
/// on leak tracking.
@isTest
void testWidgetsWithLeakTracking(
String description,
WidgetTesterCallback callback, {
bool? skip,
Timeout? timeout,
bool semanticsEnabled = true,
TestVariant<Object?> variant = const DefaultTestVariant(),
dynamic tags,
LeakTrackingTestConfig leakTrackingConfig = const LeakTrackingTestConfig(),
}) {
Future<void> wrappedCallback(WidgetTester tester) async {
await _withFlutterLeakTracking(
() async => callback(tester),
tester,
leakTrackingConfig,
);
}
testWidgets(
description,
wrappedCallback,
skip: skip,
timeout: timeout,
semanticsEnabled: semanticsEnabled,
variant: variant,
tags: tags,
);
}
bool _webWarningPrinted = false;
/// Runs [callback] with leak tracking.
///
/// Wrapper for [withLeakTracking] with Flutter specific functionality. /// Wrapper for [withLeakTracking] with Flutter specific functionality.
/// ///
/// The method will fail if wrapped code contains memory leaks. /// The method will fail if wrapped code contains memory leaks.
...@@ -19,27 +85,20 @@ typedef LeaksObtainer = void Function(Leaks foundLeaks); ...@@ -19,27 +85,20 @@ typedef LeaksObtainer = void Function(Leaks foundLeaks);
/// 1. Listens to [MemoryAllocations] events. /// 1. Listens to [MemoryAllocations] events.
/// 2. Uses `tester.runAsync` for leak detection if [tester] is provided. /// 2. Uses `tester.runAsync` for leak detection if [tester] is provided.
/// ///
/// If you use [testWidgets], pass [tester] to avoid async issues in leak processing. /// Pass [config] to troubleshoot or exempt leaks. See [LeakTrackingTestConfig]
/// Pass null otherwise. /// for details.
/// Future<void> _withFlutterLeakTracking(
/// Pass [leaksObtainer] if you want to get leak information before DartAsyncCallback callback,
/// the method failure. WidgetTester tester,
Future<void> withFlutterLeakTracking( LeakTrackingTestConfig config,
DartAsyncCallback callback, { ) async {
required WidgetTester? tester,
StackTraceCollectionConfig stackTraceCollectionConfig =
const StackTraceCollectionConfig(),
Duration? timeoutForFinalGarbageCollection,
LeaksObtainer? leaksObtainer,
}) async {
// The method is copied (with improvements) from
// `package:leak_tracker/test/test_infra/flutter_helpers.dart`.
// The method is not combined with [testWidgets], because the combining will
// impact VSCode's ability to recognize tests.
// Leak tracker does not work for web platform. // Leak tracker does not work for web platform.
if (kIsWeb) { if (kIsWeb) {
final bool shouldPrintWarning = !_webWarningPrinted && LeakTrackingTestConfig.warnForNonSupportedPlatforms;
if (shouldPrintWarning) {
_webWarningPrinted = true;
debugPrint('Leak tracking is not supported on web platform.\nTo turn off this message, set `LeakTrackingTestConfig.warnForNonSupportedPlatforms` to false.');
}
await callback(); await callback();
return; return;
} }
...@@ -50,23 +109,59 @@ Future<void> withFlutterLeakTracking( ...@@ -50,23 +109,59 @@ Future<void> withFlutterLeakTracking(
return TestAsyncUtils.guard<void>(() async { return TestAsyncUtils.guard<void>(() async {
MemoryAllocations.instance.addListener(flutterEventToLeakTracker); MemoryAllocations.instance.addListener(flutterEventToLeakTracker);
final AsyncCodeRunner asyncCodeRunner = tester == null Future<void> asyncCodeRunner(DartAsyncCallback action) async => tester.runAsync(action);
? (DartAsyncCallback action) async => action()
: (DartAsyncCallback action) async => tester.runAsync(action);
try { try {
final Leaks leaks = await withLeakTracking( Leaks leaks = await withLeakTracking(
callback, callback,
asyncCodeRunner: asyncCodeRunner, asyncCodeRunner: asyncCodeRunner,
stackTraceCollectionConfig: stackTraceCollectionConfig, stackTraceCollectionConfig: config.stackTraceCollectionConfig,
shouldThrowOnLeaks: false, shouldThrowOnLeaks: false,
timeoutForFinalGarbageCollection: timeoutForFinalGarbageCollection,
); );
if (leaksObtainer != null) {
leaksObtainer(leaks); leaks = LeakCleaner(config).clean(leaks);
if (leaks.total > 0) {
config.onLeaks?.call(leaks);
if (config.failTestOnLeaks) {
expect(leaks, isLeakFree);
}
} }
expect(leaks, isLeakFree);
} finally { } finally {
MemoryAllocations.instance.removeListener(flutterEventToLeakTracker); MemoryAllocations.instance.removeListener(flutterEventToLeakTracker);
} }
}); });
} }
/// Cleans leaks that are allowed by [config].
@visibleForTesting
class LeakCleaner {
LeakCleaner(this.config);
final LeakTrackingTestConfig config;
Leaks clean(Leaks leaks) {
final Leaks result = Leaks(<LeakType, List<LeakReport>>{
for (LeakType leakType in leaks.byType.keys)
leakType: leaks.byType[leakType]!.where((LeakReport leak) => _shouldReportLeak(leakType, leak)).toList()
});
return result;
}
/// Returns true if [leak] should be reported as failure.
bool _shouldReportLeak(LeakType leakType, LeakReport leak) {
// Tracking for non-GCed is temporarily disabled.
// TODO(polina-c): turn on tracking for non-GCed after investigating existing leaks.
if (leakType != LeakType.notDisposed) {
return false;
}
switch (leakType) {
case LeakType.notDisposed:
return !config.notDisposedAllowList.contains(leak.type);
case LeakType.notGCed:
case LeakType.gcedLate:
return !config.notGCedAllowList.contains(leak.type);
}
}
}
// 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:flutter_test/flutter_test.dart';
import 'package:leak_tracker/leak_tracker.dart';
import 'leak_tracking.dart';
final String _leakTrackedClassName = '$_LeakTrackedClass';
Leaks _leaksOfAllTypes() => Leaks(<LeakType, List<LeakReport>> {
LeakType.notDisposed: <LeakReport>[LeakReport(code: 1, context: <String, dynamic>{}, type:'myNotDisposedClass', trackedClass: 'myTrackedClass')],
LeakType.notGCed: <LeakReport>[LeakReport(code: 2, context: <String, dynamic>{}, type:'myNotGCedClass', trackedClass: 'myTrackedClass')],
LeakType.gcedLate: <LeakReport>[LeakReport(code: 3, context: <String, dynamic>{}, type:'myGCedLateClass', trackedClass: 'myTrackedClass')],
});
Future<void> main() async {
test('Trivial $LeakCleaner returns only non-disposed leaks.', () {
final LeakCleaner leakCleaner = LeakCleaner(const LeakTrackingTestConfig());
final Leaks leaks = _leaksOfAllTypes();
final int leakTotal = leaks.total;
final Leaks cleanedLeaks = leakCleaner.clean(leaks);
expect(leaks.total, leakTotal);
expect(cleanedLeaks.total, 1);
});
group('Leak tracking works for non-web', () {
testWidgetsWithLeakTracking(
'Leak tracker respects all allow lists',
(WidgetTester tester) async {
await tester.pumpWidget(_StatelessLeakingWidget());
},
leakTrackingConfig: LeakTrackingTestConfig(
notDisposedAllowList: <String>{_leakTrackedClassName},
notGCedAllowList: <String>{_leakTrackedClassName},
),
);
group('Leak tracker respects notGCed allow lists', () {
// These tests cannot run inside other tests because test nesting is forbidden.
// So, `expect` happens outside the tests, in `tearDown`.
late Leaks leaks;
testWidgetsWithLeakTracking(
'when $_StatelessLeakingWidget leaks',
(WidgetTester tester) async {
await tester.pumpWidget(_StatelessLeakingWidget());
},
leakTrackingConfig: LeakTrackingTestConfig(
onLeaks: (Leaks theLeaks) {
leaks = theLeaks;
},
failTestOnLeaks: false,
notGCedAllowList: <String>{_leakTrackedClassName},
),
);
tearDown(() => _verifyLeaks(leaks, expectedNotDisposed: 1));
});
group('Leak tracker catches that', () {
// These tests cannot run inside other tests because test nesting is forbidden.
// So, `expect` happens outside the tests, in `tearDown`.
late Leaks leaks;
testWidgetsWithLeakTracking(
'$_StatelessLeakingWidget leaks',
(WidgetTester tester) async {
await tester.pumpWidget(_StatelessLeakingWidget());
},
leakTrackingConfig: LeakTrackingTestConfig(
onLeaks: (Leaks theLeaks) {
leaks = theLeaks;
},
failTestOnLeaks: false,
),
);
tearDown(() => _verifyLeaks(leaks, expectedNotDisposed: 1));
});
},
skip: isBrowser); // [intended] Leak detection is off for web.
testWidgetsWithLeakTracking('Leak tracking is no-op for web', (WidgetTester tester) async {
await tester.pumpWidget(_StatelessLeakingWidget());
},
skip: !isBrowser); // [intended] Leaks detection is off for web.
}
/// Verifies [leaks] contains expected number of leaks for [_LeakTrackedClass].
void _verifyLeaks(Leaks leaks, { int expectedNotDisposed = 0, int expectedNotGCed = 0 }) {
const String linkToLeakTracker = 'https://github.com/dart-lang/leak_tracker';
expect(
() => expect(leaks, isLeakFree),
throwsA(
predicate((Object? e) {
return e is TestFailure && e.toString().contains(linkToLeakTracker);
}),
),
);
_verifyLeakList(leaks.notDisposed, expectedNotDisposed);
_verifyLeakList(leaks.notGCed, expectedNotGCed);
}
void _verifyLeakList(List<LeakReport> list, int expectedCount){
expect(list.length, expectedCount);
for (final LeakReport leak in list) {
expect(leak.trackedClass, contains(_LeakTrackedClass.library));
expect(leak.trackedClass, contains(_leakTrackedClassName));
}
}
/// Storage to keep disposed objects, to generate not-gced leaks.
final List<_LeakTrackedClass> _notGcedStorage = <_LeakTrackedClass>[];
class _StatelessLeakingWidget extends StatelessWidget {
_StatelessLeakingWidget() {
// ignore: unused_local_variable, the variable is used to create non disposed leak
final _LeakTrackedClass notDisposed = _LeakTrackedClass();
_notGcedStorage.add(_LeakTrackedClass()..dispose());
}
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
class _LeakTrackedClass {
_LeakTrackedClass() {
dispatchObjectCreated(
library: library,
className: '$_LeakTrackedClass',
object: this,
);
}
static const String library = 'package:my_package/lib/src/my_lib.dart';
void dispose() {
dispatchObjectDisposed(object: this);
}
}
...@@ -6,8 +6,10 @@ import 'package:flutter/foundation.dart'; ...@@ -6,8 +6,10 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
void main() { void main() {
testWidgets('debugPrintGestureArenaDiagnostics', (WidgetTester tester) async { testWidgetsWithLeakTracking('debugPrintGestureArenaDiagnostics', (WidgetTester tester) async {
PointerEvent event; PointerEvent event;
debugPrintGestureArenaDiagnostics = true; debugPrintGestureArenaDiagnostics = true;
final DebugPrintCallback oldCallback = debugPrint; final DebugPrintCallback oldCallback = debugPrint;
...@@ -53,7 +55,7 @@ void main() { ...@@ -53,7 +55,7 @@ void main() {
debugPrint = oldCallback; debugPrint = oldCallback;
}); });
testWidgets('debugPrintRecognizerCallbacksTrace', (WidgetTester tester) async { testWidgetsWithLeakTracking('debugPrintRecognizerCallbacksTrace', (WidgetTester tester) async {
PointerEvent event; PointerEvent event;
debugPrintRecognizerCallbacksTrace = true; debugPrintRecognizerCallbacksTrace = true;
final DebugPrintCallback oldCallback = debugPrint; final DebugPrintCallback oldCallback = debugPrint;
...@@ -95,7 +97,7 @@ void main() { ...@@ -95,7 +97,7 @@ void main() {
debugPrint = oldCallback; debugPrint = oldCallback;
}); });
testWidgets('debugPrintGestureArenaDiagnostics and debugPrintRecognizerCallbacksTrace', (WidgetTester tester) async { testWidgetsWithLeakTracking('debugPrintGestureArenaDiagnostics and debugPrintRecognizerCallbacksTrace', (WidgetTester tester) async {
PointerEvent event; PointerEvent event;
debugPrintGestureArenaDiagnostics = true; debugPrintGestureArenaDiagnostics = true;
debugPrintRecognizerCallbacksTrace = true; debugPrintRecognizerCallbacksTrace = true;
......
...@@ -10,6 +10,8 @@ import 'package:flutter/scheduler.dart'; ...@@ -10,6 +10,8 @@ import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
class TestResampleEventFlutterBinding extends AutomatedTestWidgetsFlutterBinding { class TestResampleEventFlutterBinding extends AutomatedTestWidgetsFlutterBinding {
@override @override
SamplingClock? get debugSamplingClock => TestSamplingClock(this.clock); SamplingClock? get debugSamplingClock => TestSamplingClock(this.clock);
...@@ -29,7 +31,7 @@ class TestSamplingClock implements SamplingClock { ...@@ -29,7 +31,7 @@ class TestSamplingClock implements SamplingClock {
void main() { void main() {
final TestWidgetsFlutterBinding binding = TestResampleEventFlutterBinding(); final TestWidgetsFlutterBinding binding = TestResampleEventFlutterBinding();
testWidgets('PointerEvent resampling on a widget', (WidgetTester tester) async { testWidgetsWithLeakTracking('PointerEvent resampling on a widget', (WidgetTester tester) async {
assert(WidgetsBinding.instance == binding); assert(WidgetsBinding.instance == binding);
Duration currentTestFrameTime() => Duration(milliseconds: binding.clock.now().millisecondsSinceEpoch); Duration currentTestFrameTime() => Duration(milliseconds: binding.clock.now().millisecondsSinceEpoch);
void requestFrame() => SchedulerBinding.instance.scheduleFrameCallback((_) {}); void requestFrame() => SchedulerBinding.instance.scheduleFrameCallback((_) {});
...@@ -124,7 +126,7 @@ void main() { ...@@ -124,7 +126,7 @@ void main() {
expect(events[3], isA<PointerUpEvent>()); expect(events[3], isA<PointerUpEvent>());
}); });
testWidgets('Timer should be canceled when resampling stopped', (WidgetTester tester) async { testWidgetsWithLeakTracking('Timer should be canceled when resampling stopped', (WidgetTester tester) async {
// A timer will be started when event's timeStamp is larger than sampleTime. // A timer will be started when event's timeStamp is larger than sampleTime.
final ui.PointerDataPacket packet = ui.PointerDataPacket( final ui.PointerDataPacket packet = ui.PointerDataPacket(
data: <ui.PointerData>[ data: <ui.PointerData>[
......
...@@ -7,6 +7,8 @@ import 'dart:ui' as ui; ...@@ -7,6 +7,8 @@ import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
class TestResult { class TestResult {
bool dragStarted = false; bool dragStarted = false;
bool dragUpdate = false; bool dragUpdate = false;
...@@ -89,7 +91,7 @@ class NestedDraggableCase extends StatelessWidget { ...@@ -89,7 +91,7 @@ class NestedDraggableCase extends StatelessWidget {
} }
void main() { void main() {
testWidgets('Scroll Views get the same ScrollConfiguration as GestureDetectors', (WidgetTester tester) async { testWidgetsWithLeakTracking('Scroll Views get the same ScrollConfiguration as GestureDetectors', (WidgetTester tester) async {
tester.view.gestureSettings = const ui.GestureSettings(physicalTouchSlop: 4); tester.view.gestureSettings = const ui.GestureSettings(physicalTouchSlop: 4);
addTearDown(tester.view.reset); addTearDown(tester.view.reset);
...@@ -112,6 +114,8 @@ void main() { ...@@ -112,6 +114,8 @@ void main() {
expect(result.dragUpdate, true); expect(result.dragUpdate, true);
}); });
// TODO(polina-c): fix ValueNotifier not disposed and switch to testWidgetsWithLeakTracking.
// https://github.com/flutter/devtools/issues/3951
testWidgets('Scroll Views get the same ScrollConfiguration as Draggables', (WidgetTester tester) async { testWidgets('Scroll Views get the same ScrollConfiguration as Draggables', (WidgetTester tester) async {
tester.view.gestureSettings = const ui.GestureSettings(physicalTouchSlop: 4); tester.view.gestureSettings = const ui.GestureSettings(physicalTouchSlop: 4);
addTearDown(tester.view.reset); addTearDown(tester.view.reset);
......
...@@ -6,8 +6,10 @@ import 'package:flutter/gestures.dart'; ...@@ -6,8 +6,10 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
void main() { void main() {
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async { testWidgetsWithLeakTracking('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async {
int doubleTapCount = 0; int doubleTapCount = 0;
final Key redContainer = UniqueKey(); final Key redContainer = UniqueKey();
...@@ -51,7 +53,7 @@ void main() { ...@@ -51,7 +53,7 @@ void main() {
expect(doubleTapCount, 0); expect(doubleTapCount, 0);
}); });
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async { testWidgetsWithLeakTracking('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async {
int doubleTapCount = 0; int doubleTapCount = 0;
final Key redContainer = UniqueKey(); final Key redContainer = UniqueKey();
......
...@@ -6,8 +6,10 @@ import 'package:flutter/gestures.dart'; ...@@ -6,8 +6,10 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
void main() { void main() {
testWidgets('gets local coordinates', (WidgetTester tester) async { testWidgetsWithLeakTracking('gets local coordinates', (WidgetTester tester) async {
int longPressCount = 0; int longPressCount = 0;
int longPressUpCount = 0; int longPressUpCount = 0;
final List<LongPressEndDetails> endDetails = <LongPressEndDetails>[]; final List<LongPressEndDetails> endDetails = <LongPressEndDetails>[];
...@@ -53,7 +55,7 @@ void main() { ...@@ -53,7 +55,7 @@ void main() {
expect(endDetails.single.globalPosition, const Offset(400, 300)); expect(endDetails.single.globalPosition, const Offset(400, 300));
}); });
testWidgets('scaled up', (WidgetTester tester) async { testWidgetsWithLeakTracking('scaled up', (WidgetTester tester) async {
int longPressCount = 0; int longPressCount = 0;
int longPressUpCount = 0; int longPressUpCount = 0;
final List<LongPressEndDetails> endDetails = <LongPressEndDetails>[]; final List<LongPressEndDetails> endDetails = <LongPressEndDetails>[];
...@@ -128,7 +130,7 @@ void main() { ...@@ -128,7 +130,7 @@ void main() {
expect(moveDetails.single.localOffsetFromOrigin, const Offset(0, 100.0 / 2.0)); expect(moveDetails.single.localOffsetFromOrigin, const Offset(0, 100.0 / 2.0));
}); });
testWidgets('scaled down', (WidgetTester tester) async { testWidgetsWithLeakTracking('scaled down', (WidgetTester tester) async {
int longPressCount = 0; int longPressCount = 0;
int longPressUpCount = 0; int longPressUpCount = 0;
final List<LongPressEndDetails> endDetails = <LongPressEndDetails>[]; final List<LongPressEndDetails> endDetails = <LongPressEndDetails>[];
......
...@@ -8,9 +8,11 @@ import 'package:flutter/gestures.dart'; ...@@ -8,9 +8,11 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
void main() { void main() {
group('Horizontal', () { group('Horizontal', () {
testWidgets('gets local coordinates', (WidgetTester tester) async { testWidgetsWithLeakTracking('gets local coordinates', (WidgetTester tester) async {
int dragCancelCount = 0; int dragCancelCount = 0;
final List<DragDownDetails> downDetails = <DragDownDetails>[]; final List<DragDownDetails> downDetails = <DragDownDetails>[];
final List<DragEndDetails> endDetails = <DragEndDetails>[]; final List<DragEndDetails> endDetails = <DragEndDetails>[];
...@@ -65,7 +67,7 @@ void main() { ...@@ -65,7 +67,7 @@ void main() {
); );
}); });
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async { testWidgetsWithLeakTracking('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async {
int dragCancelCount = 0; int dragCancelCount = 0;
final List<DragDownDetails> downDetails = <DragDownDetails>[]; final List<DragDownDetails> downDetails = <DragDownDetails>[];
final List<DragEndDetails> endDetails = <DragEndDetails>[]; final List<DragEndDetails> endDetails = <DragEndDetails>[];
...@@ -163,7 +165,7 @@ void main() { ...@@ -163,7 +165,7 @@ void main() {
updateDetails.clear(); updateDetails.clear();
}); });
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async { testWidgetsWithLeakTracking('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async {
int dragCancelCount = 0; int dragCancelCount = 0;
final List<DragDownDetails> downDetails = <DragDownDetails>[]; final List<DragDownDetails> downDetails = <DragDownDetails>[];
final List<DragEndDetails> endDetails = <DragEndDetails>[]; final List<DragEndDetails> endDetails = <DragEndDetails>[];
...@@ -261,7 +263,7 @@ void main() { ...@@ -261,7 +263,7 @@ void main() {
updateDetails.clear(); updateDetails.clear();
}); });
testWidgets('kTouchSlop is evaluated in the global coordinate space when rotated 45 degrees', (WidgetTester tester) async { testWidgetsWithLeakTracking('kTouchSlop is evaluated in the global coordinate space when rotated 45 degrees', (WidgetTester tester) async {
int dragCancelCount = 0; int dragCancelCount = 0;
final List<DragDownDetails> downDetails = <DragDownDetails>[]; final List<DragDownDetails> downDetails = <DragDownDetails>[];
final List<DragEndDetails> endDetails = <DragEndDetails>[]; final List<DragEndDetails> endDetails = <DragEndDetails>[];
...@@ -338,7 +340,7 @@ void main() { ...@@ -338,7 +340,7 @@ void main() {
}); });
group('Vertical', () { group('Vertical', () {
testWidgets('gets local coordinates', (WidgetTester tester) async { testWidgetsWithLeakTracking('gets local coordinates', (WidgetTester tester) async {
int dragCancelCount = 0; int dragCancelCount = 0;
final List<DragDownDetails> downDetails = <DragDownDetails>[]; final List<DragDownDetails> downDetails = <DragDownDetails>[];
final List<DragEndDetails> endDetails = <DragEndDetails>[]; final List<DragEndDetails> endDetails = <DragEndDetails>[];
...@@ -393,7 +395,7 @@ void main() { ...@@ -393,7 +395,7 @@ void main() {
); );
}); });
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async { testWidgetsWithLeakTracking('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async {
int dragCancelCount = 0; int dragCancelCount = 0;
final List<DragDownDetails> downDetails = <DragDownDetails>[]; final List<DragDownDetails> downDetails = <DragDownDetails>[];
final List<DragEndDetails> endDetails = <DragEndDetails>[]; final List<DragEndDetails> endDetails = <DragEndDetails>[];
...@@ -491,7 +493,7 @@ void main() { ...@@ -491,7 +493,7 @@ void main() {
updateDetails.clear(); updateDetails.clear();
}); });
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async { testWidgetsWithLeakTracking('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async {
int dragCancelCount = 0; int dragCancelCount = 0;
final List<DragDownDetails> downDetails = <DragDownDetails>[]; final List<DragDownDetails> downDetails = <DragDownDetails>[];
final List<DragEndDetails> endDetails = <DragEndDetails>[]; final List<DragEndDetails> endDetails = <DragEndDetails>[];
...@@ -589,7 +591,7 @@ void main() { ...@@ -589,7 +591,7 @@ void main() {
updateDetails.clear(); updateDetails.clear();
}); });
testWidgets('kTouchSlop is evaluated in the global coordinate space when rotated 45 degrees', (WidgetTester tester) async { testWidgetsWithLeakTracking('kTouchSlop is evaluated in the global coordinate space when rotated 45 degrees', (WidgetTester tester) async {
int dragCancelCount = 0; int dragCancelCount = 0;
final List<DragDownDetails> downDetails = <DragDownDetails>[]; final List<DragDownDetails> downDetails = <DragDownDetails>[];
final List<DragEndDetails> endDetails = <DragEndDetails>[]; final List<DragEndDetails> endDetails = <DragEndDetails>[];
......
...@@ -5,8 +5,10 @@ ...@@ -5,8 +5,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
void main() { void main() {
testWidgets('gets local coordinates', (WidgetTester tester) async { testWidgetsWithLeakTracking('gets local coordinates', (WidgetTester tester) async {
final List<ScaleStartDetails> startDetails = <ScaleStartDetails>[]; final List<ScaleStartDetails> startDetails = <ScaleStartDetails>[];
final List<ScaleUpdateDetails> updateDetails = <ScaleUpdateDetails>[]; final List<ScaleUpdateDetails> updateDetails = <ScaleUpdateDetails>[];
......
...@@ -6,8 +6,10 @@ import 'package:flutter/gestures.dart'; ...@@ -6,8 +6,10 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
void main() { void main() {
testWidgets('gets local coordinates', (WidgetTester tester) async { testWidgetsWithLeakTracking('gets local coordinates', (WidgetTester tester) async {
int tapCount = 0; int tapCount = 0;
int tapCancelCount = 0; int tapCancelCount = 0;
final List<TapDownDetails> downDetails = <TapDownDetails>[]; final List<TapDownDetails> downDetails = <TapDownDetails>[];
...@@ -48,7 +50,7 @@ void main() { ...@@ -48,7 +50,7 @@ void main() {
expect(upDetails.single.globalPosition, const Offset(400, 300)); expect(upDetails.single.globalPosition, const Offset(400, 300));
}); });
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async { testWidgetsWithLeakTracking('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async {
int tapCount = 0; int tapCount = 0;
int tapCancelCount = 0; int tapCancelCount = 0;
final List<TapDownDetails> downDetails = <TapDownDetails>[]; final List<TapDownDetails> downDetails = <TapDownDetails>[];
...@@ -111,7 +113,7 @@ void main() { ...@@ -111,7 +113,7 @@ void main() {
expect(upDetails, isEmpty); expect(upDetails, isEmpty);
}); });
testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async { testWidgetsWithLeakTracking('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async {
int tapCount = 0; int tapCount = 0;
int tapCancelCount = 0; int tapCancelCount = 0;
final List<TapDownDetails> downDetails = <TapDownDetails>[]; final List<TapDownDetails> downDetails = <TapDownDetails>[];
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
/// Adds the basic requirements for a Chip. /// Adds the basic requirements for a Chip.
Widget wrapForChip({ Widget wrapForChip({
required Widget child, required Widget child,
...@@ -34,7 +36,7 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { ...@@ -34,7 +36,7 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) {
} }
void main() { void main() {
testWidgets('ActionChip can be tapped', (WidgetTester tester) async { testWidgetsWithLeakTracking('ActionChip can be tapped', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Material( home: Material(
...@@ -50,7 +52,7 @@ void main() { ...@@ -50,7 +52,7 @@ void main() {
expect(tester.takeException(), null); expect(tester.takeException(), null);
}); });
testWidgets('ActionChip clipBehavior properly passes through to the Material', (WidgetTester tester) async { testWidgetsWithLeakTracking('ActionChip clipBehavior properly passes through to the Material', (WidgetTester tester) async {
const Text label = Text('label'); const Text label = Text('label');
await tester.pumpWidget(wrapForChip(child: ActionChip(label: label, onPressed: () { }))); await tester.pumpWidget(wrapForChip(child: ActionChip(label: label, onPressed: () { })));
checkChipMaterialClipBehavior(tester, Clip.none); checkChipMaterialClipBehavior(tester, Clip.none);
......
...@@ -6,6 +6,8 @@ import 'package:flutter/material.dart'; ...@@ -6,6 +6,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
void main() { void main() {
test('ActionIconThemeData copyWith, ==, hashCode basics', () { test('ActionIconThemeData copyWith, ==, hashCode basics', () {
expect(const ActionIconThemeData(), const ActionIconThemeData().copyWith()); expect(const ActionIconThemeData(), const ActionIconThemeData().copyWith());
...@@ -21,7 +23,7 @@ void main() { ...@@ -21,7 +23,7 @@ void main() {
expect(themeData.endDrawerButtonIconBuilder, null); expect(themeData.endDrawerButtonIconBuilder, null);
}); });
testWidgets('Default ActionIconThemeData debugFillProperties', testWidgetsWithLeakTracking('Default ActionIconThemeData debugFillProperties',
(WidgetTester tester) async { (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const ActionIconThemeData().debugFillProperties(builder); const ActionIconThemeData().debugFillProperties(builder);
...@@ -34,7 +36,7 @@ void main() { ...@@ -34,7 +36,7 @@ void main() {
expect(description, <String>[]); expect(description, <String>[]);
}); });
testWidgets('ActionIconThemeData implements debugFillProperties', testWidgetsWithLeakTracking('ActionIconThemeData implements debugFillProperties',
(WidgetTester tester) async { (WidgetTester tester) async {
Widget actionButtonIconBuilder(BuildContext context) { Widget actionButtonIconBuilder(BuildContext context) {
return const Icon(IconData(0)); return const Icon(IconData(0));
...@@ -62,7 +64,7 @@ void main() { ...@@ -62,7 +64,7 @@ void main() {
]); ]);
}); });
testWidgets('Action buttons use ThemeData action icon theme', (WidgetTester tester) async { testWidgetsWithLeakTracking('Action buttons use ThemeData action icon theme', (WidgetTester tester) async {
const Color green = Color(0xff00ff00); const Color green = Color(0xff00ff00);
const IconData icon = IconData(0); const IconData icon = IconData(0);
...@@ -123,7 +125,7 @@ void main() { ...@@ -123,7 +125,7 @@ void main() {
// This test is essentially the same as 'Action buttons use ThemeData action icon theme'. In // This test is essentially the same as 'Action buttons use ThemeData action icon theme'. In
// this case the theme is introduced with the ActionIconTheme widget instead of // this case the theme is introduced with the ActionIconTheme widget instead of
// ThemeData.actionIconTheme. // ThemeData.actionIconTheme.
testWidgets('Action buttons use ActionIconTheme', (WidgetTester tester) async { testWidgetsWithLeakTracking('Action buttons use ActionIconTheme', (WidgetTester tester) async {
const Color green = Color(0xff00ff00); const Color green = Color(0xff00ff00);
const IconData icon = IconData(0); const IconData icon = IconData(0);
......
...@@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; ...@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
import '../widgets/clipboard_utils.dart'; import '../widgets/clipboard_utils.dart';
import '../widgets/editable_text_utils.dart'; import '../widgets/editable_text_utils.dart';
...@@ -25,7 +26,7 @@ void main() { ...@@ -25,7 +26,7 @@ void main() {
await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); await Clipboard.setData(const ClipboardData(text: 'Clipboard data'));
}); });
testWidgets('Builds the right toolbar on each platform, including web, and shows buttonItems', (WidgetTester tester) async { testWidgetsWithLeakTracking('Builds the right toolbar on each platform, including web, and shows buttonItems', (WidgetTester tester) async {
const String buttonText = 'Click me'; const String buttonText = 'Click me';
await tester.pumpWidget( await tester.pumpWidget(
...@@ -80,7 +81,7 @@ void main() { ...@@ -80,7 +81,7 @@ void main() {
skip: isBrowser, // [intended] see https://github.com/flutter/flutter/issues/108382 skip: isBrowser, // [intended] see https://github.com/flutter/flutter/issues/108382
); );
testWidgets('Can build children directly as well', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can build children directly as well', (WidgetTester tester) async {
final GlobalKey key = GlobalKey(); final GlobalKey key = GlobalKey();
await tester.pumpWidget( await tester.pumpWidget(
...@@ -103,7 +104,7 @@ void main() { ...@@ -103,7 +104,7 @@ void main() {
expect(find.byKey(key), findsOneWidget); expect(find.byKey(key), findsOneWidget);
}); });
testWidgets('Can build from EditableTextState', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can build from EditableTextState', (WidgetTester tester) async {
final GlobalKey key = GlobalKey(); final GlobalKey key = GlobalKey();
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -167,7 +168,7 @@ void main() { ...@@ -167,7 +168,7 @@ void main() {
variant: TargetPlatformVariant.all(), variant: TargetPlatformVariant.all(),
); );
testWidgets('Can build for editable text from raw parameters', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can build for editable text from raw parameters', (WidgetTester tester) async {
final GlobalKey key = GlobalKey(); final GlobalKey key = GlobalKey();
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -216,7 +217,7 @@ void main() { ...@@ -216,7 +217,7 @@ void main() {
); );
group('buttonItems', () { group('buttonItems', () {
testWidgets('getEditableTextButtonItems builds the correct button items per-platform', (WidgetTester tester) async { testWidgetsWithLeakTracking('getEditableTextButtonItems builds the correct button items per-platform', (WidgetTester tester) async {
// Fill the clipboard so that the Paste option is available in the text // Fill the clipboard so that the Paste option is available in the text
// selection menu. // selection menu.
await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); await Clipboard.setData(const ClipboardData(text: 'Clipboard data'));
...@@ -311,7 +312,7 @@ void main() { ...@@ -311,7 +312,7 @@ void main() {
skip: kIsWeb, // [intended] skip: kIsWeb, // [intended]
); );
testWidgets('getAdaptiveButtons builds the correct button widgets per-platform', (WidgetTester tester) async { testWidgetsWithLeakTracking('getAdaptiveButtons builds the correct button widgets per-platform', (WidgetTester tester) async {
const String buttonText = 'Click me'; const String buttonText = 'Click me';
await tester.pumpWidget( await tester.pumpWidget(
......
...@@ -11,6 +11,8 @@ import 'package:flutter/material.dart'; ...@@ -11,6 +11,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
// TODO(polina-c): fix Image not disposed and switch to testWidgetsWithLeakTracking.
// https://github.com/flutter/devtools/issues/3951
testWidgets('Flutter Logo golden test', (WidgetTester tester) async { testWidgets('Flutter Logo golden test', (WidgetTester tester) async {
final Key logo = UniqueKey(); final Key logo = UniqueKey();
await tester.pumpWidget(FlutterLogo(key: logo)); await tester.pumpWidget(FlutterLogo(key: logo));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment