Example #1
0
        public async Task When_Add_Remove(object controlTypeRaw, int count)
        {
#if TRACK_REFS
            var initialInactiveStats = Uno.UI.DataBinding.BinderReferenceHolder.GetInactiveViewReferencesStats();
            var initialActiveStats   = Uno.UI.DataBinding.BinderReferenceHolder.GetReferenceStats();
#endif

            Type GetType(string s)
            => AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType(s)).Where(t => t != null).First() !;

            var controlType = controlTypeRaw switch
            {
                Type ct => ct,
                string s => GetType(s),
                _ => throw new InvalidOperationException()
            };

            var _holders = new ConditionalWeakTable <DependencyObject, Holder>();
            void TrackDependencyObject(DependencyObject target) => _holders.Add(target, new Holder(HolderUpdate));

            var maxCounter        = 0;
            var activeControls    = 0;
            var maxActiveControls = 0;

            var rootContainer = new ContentControl();

            TestServices.WindowHelper.WindowContent = rootContainer;

            await TestServices.WindowHelper.WaitForIdle();

            for (int i = 0; i < count; i++)
            {
                await MaterializeControl(controlType, _holders, maxCounter, rootContainer);
            }

            TestServices.WindowHelper.WindowContent = null;

            void HolderUpdate(int value)
            {
                _ = rootContainer.Dispatcher.RunAsync(CoreDispatcherPriority.High,
                                                      () =>
                {
                    maxCounter        = Math.Max(value, maxCounter);
                    activeControls    = value;
                    maxActiveControls = maxCounter;
                }
                                                      );
            }

            var sw                 = Stopwatch.StartNew();
            var endTime            = TimeSpan.FromSeconds(30);
            var maxTime            = TimeSpan.FromMinutes(1);
            var lastActiveControls = activeControls;

            while (sw.Elapsed < endTime && sw.Elapsed < maxTime && activeControls != 0)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();

                // Waiting for idle is required for collection of
                // DispatcherConditionalDisposable to be executed
                await TestServices.WindowHelper.WaitForIdle();

                if (lastActiveControls != activeControls)
                {
                    // Expand the timeout if the count has changed, as the
                    // GC may still be processing levels of the hierarcy on iOS
                    endTime += TimeSpan.FromMilliseconds(500);
                }

                lastActiveControls = activeControls;
            }

#if TRACK_REFS
            Uno.UI.DataBinding.BinderReferenceHolder.LogInactiveViewReferencesStatsDiff(initialInactiveStats);
            Uno.UI.DataBinding.BinderReferenceHolder.LogActiveViewReferencesStatsDiff(initialActiveStats);
#endif

            var retainedMessage = "";

#if NET5_0 || __IOS__ || __ANDROID__
            if (activeControls != 0)
            {
                var retainedTypes = _holders.AsEnumerable().Select(ExtractTargetName).JoinBy(";");
                Console.WriteLine($"Retained types: {retainedTypes}");

                retainedMessage = $"Retained types: {retainedTypes}";
            }
#endif

#if __IOS__
            // On iOS, the collection of objects does not seem to be reliable enough
            // to always go to zero during runtime tests. If the count of active objects
            // is arbitrarily below the half of the number of top-level objects.
            // created, we can assume that enough objects were collected entirely.
            Assert.IsTrue(activeControls < count, retainedMessage);
#else
            Assert.AreEqual(0, activeControls, retainedMessage);