From 10bc0abaf7f5278979ab4c24d4bc596f2cc78c8d Mon Sep 17 00:00:00 2001 From: Rosario Pulella Date: Tue, 16 Mar 2021 16:07:57 -0400 Subject: [PATCH 1/9] Only Increment CurrentPageIndex if Source.GetPagedItemsAsync's task completes with RanToCompletion --- .../IncrementalLoadingCollection.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs b/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs index 44871dde607..746c2c552d4 100644 --- a/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs +++ b/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs @@ -226,7 +226,18 @@ public Task RefreshAsync() /// protected virtual async Task> LoadDataAsync(CancellationToken cancellationToken) { - var result = await Source.GetPagedItemsAsync(CurrentPageIndex++, ItemsPerPage, cancellationToken); + var result = await Source.GetPagedItemsAsync(CurrentPageIndex, ItemsPerPage, cancellationToken) + .ContinueWith( + t => + { + if (t.Status == TaskStatus.RanToCompletion) + { + CurrentPageIndex += 1; + } + + return t.Result; + }, cancellationToken); + return result; } From 3d6b98f00e122c77be18fd0c2aa448c19d94e29c Mon Sep 17 00:00:00 2001 From: Rosario Pulella Date: Tue, 4 May 2021 15:08:05 -0700 Subject: [PATCH 2/9] Add syncronus and asyncronus tests for the incremtnetal loading collection --- .../UnitTests.UWP/UI/Collection/DataSource.cs | 43 +++++++++++++++ .../Test_IncrementalLoadingCollection.cs | 54 +++++++++++++++++++ UnitTests/UnitTests.UWP/UnitTests.UWP.csproj | 4 +- 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 UnitTests/UnitTests.UWP/UI/Collection/DataSource.cs create mode 100644 UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs diff --git a/UnitTests/UnitTests.UWP/UI/Collection/DataSource.cs b/UnitTests/UnitTests.UWP/UI/Collection/DataSource.cs new file mode 100644 index 00000000000..23b7a342d02 --- /dev/null +++ b/UnitTests/UnitTests.UWP/UI/Collection/DataSource.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace UnitTests.UI +{ + public class DataSource : IIncrementalSource + { + private readonly IEnumerable _data; + + public DataSource(IEnumerable items) + { + _data = items; + } + + public async Task> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken)) + { + // Gets items from the collection according to pageIndex and pageSize parameters. + var result = (from p in _data + select p).Skip(pageIndex * pageSize).Take(pageSize); + + // Simulates a longer request... + // Make sure the list is still in order after a refresh, + // even if the first page takes longer to load + if (pageIndex == 0) + { + await Task.Delay(2500); + } + else + { + await Task.Delay(1000); + } + + return result; + } + } +} diff --git a/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs b/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs new file mode 100644 index 00000000000..aea6bdcf47f --- /dev/null +++ b/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.UI +{ + [TestClass] + public class Test_IncrementalLoadingCollection + { + [TestMethod] + public async Task SequentialRequests() + { + const int pageSize = 20; + const int pages = 5; + var collection = new IncrementalLoadingCollection, int>(new DataSource(Enumerable.Range(0, pageSize * pages)), pageSize); + + for (int pageNum = 1; pageNum <= pages; pageNum++) + { + var rez1 = await collection.LoadMoreItemsAsync(0); + Assert.AreEqual((uint)pageSize, rez1.Count); + CollectionAssert.AreEquivalent(Enumerable.Range(0, pageSize * pageNum).ToArray(), collection); + } + } + + [TestMethod] + public void ConcurentRequests() + { + const int pageSize = 20; + const int pages = 5; + var collection = new IncrementalLoadingCollection, int>(new DataSource(Enumerable.Range(0, pageSize * pages)), pageSize); + + var requests = new List(); + + for (int pageNum = 1; pageNum <= pages; pageNum++) + { + requests.Add(collection.LoadMoreItemsAsync(0).AsTask().ContinueWith(t => + { + Assert.AreEqual(TaskStatus.RanToCompletion, t.Status); + Assert.AreEqual((uint)pageSize, t.Result.Count); + })); + } + + Task.WaitAll(requests.ToArray()); + CollectionAssert.AreEquivalent(Enumerable.Range(0, pageSize * pages).ToArray(), collection); + } + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj index b9631e7cff8..59f18e8b106 100644 --- a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj +++ b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj @@ -197,6 +197,8 @@ + + @@ -548,4 +550,4 @@ --> - + \ No newline at end of file From 81a80098c11c76ebb49f16e1dd44c5a12d0d7610 Mon Sep 17 00:00:00 2001 From: Rosario Pulella Date: Tue, 4 May 2021 15:08:44 -0700 Subject: [PATCH 3/9] Fix issue with mumtiple requests comming to the incremental loading collection --- .../IncrementalLoadingCollection.cs | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs b/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs index 746c2c552d4..6086d331f68 100644 --- a/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs +++ b/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs @@ -30,6 +30,8 @@ public class IncrementalLoadingCollection : ObservableCollection ISupportIncrementalLoading where TSource : Collections.IIncrementalSource { + private static readonly SemaphoreSlim _mutex = new SemaphoreSlim(1); + /// /// Gets or sets an that is called when a retrieval operation begins. /// @@ -226,19 +228,29 @@ public Task RefreshAsync() /// protected virtual async Task> LoadDataAsync(CancellationToken cancellationToken) { - var result = await Source.GetPagedItemsAsync(CurrentPageIndex, ItemsPerPage, cancellationToken) - .ContinueWith( - t => - { - if (t.Status == TaskStatus.RanToCompletion) + // TODO (2021.05.05): Make use common AsyncMutex class. + // AsyncMutex is located at Microsoft.Toolkit.Uwp.UI.Media/Extensions/System.Threading.Tasks/AsyncMutex.cs at the time of this note. + await _mutex.WaitAsync(); + try + { + var result = await Source.GetPagedItemsAsync(CurrentPageIndex, ItemsPerPage, cancellationToken) + .ContinueWith( + t => { - CurrentPageIndex += 1; - } + if (t.Status == TaskStatus.RanToCompletion) + { + CurrentPageIndex += 1; + } - return t.Result; - }, cancellationToken); + return t.Result; + }, cancellationToken); - return result; + return result; + } + finally + { + _mutex.Release(); + } } private async Task LoadMoreItemsAsync(uint count, CancellationToken cancellationToken) From cb00c1db3d9b6377f7e319079a60f80902aa5e5a Mon Sep 17 00:00:00 2001 From: Rosario Pulella Date: Wed, 5 May 2021 17:49:55 -0400 Subject: [PATCH 4/9] Move async lock to handle entire load and update opertation. Added and cleaned up tests. --- .../IncrementalLoadingCollection.cs | 36 +++--- .../UnitTests.UWP/UI/Collection/DataSource.cs | 52 +++++--- .../Test_IncrementalLoadingCollection.cs | 117 ++++++++++++++++-- 3 files changed, 159 insertions(+), 46 deletions(-) diff --git a/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs b/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs index 6086d331f68..9bfae4df227 100644 --- a/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs +++ b/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs @@ -228,36 +228,28 @@ public Task RefreshAsync() /// protected virtual async Task> LoadDataAsync(CancellationToken cancellationToken) { - // TODO (2021.05.05): Make use common AsyncMutex class. - // AsyncMutex is located at Microsoft.Toolkit.Uwp.UI.Media/Extensions/System.Threading.Tasks/AsyncMutex.cs at the time of this note. - await _mutex.WaitAsync(); - try - { - var result = await Source.GetPagedItemsAsync(CurrentPageIndex, ItemsPerPage, cancellationToken) - .ContinueWith( - t => + var result = await Source.GetPagedItemsAsync(CurrentPageIndex, ItemsPerPage, cancellationToken) + .ContinueWith( + t => + { + if (t.Status == TaskStatus.RanToCompletion) { - if (t.Status == TaskStatus.RanToCompletion) - { - CurrentPageIndex += 1; - } + CurrentPageIndex += 1; + } - return t.Result; - }, cancellationToken); + return t.Result; + }, cancellationToken); - return result; - } - finally - { - _mutex.Release(); - } + return result; } private async Task LoadMoreItemsAsync(uint count, CancellationToken cancellationToken) { uint resultCount = 0; _cancellationToken = cancellationToken; - + // TODO (2021.05.05): Make use common AsyncMutex class. + // AsyncMutex is located at Microsoft.Toolkit.Uwp.UI.Media/Extensions/System.Threading.Tasks/AsyncMutex.cs at the time of this note. + await _mutex.WaitAsync(); try { if (!_cancellationToken.IsCancellationRequested) @@ -301,6 +293,8 @@ private async Task LoadMoreItemsAsync(uint count, Cancellat _refreshOnLoad = false; await RefreshAsync(); } + + _mutex.Release(); } return new LoadMoreItemsResult { Count = resultCount }; diff --git a/UnitTests/UnitTests.UWP/UI/Collection/DataSource.cs b/UnitTests/UnitTests.UWP/UI/Collection/DataSource.cs index 23b7a342d02..b6c76ed83d4 100644 --- a/UnitTests/UnitTests.UWP/UI/Collection/DataSource.cs +++ b/UnitTests/UnitTests.UWP/UI/Collection/DataSource.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.Toolkit.Collections; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -12,32 +13,47 @@ namespace UnitTests.UI { public class DataSource : IIncrementalSource { - private readonly IEnumerable _data; + private readonly IEnumerable items; + private readonly Queue pageRequestOperations; - public DataSource(IEnumerable items) + public delegate IEnumerable PageOperation(IEnumerable page); + + public DataSource(IEnumerable items, IEnumerable pageOps) + : this(items, new Queue(pageOps)) { - _data = items; } - public async Task> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken)) + public DataSource(IEnumerable items, params PageOperation[] pageOps) + : this(items, new Queue(pageOps)) { - // Gets items from the collection according to pageIndex and pageSize parameters. - var result = (from p in _data - select p).Skip(pageIndex * pageSize).Take(pageSize); + } - // Simulates a longer request... - // Make sure the list is still in order after a refresh, - // even if the first page takes longer to load - if (pageIndex == 0) - { - await Task.Delay(2500); - } - else + public DataSource(IEnumerable items, Queue pageOps = default) + { + this.items = items ?? throw new ArgumentNullException(nameof(items)); + this.pageRequestOperations = pageOps ?? new Queue(); + } + + public static PageOperation MakeDelayOp(int delay) + => new (page => { - await Task.Delay(1000); - } + Thread.Sleep(delay); + return page; + }); + + public static IEnumerable ThrowException(IEnumerable page) => throw new Exception(); + + public static IEnumerable PassThrough(IEnumerable page) => page; + + public async Task> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default) + { + // Gets items from the collection according to pageIndex and pageSize parameters. + var result = (from p in items + select p).Skip(pageIndex * pageSize).Take(pageSize); - return result; + return this.pageRequestOperations.TryDequeue(out var op) + ? await Task.Factory.StartNew(new Func>(o => op(o as IEnumerable)), state: result) + : result; } } } diff --git a/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs b/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs index aea6bdcf47f..ac3a6286967 100644 --- a/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs +++ b/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Toolkit.Uwp; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -14,27 +15,45 @@ namespace UnitTests.UI [TestClass] public class Test_IncrementalLoadingCollection { + private static readonly DataSource.PageOperation[] FailPassSequence + = new DataSource.PageOperation[] + { + DataSource.ThrowException, DataSource.PassThrough, + DataSource.ThrowException, DataSource.PassThrough, + DataSource.ThrowException, DataSource.PassThrough, + DataSource.ThrowException, DataSource.PassThrough, + DataSource.ThrowException, DataSource.PassThrough, + }; + + [DataRow(2500, 1000, 1000, 1000, 1000)] + [DataRow] [TestMethod] - public async Task SequentialRequests() + public async Task Requests(params int[] pageDelays) { const int pageSize = 20; const int pages = 5; - var collection = new IncrementalLoadingCollection, int>(new DataSource(Enumerable.Range(0, pageSize * pages)), pageSize); + + var source = new DataSource(Enumerable.Range(0, pageSize * pages), pageDelays.Select(DataSource.MakeDelayOp)); + var collection = new IncrementalLoadingCollection, int>(source, pageSize); for (int pageNum = 1; pageNum <= pages; pageNum++) { var rez1 = await collection.LoadMoreItemsAsync(0); Assert.AreEqual((uint)pageSize, rez1.Count); - CollectionAssert.AreEquivalent(Enumerable.Range(0, pageSize * pageNum).ToArray(), collection); + CollectionAssert.AreEqual(Enumerable.Range(0, pageSize * pageNum).ToArray(), collection); } } + [DataRow(2500, 1000, 1000, 1000, 1000)] + [DataRow] [TestMethod] - public void ConcurentRequests() + public async Task RequestsAsync(params int[] pageDelays) { - const int pageSize = 20; + const int pageSize = 20; const int pages = 5; - var collection = new IncrementalLoadingCollection, int>(new DataSource(Enumerable.Range(0, pageSize * pages)), pageSize); + + var source = new DataSource(Enumerable.Range(0, pageSize * pages), pageDelays.Select(DataSource.MakeDelayOp)); + var collection = new IncrementalLoadingCollection, int>(source, pageSize); var requests = new List(); @@ -47,8 +66,92 @@ public void ConcurentRequests() })); } - Task.WaitAll(requests.ToArray()); + await Task.WhenAll(requests); + + CollectionAssert.AreEqual(Enumerable.Range(0, pageSize * pages).ToArray(), collection); + } + + [TestMethod] + public async Task FirstRequestFails() + { + const int pageSize = 20; + const int pages = 5; + + var source = new DataSource(Enumerable.Range(0, pageSize * pages), DataSource.ThrowException); + var collection = new IncrementalLoadingCollection, int>(source, pageSize); + + await Assert.ThrowsExceptionAsync(async () => await collection.LoadMoreItemsAsync(0)); + + Assert.IsTrue(!collection.Any()); + + var requests = new List(); + + for (int pageNum = 1; pageNum <= pages; pageNum++) + { + requests.Add(collection.LoadMoreItemsAsync(0).AsTask()); + } + + await Task.WhenAll(requests); + + CollectionAssert.AreEqual(Enumerable.Range(0, pageSize * pages).ToArray(), collection); + } + + [TestMethod] + public async Task EveryOtherRequestFails() + { + const int pageSize = 20; + const int pages = 5; + + var source = new DataSource(Enumerable.Range(0, pageSize * pages), FailPassSequence); + var collection = new IncrementalLoadingCollection, int>(source, pageSize); + + var willFail = true; + for (int submitedRequests = 0; submitedRequests < 10; submitedRequests++) + { + if (willFail) + { + await collection.LoadMoreItemsAsync(0).AsTask().ContinueWith(t => Assert.AreEqual(TaskStatus.Faulted, t.Status)); + } + else + { + await collection.LoadMoreItemsAsync(0).AsTask().ContinueWith(t => Assert.AreEqual(TaskStatus.RanToCompletion, t.Status)); + } + + willFail = !willFail; + } + CollectionAssert.AreEquivalent(Enumerable.Range(0, pageSize * pages).ToArray(), collection); } + + [TestMethod] + public async Task EveryOtherRequestFailsAsync() + { + const int pageSize = 20; + const int pages = 5; + + var source = new DataSource(Enumerable.Range(0, pageSize * pages), FailPassSequence); + var collection = new IncrementalLoadingCollection, int>(source, pageSize); + + var requests = new List(); + + var willFail = true; + for (int submitedRequests = 0; submitedRequests < 10; submitedRequests++) + { + if (willFail) + { + requests.Add(Assert.ThrowsExceptionAsync(() => collection.LoadMoreItemsAsync(0).AsTask())); + } + else + { + requests.Add(collection.LoadMoreItemsAsync(0).AsTask()); + } + + willFail = !willFail; + } + + await Task.WhenAll(requests); + + CollectionAssert.AreEqual(Enumerable.Range(0, pageSize * pages).ToArray(), collection); + } } } \ No newline at end of file From 214520d4fdb83c95c7ff64c0cf5a6b49cb082b2c Mon Sep 17 00:00:00 2001 From: Rosario Pulella Date: Thu, 6 May 2021 17:03:30 -0400 Subject: [PATCH 5/9] Better handle faults and cancelations from data source --- .../IncrementalLoadingCollection.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs b/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs index 9bfae4df227..2c0b9696308 100644 --- a/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs +++ b/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs @@ -232,7 +232,12 @@ protected virtual async Task> LoadDataAsync(CancellationToken .ContinueWith( t => { - if (t.Status == TaskStatus.RanToCompletion) + if(t.IsFaulted) + { + throw t.Exception; + } + + if (t.IsCompletedSuccessfully) { CurrentPageIndex += 1; } From 53e1c15e6c94bf51a5ea7fcf069ae3c72912da09 Mon Sep 17 00:00:00 2001 From: Rosario Pulella Date: Thu, 6 May 2021 17:03:51 -0400 Subject: [PATCH 6/9] Fix syle cop issue --- .../IncrementalLoadingCollection/IncrementalLoadingCollection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs b/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs index 2c0b9696308..f1db58c050b 100644 --- a/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs +++ b/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs @@ -252,6 +252,7 @@ private async Task LoadMoreItemsAsync(uint count, Cancellat { uint resultCount = 0; _cancellationToken = cancellationToken; + // TODO (2021.05.05): Make use common AsyncMutex class. // AsyncMutex is located at Microsoft.Toolkit.Uwp.UI.Media/Extensions/System.Threading.Tasks/AsyncMutex.cs at the time of this note. await _mutex.WaitAsync(); From 84e799a41adc0bebb63558156d6ab7ff4b3935b4 Mon Sep 17 00:00:00 2001 From: Rosario Pulella Date: Thu, 6 May 2021 17:39:59 -0400 Subject: [PATCH 7/9] Clean up tests --- .../Test_IncrementalLoadingCollection.cs | 88 ++++++++----------- 1 file changed, 38 insertions(+), 50 deletions(-) diff --git a/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs b/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs index ac3a6286967..ad396410ce7 100644 --- a/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs +++ b/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs @@ -15,6 +15,9 @@ namespace UnitTests.UI [TestClass] public class Test_IncrementalLoadingCollection { + private const int PageSize = 20; + private const int Pages = 5; + private static readonly DataSource.PageOperation[] FailPassSequence = new DataSource.PageOperation[] { @@ -25,125 +28,110 @@ private static readonly DataSource.PageOperation[] FailPassSequence DataSource.ThrowException, DataSource.PassThrough, }; - [DataRow(2500, 1000, 1000, 1000, 1000)] + private static readonly int[] AllData + = Enumerable.Range(0, Pages * PageSize).ToArray(); + [DataRow] + [DataRow(2500, 1000, 1000, 1000, 1000)] [TestMethod] public async Task Requests(params int[] pageDelays) { - const int pageSize = 20; - const int pages = 5; - - var source = new DataSource(Enumerable.Range(0, pageSize * pages), pageDelays.Select(DataSource.MakeDelayOp)); - var collection = new IncrementalLoadingCollection, int>(source, pageSize); + var source = new DataSource(AllData, pageDelays.Select(DataSource.MakeDelayOp)); + var collection = new IncrementalLoadingCollection, int>(source, PageSize); - for (int pageNum = 1; pageNum <= pages; pageNum++) + for (int pageNum = 1; pageNum <= Pages; pageNum++) { - var rez1 = await collection.LoadMoreItemsAsync(0); - Assert.AreEqual((uint)pageSize, rez1.Count); - CollectionAssert.AreEqual(Enumerable.Range(0, pageSize * pageNum).ToArray(), collection); + await collection.LoadMoreItemsAsync(0); + CollectionAssert.AreEqual(Enumerable.Range(0, PageSize * pageNum).ToArray(), collection); } } - [DataRow(2500, 1000, 1000, 1000, 1000)] [DataRow] + [DataRow(2500, 1000, 1000, 1000, 1000)] [TestMethod] public async Task RequestsAsync(params int[] pageDelays) { - const int pageSize = 20; - const int pages = 5; - - var source = new DataSource(Enumerable.Range(0, pageSize * pages), pageDelays.Select(DataSource.MakeDelayOp)); - var collection = new IncrementalLoadingCollection, int>(source, pageSize); + var source = new DataSource(AllData, pageDelays.Select(DataSource.MakeDelayOp)); + var collection = new IncrementalLoadingCollection, int>(source, PageSize); var requests = new List(); - for (int pageNum = 1; pageNum <= pages; pageNum++) + for (int pageNum = 1; pageNum <= Pages; pageNum++) { - requests.Add(collection.LoadMoreItemsAsync(0).AsTask().ContinueWith(t => - { - Assert.AreEqual(TaskStatus.RanToCompletion, t.Status); - Assert.AreEqual((uint)pageSize, t.Result.Count); - })); + requests.Add(collection.LoadMoreItemsAsync(0).AsTask() + .ContinueWith(t => Assert.IsTrue(t.IsCompletedSuccessfully))); } await Task.WhenAll(requests); - CollectionAssert.AreEqual(Enumerable.Range(0, pageSize * pages).ToArray(), collection); + CollectionAssert.AreEqual(AllData, collection); } [TestMethod] public async Task FirstRequestFails() { - const int pageSize = 20; - const int pages = 5; + var source = new DataSource(AllData, DataSource.ThrowException); + var collection = new IncrementalLoadingCollection, int>(source, PageSize); - var source = new DataSource(Enumerable.Range(0, pageSize * pages), DataSource.ThrowException); - var collection = new IncrementalLoadingCollection, int>(source, pageSize); - - await Assert.ThrowsExceptionAsync(async () => await collection.LoadMoreItemsAsync(0)); + await Assert.ThrowsExceptionAsync(collection.LoadMoreItemsAsync(0).AsTask); Assert.IsTrue(!collection.Any()); var requests = new List(); - for (int pageNum = 1; pageNum <= pages; pageNum++) + for (int pageNum = 1; pageNum <= Pages; pageNum++) { - requests.Add(collection.LoadMoreItemsAsync(0).AsTask()); + requests.Add(collection.LoadMoreItemsAsync(0).AsTask() + .ContinueWith(t => Assert.IsTrue(t.IsCompletedSuccessfully))); } await Task.WhenAll(requests); - CollectionAssert.AreEqual(Enumerable.Range(0, pageSize * pages).ToArray(), collection); + CollectionAssert.AreEqual(AllData, collection); } [TestMethod] public async Task EveryOtherRequestFails() { - const int pageSize = 20; - const int pages = 5; - - var source = new DataSource(Enumerable.Range(0, pageSize * pages), FailPassSequence); - var collection = new IncrementalLoadingCollection, int>(source, pageSize); + var source = new DataSource(AllData, FailPassSequence); + var collection = new IncrementalLoadingCollection, int>(source, PageSize); var willFail = true; - for (int submitedRequests = 0; submitedRequests < 10; submitedRequests++) + for (int submitedRequests = 0; submitedRequests < Pages * 2; submitedRequests++) { if (willFail) { - await collection.LoadMoreItemsAsync(0).AsTask().ContinueWith(t => Assert.AreEqual(TaskStatus.Faulted, t.Status)); + await Assert.ThrowsExceptionAsync(collection.LoadMoreItemsAsync(0).AsTask); } else { - await collection.LoadMoreItemsAsync(0).AsTask().ContinueWith(t => Assert.AreEqual(TaskStatus.RanToCompletion, t.Status)); + await collection.LoadMoreItemsAsync(0); } willFail = !willFail; } - CollectionAssert.AreEquivalent(Enumerable.Range(0, pageSize * pages).ToArray(), collection); + CollectionAssert.AreEquivalent(AllData, collection); } [TestMethod] public async Task EveryOtherRequestFailsAsync() { - const int pageSize = 20; - const int pages = 5; - - var source = new DataSource(Enumerable.Range(0, pageSize * pages), FailPassSequence); - var collection = new IncrementalLoadingCollection, int>(source, pageSize); + var source = new DataSource(AllData, FailPassSequence); + var collection = new IncrementalLoadingCollection, int>(source, PageSize); var requests = new List(); var willFail = true; - for (int submitedRequests = 0; submitedRequests < 10; submitedRequests++) + for (int submitedRequests = 0; submitedRequests < Pages * 2; submitedRequests++) { if (willFail) { - requests.Add(Assert.ThrowsExceptionAsync(() => collection.LoadMoreItemsAsync(0).AsTask())); + requests.Add(Assert.ThrowsExceptionAsync(collection.LoadMoreItemsAsync(0).AsTask)); } else { - requests.Add(collection.LoadMoreItemsAsync(0).AsTask()); + requests.Add(collection.LoadMoreItemsAsync(0).AsTask().ContinueWith(t => Assert.IsTrue(t.IsCompletedSuccessfully))); } willFail = !willFail; @@ -151,7 +139,7 @@ public async Task EveryOtherRequestFailsAsync() await Task.WhenAll(requests); - CollectionAssert.AreEqual(Enumerable.Range(0, pageSize * pages).ToArray(), collection); + CollectionAssert.AreEqual(AllData, collection); } } } \ No newline at end of file From ce5b1539fa8112c633a79ad46596b127b4029661 Mon Sep 17 00:00:00 2001 From: Rosario Pulella Date: Thu, 6 May 2021 17:41:56 -0400 Subject: [PATCH 8/9] Fix test not checking order --- .../UI/Collection/Test_IncrementalLoadingCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs b/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs index ad396410ce7..45a35d17b85 100644 --- a/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs +++ b/UnitTests/UnitTests.UWP/UI/Collection/Test_IncrementalLoadingCollection.cs @@ -111,7 +111,7 @@ public async Task EveryOtherRequestFails() willFail = !willFail; } - CollectionAssert.AreEquivalent(AllData, collection); + CollectionAssert.AreEqual(AllData, collection); } [TestMethod] From 70e9271d75d851ee8390e8f6725b777561c449c8 Mon Sep 17 00:00:00 2001 From: Rosario Pulella Date: Mon, 10 May 2021 10:51:59 -0400 Subject: [PATCH 9/9] make mutex not static --- .../IncrementalLoadingCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs b/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs index f1db58c050b..4058b0bf142 100644 --- a/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs +++ b/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs @@ -30,7 +30,7 @@ public class IncrementalLoadingCollection : ObservableCollection ISupportIncrementalLoading where TSource : Collections.IIncrementalSource { - private static readonly SemaphoreSlim _mutex = new SemaphoreSlim(1); + private readonly SemaphoreSlim _mutex = new SemaphoreSlim(1); /// /// Gets or sets an that is called when a retrieval operation begins.