diff --git a/Core/src/Umbrella.Utilities/Numerics/Abstractions/IConcurrentRandomGenerator.cs b/Core/src/Umbrella.Utilities/Numerics/Abstractions/IConcurrentRandomGenerator.cs
index c6cac7c5a..c1412db4c 100644
--- a/Core/src/Umbrella.Utilities/Numerics/Abstractions/IConcurrentRandomGenerator.cs
+++ b/Core/src/Umbrella.Utilities/Numerics/Abstractions/IConcurrentRandomGenerator.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Umbrella.Utilities.Exceptions;
namespace Umbrella.Utilities.Numerics.Abstractions
{
@@ -22,7 +23,8 @@ public interface IConcurrentRandomGenerator
/// is less than one or is greater than the difference between and such that it will be
/// impossible to generate a collection of the size of the specified value.
///
- IReadOnlyCollection GenerateDistinctList(int min, int max, int count, bool shuffle);
+ /// An error has occurred while generating the collection.
+ IReadOnlyCollection GenerateDistinctCollection(int min, int max, int count, bool shuffle = false);
///
/// Gets the next random number based on the specified and values.
@@ -34,8 +36,16 @@ public interface IConcurrentRandomGenerator
/// that is, the range of return values includes but not . If
/// equals , is returned.
/// Thrown if either parameter value is less than zero, or if is less than .
+ /// An error has occurred while generating the random number.
int Next(int min = 0, int max = 0);
+ ///
+ /// Returns a non-negative random integer.
+ ///
+ /// A 32-bit signed integer that is greater than or equal to 0 and less than .
+ /// An error has occurred while generating the random number.
+ int Next();
+
///
/// Returns a specified number of random elements from a sequence.
///
diff --git a/Core/src/Umbrella.Utilities/Numerics/ConcurrentRandomGenerator.cs b/Core/src/Umbrella.Utilities/Numerics/ConcurrentRandomGenerator.cs
index a4a3d797b..fca3e5348 100644
--- a/Core/src/Umbrella.Utilities/Numerics/ConcurrentRandomGenerator.cs
+++ b/Core/src/Umbrella.Utilities/Numerics/ConcurrentRandomGenerator.cs
@@ -6,6 +6,7 @@
using System.Security.Cryptography;
using System.Threading;
using Microsoft.Extensions.Logging;
+using Umbrella.Utilities.Exceptions;
using Umbrella.Utilities.Numerics.Abstractions;
namespace Umbrella.Utilities.Numerics
@@ -31,6 +32,23 @@ public ConcurrentRandomGenerator(ILogger logger)
_threadLocalRandom = new ThreadLocal(CreateRandom);
}
+ ///
+ /// Returns a non-negative random integer.
+ ///
+ /// A 32-bit signed integer that is greater than or equal to 0 and less than .
+ /// An error has occurred while generating the random number.
+ public int Next()
+ {
+ try
+ {
+ return _threadLocalRandom.Value.Next();
+ }
+ catch (Exception exc) when (_log.WriteError(exc))
+ {
+ throw new UmbrellaException("An error has occurred while generating the random number.", exc);
+ }
+ }
+
///
/// Gets the next random number based on the specified and values.
///
@@ -41,10 +59,11 @@ public ConcurrentRandomGenerator(ILogger logger)
/// that is, the range of return values includes but not . If
/// equals , is returned.
/// Thrown if either parameter value is less than zero, or if is less than .
+ /// An error has occurred while generating the random number.
public int Next(int min = 0, int max = 0)
{
- Guard.ArgumentInRange(min, nameof(min), 0);
- Guard.ArgumentInRange(max, nameof(max), 0);
+ Guard.ArgumentInRange(min, nameof(min), 0, int.MaxValue);
+ Guard.ArgumentInRange(max, nameof(max), 0, int.MaxValue);
if (min == max)
return min;
@@ -53,19 +72,11 @@ public int Next(int min = 0, int max = 0)
try
{
- Random random = _threadLocalRandom.Value;
-
- if (min > 0 && max > 0)
- return random.Next(min, max);
-
- if (max > 0)
- return random.Next(max);
-
- return random.Next();
+ return _threadLocalRandom.Value.Next(min, max);
}
catch (Exception exc) when (_log.WriteError(exc, new { min, max }))
{
- throw;
+ throw new UmbrellaException("An error has occurred while generating the random number.", exc);
}
}
@@ -83,7 +94,8 @@ public int Next(int min = 0, int max = 0)
/// is less than one or is greater than the difference between and such that it will be
/// impossible to generate a collection of the size of the specified value.
///
- public IReadOnlyCollection GenerateDistinctList(int min, int max, int count, bool shuffle)
+ /// An error has occurred while generating the collection.
+ public IReadOnlyCollection GenerateDistinctCollection(int min, int max, int count, bool shuffle = false)
{
Guard.ArgumentInRange(min, nameof(min), 0);
Guard.ArgumentInRange(max, nameof(max), 0);
@@ -125,7 +137,7 @@ public IReadOnlyCollection GenerateDistinctList(int min, int max, int count
}
catch (Exception exc) when (_log.WriteError(exc, new { min, max, count, shuffle }))
{
- throw;
+ throw new UmbrellaException("An error has occurred while generating the collection.", exc);
}
}
@@ -142,14 +154,15 @@ public IReadOnlyCollection GenerateDistinctList(int min, int max, int count
public IEnumerable TakeRandom(IEnumerable source, int count, bool shuffle = false)
{
Guard.ArgumentNotNull(source, nameof(source));
- Guard.ArgumentInRange(count, nameof(count), 1);
int sourceCount = source.Count();
if (sourceCount == 0)
yield break;
- var indexes = GenerateDistinctList(0, sourceCount, count, shuffle);
+ Guard.ArgumentInRange(count, nameof(count), 1, sourceCount);
+
+ var indexes = GenerateDistinctCollection(0, sourceCount, count, shuffle);
foreach (int idx in indexes)
{
diff --git a/Core/test/Umbrella.Utilities.Test/Numerics/ConcurrentRandomGeneratorTest.cs b/Core/test/Umbrella.Utilities.Test/Numerics/ConcurrentRandomGeneratorTest.cs
new file mode 100644
index 000000000..152e5a0de
--- /dev/null
+++ b/Core/test/Umbrella.Utilities.Test/Numerics/ConcurrentRandomGeneratorTest.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Umbrella.Utilities.Numerics;
+using Xunit;
+
+namespace Umbrella.Utilities.Test.Numerics
+{
+ public class ConcurrentRandomGeneratorTest
+ {
+ private readonly ConcurrentRandomGenerator _concurrentRandomGenerator;
+
+ public ConcurrentRandomGeneratorTest()
+ {
+ _concurrentRandomGenerator = CreateConcurrentRandomGenerator();
+ }
+
+ [Fact]
+ public void Next_Valid()
+ {
+ int num = _concurrentRandomGenerator.Next();
+
+ Assert.True(num > -1);
+ }
+
+ [Fact]
+ public void Next_InvalidMin()
+ => Assert.Throws(() => _concurrentRandomGenerator.Next(-1));
+
+ [Fact]
+ public void Next_InvalidMax()
+ => Assert.Throws(() => _concurrentRandomGenerator.Next(0, -1));
+
+ [Fact]
+ public void Next_InvalidMinMax()
+ => Assert.Throws(() => _concurrentRandomGenerator.Next(4, 3));
+
+ [Theory]
+ [InlineData(0, 0)]
+ [InlineData(10, 10)]
+ public void Next_EqMinMax_Valid(int min, int max)
+ {
+ int num = _concurrentRandomGenerator.Next(min, max);
+
+ Assert.Equal(min, num);
+ }
+
+ [Theory]
+ [InlineData(0, 1)]
+ [InlineData(0, 2)]
+ [InlineData(1, 2)]
+ [InlineData(1, 3)]
+ [InlineData(0, 99)]
+ [InlineData(100, 300)]
+ public void Next_NEqMinMax_Valid(int min, int max)
+ {
+ int num = _concurrentRandomGenerator.Next(min, max);
+
+ Assert.True(num >= min && num < max);
+ }
+
+ [Fact]
+ public void GenerateDistinctList_InvalidMin()
+ => Assert.Throws(() => _concurrentRandomGenerator.GenerateDistinctCollection(-1, 0, 1));
+
+ [Fact]
+ public void GenerateDistinctList_InvalidMax()
+ => Assert.Throws(() => _concurrentRandomGenerator.GenerateDistinctCollection(0, -1, 1));
+
+ [Fact]
+ public void GenerateDistinctList_InvalidMinMax()
+ => Assert.Throws(() => _concurrentRandomGenerator.GenerateDistinctCollection(4, 3, 1));
+
+ [Theory]
+ [InlineData(0, 1, 0)]
+ [InlineData(9, 10, 2)]
+ [InlineData(10, 12, 3)]
+ public void GenerateDistinctList_InvalidCount(int min, int max, int count)
+ => Assert.Throws(() => _concurrentRandomGenerator.GenerateDistinctCollection(min, max, count));
+
+ [Theory]
+ [InlineData(0, 0, 1)]
+ [InlineData(10, 10, 1)]
+ public void GenerateDistinctList_EqMinMaxCount_Valid(int min, int max, int count)
+ {
+ var items = _concurrentRandomGenerator.GenerateDistinctCollection(min, max, count);
+
+ Assert.Equal(count, items.Count);
+ Assert.Equal(count, items.Distinct().Count());
+ }
+
+ [Theory]
+ [InlineData(0, 9, 1, false)]
+ [InlineData(0, 4, 2, false)]
+ [InlineData(1, 5, 1, false)]
+ [InlineData(1, 10, 2, false)]
+ [InlineData(0, 99, 10, false)]
+ [InlineData(100, 300, 27, false)]
+ [InlineData(0, 9, 1, true)]
+ [InlineData(0, 4, 2, true)]
+ [InlineData(1, 5, 1, true)]
+ [InlineData(1, 10, 2, true)]
+ [InlineData(0, 99, 10, true)]
+ [InlineData(100, 300, 27, true)]
+ public void GenerateDistinctList_NEqMinMaxCount_Valid(int min, int max, int count, bool shuffle)
+ {
+ var items = _concurrentRandomGenerator.GenerateDistinctCollection(min, max, count, shuffle);
+
+ Assert.Equal(count, items.Count);
+ Assert.Equal(count, items.Distinct().Count());
+ }
+
+ [Theory]
+ [InlineData(0, 1, 1, false)]
+ [InlineData(0, 2, 2, false)]
+ [InlineData(1, 2, 1, false)]
+ [InlineData(1, 3, 2, false)]
+ [InlineData(0, 99, 99, false)]
+ [InlineData(100, 300, 200, false)]
+ [InlineData(0, 1, 1, true)]
+ [InlineData(0, 2, 2, true)]
+ [InlineData(1, 2, 1, true)]
+ [InlineData(1, 3, 2, true)]
+ [InlineData(0, 99, 99, true)]
+ [InlineData(100, 300, 200, true)]
+ public void GenerateDistinctList_NEqMinMaxCountFullRange_Valid(int min, int max, int count, bool shuffle)
+ {
+ var items = _concurrentRandomGenerator.GenerateDistinctCollection(min, max, count, shuffle);
+
+ Assert.Equal(count, items.Count);
+ Assert.Equal(count, items.Distinct().Count());
+ }
+
+ [Fact]
+ public void TakeRandom_Source_Null()
+ => Assert.Throws(() => _concurrentRandomGenerator.TakeRandom