From 87aa8423a2b144dc9021258f0a46daaa9b0fadff Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Tue, 21 May 2024 14:33:20 -0700 Subject: [PATCH] Fix test that leaks images. (#148494) Contributes to https://github.com/flutter/flutter/issues/145599 Repro: `flutter test test/cupertino/tab_scaffold_test.dart --dart-define LEAK_TRACKING=true --plain-name "Adding new tabs does not crash the app"` What is going on: 1. ImageCache.putIfAbsent, in case the image already existed in the cache, invokes ImageCache._trackLiveImage, that creates _LiveImage and passes the image's completer as parameter `completer` 3. _LiveImage constructor invokes super constructor (of _CachedImageBase) that initializes the member `handle` 4. `handle` is disposed [using scheduler](https://github.com/polina-c/flutter/blob/c698e694c871278fd00dc52394a91323494b6395/packages/flutter/lib/src/painting/image_cache.dart#L633), and disposal does not happen in time of test completion. Adding delay to the test increases number of not disposed objects from 30 to 120. Should we force schedule at the end of the widget tests somehow to make scheduler switched to right state? Creation call stack: ``` #9______new_ImageStreamCompleterHandle.__(package:flutter/src/painting/image_stream.dart:465:41) #10_____ImageStreamCompleter.keepAlive_(package:flutter/src/painting/image_stream.dart:655:39) #11_____new__CachedImageBase_(package:flutter/src/painting/image_cache.dart:609:27) #12_____new__LiveImage_(package:flutter/src/painting/image_cache.dart:647:9) #13_____ImageCache._trackLiveImage._(package:flutter/src/painting/image_cache.dart:302:14) #14______LinkedHashMapMixin.putIfAbsent_(dart:collection-patch/compact_hash.dart:543:23) #15_____ImageCache._trackLiveImage_(package:flutter/src/painting/image_cache.dart:296:17) #16_____ImageCache.putIfAbsent_(package:flutter/src/painting/image_cache.dart:378:7) #17_____ImageProvider.resolveStreamForKey_(package:flutter/src/painting/image_provider.dart:517:81) #18_____ScrollAwareImageProvider.resolveStreamForKey_(package:flutter/src/widgets/scroll_aware_image_provider.dart:104:19) #19_____ImageProvider.resolve._(package:flutter/src/painting/image_provider.dart:366:9) #20_____ImageProvider._createErrorHandlerAndKey._(package:flutter/src/painting/image_provider.dart:479:24) #21_____SynchronousFuture.then_(package:flutter/src/foundation/synchronous_future.dart:43:39) #22_____ImageProvider._createErrorHandlerAndKey_(package:flutter/src/painting/image_provider.dart:476:9) #23_____ImageProvider.resolve_(package:flutter/src/painting/image_provider.dart:363:5) #24______ImageState._resolveImage_(package:flutter/src/widgets/image.dart:1111:16) #25______ImageState.didChangeDependencies_(package:flutter/src/widgets/image.dart:1061:5) #26_____StatefulElement._firstBuild_(package:flutter/src/widgets/framework.dart:5630:11) #27_____ComponentElement.mount_(package:flutter/src/widgets/framework.dart:5457:5) #28_____Element.inflateWidget_(package:flutter/src/widgets/framework.dart:4334:16) #29_____Element.updateChild_(package:flutter/src/widgets/framework.dart:3843:18) #30_____SingleChildRenderObjectElement.mount_(package:flutter/src/widgets/framework.dart:6763:14) ``` --- packages/flutter/test/cupertino/tab_scaffold_test.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/flutter/test/cupertino/tab_scaffold_test.dart b/packages/flutter/test/cupertino/tab_scaffold_test.dart index df81155716f..2d3e323be22 100644 --- a/packages/flutter/test/cupertino/tab_scaffold_test.dart +++ b/packages/flutter/test/cupertino/tab_scaffold_test.dart @@ -47,6 +47,10 @@ void main() { selectedTabs = []; }); + tearDown(() { + imageCache.clear(); + }); + BottomNavigationBarItem tabGenerator(int index) { return BottomNavigationBarItem( icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))), @@ -632,7 +636,9 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/33455 - testWidgets('Adding new tabs does not crash the app', (WidgetTester tester) async { + testWidgets('Adding new tabs does not crash the app', + experimentalLeakTesting: LeakTesting.settings.withTrackedAll(), + (WidgetTester tester) async { final List tabsPainted = []; final CupertinoTabController controller = CupertinoTabController(); addTearDown(controller.dispose);