From d6630f29eadb5ead891e226c09ff20071376cf22 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 30 May 2021 19:04:51 +0200 Subject: [PATCH] Adding tests for enumerables --- Kyoo.Common/Utility/EnumerableExtensions.cs | 105 ++++++++++++-------- Kyoo.Tests/KAssert.cs | 27 +++++ Kyoo.Tests/Utility/EnumerableTests.cs | 71 +++++++++++++ 3 files changed, 163 insertions(+), 40 deletions(-) create mode 100644 Kyoo.Tests/Utility/EnumerableTests.cs diff --git a/Kyoo.Common/Utility/EnumerableExtensions.cs b/Kyoo.Common/Utility/EnumerableExtensions.cs index e4d0379e..459d5f1c 100644 --- a/Kyoo.Common/Utility/EnumerableExtensions.cs +++ b/Kyoo.Common/Utility/EnumerableExtensions.cs @@ -18,13 +18,13 @@ namespace Kyoo /// The function that will map each items /// The type of items in /// The type of items in the returned list - /// The list mapped or null if the input map was null. - /// mapper can't be null - public static IEnumerable Map([CanBeNull] this IEnumerable self, + /// The list mapped. + /// The list or the mapper can't be null + public static IEnumerable Map([NotNull] this IEnumerable self, [NotNull] Func mapper) { if (self == null) - return null; + throw new ArgumentNullException(nameof(self)); if (mapper == null) throw new ArgumentNullException(nameof(mapper)); @@ -46,28 +46,33 @@ namespace Kyoo /// A map where the mapping function is asynchronous. /// Note: might interest you. /// - /// The IEnumerable to map. If self is null, an empty list is returned + /// The IEnumerable to map. /// The asynchronous function that will map each items /// The type of items in /// The type of items in the returned list /// The list mapped as an AsyncEnumerable - /// mapper can't be null - public static async IAsyncEnumerable MapAsync([CanBeNull] this IEnumerable self, + /// The list or the mapper can't be null + public static IAsyncEnumerable MapAsync([NotNull] this IEnumerable self, [NotNull] Func> mapper) { if (self == null) - yield break; + throw new ArgumentNullException(nameof(self)); if (mapper == null) throw new ArgumentNullException(nameof(mapper)); - - using IEnumerator enumerator = self.GetEnumerator(); - int index = 0; - while (enumerator.MoveNext()) + static async IAsyncEnumerable Generator(IEnumerable self, Func> mapper) { - yield return await mapper(enumerator.Current, index); - index++; + using IEnumerator enumerator = self.GetEnumerator(); + int index = 0; + + while (enumerator.MoveNext()) + { + yield return await mapper(enumerator.Current, index); + index++; + } } + + return Generator(self, mapper); } /// @@ -78,19 +83,24 @@ namespace Kyoo /// The type of items in /// The type of items in the returned list /// The list mapped as an AsyncEnumerable - /// mapper can't be null - public static async IAsyncEnumerable SelectAsync([CanBeNull] this IEnumerable self, + /// The list or the mapper can't be null + public static IAsyncEnumerable SelectAsync([NotNull] this IEnumerable self, [NotNull] Func> mapper) { if (self == null) - yield break; + throw new ArgumentNullException(nameof(self)); if (mapper == null) throw new ArgumentNullException(nameof(mapper)); - - using IEnumerator enumerator = self.GetEnumerator(); - while (enumerator.MoveNext()) - yield return await mapper(enumerator.Current); + static async IAsyncEnumerable Generator(IEnumerable self, Func> mapper) + { + using IEnumerator enumerator = self.GetEnumerator(); + + while (enumerator.MoveNext()) + yield return await mapper(enumerator.Current); + } + + return Generator(self, mapper); } /// @@ -100,16 +110,20 @@ namespace Kyoo /// The type of items in the async list and in the returned list. /// A task that will return a simple list /// The list can't be null - public static async Task> ToListAsync([NotNull] this IAsyncEnumerable self) + public static Task> ToListAsync([NotNull] this IAsyncEnumerable self) { if (self == null) throw new ArgumentNullException(nameof(self)); - - List ret = new(); - - await foreach(T i in self) - ret.Add(i); - return ret; + + static async Task> ToList(IAsyncEnumerable self) + { + List ret = new(); + await foreach (T i in self) + ret.Add(i); + return ret; + } + + return ToList(self); } /// @@ -118,22 +132,33 @@ namespace Kyoo /// The enumerable to check /// The action to execute is the list is empty /// The type of items inside the list - /// - public static IEnumerable IfEmpty(this IEnumerable self, Action action) + /// The iterable and the action can't be null. + /// The iterator proxied, there is no dual iterations. + public static IEnumerable IfEmpty([NotNull] this IEnumerable self, [NotNull] Action action) { - using IEnumerator enumerator = self.GetEnumerator(); + if (self == null) + throw new ArgumentNullException(nameof(self)); + if (action == null) + throw new ArgumentNullException(nameof(action)); - if (!enumerator.MoveNext()) + static IEnumerable Generator(IEnumerable self, Action action) { - action(); - yield break; + using IEnumerator enumerator = self.GetEnumerator(); + + if (!enumerator.MoveNext()) + { + action(); + yield break; + } + + do + { + yield return enumerator.Current; + } + while (enumerator.MoveNext()); } - - do - { - yield return enumerator.Current; - } - while (enumerator.MoveNext()); + + return Generator(self, action); } /// diff --git a/Kyoo.Tests/KAssert.cs b/Kyoo.Tests/KAssert.cs index b6168251..2c97ddce 100644 --- a/Kyoo.Tests/KAssert.cs +++ b/Kyoo.Tests/KAssert.cs @@ -1,14 +1,41 @@ using System.Reflection; using Xunit; +using Xunit.Sdk; namespace Kyoo.Tests { + /// + /// Custom assertions used by Kyoo's tests. + /// public static class KAssert { + /// + /// Check if every property of the item is equal to the other's object. + /// + /// The value to check against + /// The value to check + /// The type to check public static void DeepEqual(T expected, T value) { foreach (PropertyInfo property in typeof(T).GetProperties()) Assert.Equal(property.GetValue(expected), property.GetValue(value)); } + + /// + /// Explicitly fail a test. + /// + public static void Fail() + { + throw new XunitException(); + } + + /// + /// Explicitly fail a test. + /// + /// The message that will be seen in the test report + public static void Fail(string message) + { + throw new XunitException(message); + } } } \ No newline at end of file diff --git a/Kyoo.Tests/Utility/EnumerableTests.cs b/Kyoo.Tests/Utility/EnumerableTests.cs new file mode 100644 index 00000000..9cdd8a00 --- /dev/null +++ b/Kyoo.Tests/Utility/EnumerableTests.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Kyoo.Tests +{ + public class EnumerableTests + { + [Fact] + public void MapTest() + { + int[] list = {1, 2, 3, 4}; + Assert.All(list.Map((x, i) => (x, i)), x => Assert.Equal(x.x - 1, x.i)); + Assert.Throws(() => list.Map(((Func)null)!)); + list = null; + Assert.Throws(() => list!.Map((x, _) => x + 1)); + } + + [Fact] + public async Task MapAsyncTest() + { + int[] list = {1, 2, 3, 4}; + await foreach((int x, int i) in list.MapAsync((x, i) => Task.FromResult((x, i)))) + { + Assert.Equal(x - 1, i); + } + Assert.Throws(() => list.MapAsync(((Func>)null)!)); + list = null; + Assert.Throws(() => list!.MapAsync((x, _) => Task.FromResult(x + 1))); + } + + [Fact] + public async Task SelectAsyncTest() + { + int[] list = {1, 2, 3, 4}; + int i = 2; + await foreach(int x in list.SelectAsync(x => Task.FromResult(x + 1))) + { + Assert.Equal(i++, x); + } + Assert.Throws(() => list.SelectAsync(((Func>)null)!)); + list = null; + Assert.Throws(() => list!.SelectAsync(x => Task.FromResult(x + 1))); + } + + [Fact] + public async Task ToListAsyncTest() + { + int[] expected = {1, 2, 3, 4}; + IAsyncEnumerable list = expected.SelectAsync(Task.FromResult); + Assert.Equal(expected, await list.ToListAsync()); + list = null; + await Assert.ThrowsAsync(() => list!.ToListAsync()); + } + + [Fact] + public void IfEmptyTest() + { + int[] list = {1, 2, 3, 4}; + list = list.IfEmpty(() => KAssert.Fail("Empty action should not be triggered.")).ToArray(); + Assert.Throws(() => list.IfEmpty(null!).ToList()); + list = null; + Assert.Throws(() => list!.IfEmpty(() => {}).ToList()); + list = Array.Empty(); + Assert.Throws(() => list.IfEmpty(() => throw new ArgumentException()).ToList()); + Assert.Empty(list.IfEmpty(() => {})); + } + } +} \ No newline at end of file