Skip to content

Commit

Permalink
Fixes to the ConcurrentRandomGenerator.
Browse files Browse the repository at this point in the history
Added unit tests for the ConcurrentRandomGenerator.
  • Loading branch information
ricardoeduardo committed Jul 30, 2019
1 parent 88be9cb commit 608a652
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Umbrella.Utilities.Exceptions;

namespace Umbrella.Utilities.Numerics.Abstractions
{
Expand All @@ -22,7 +23,8 @@ public interface IConcurrentRandomGenerator
/// is less than one or is greater than the difference between <paramref name="min"/> and <paramref name="max"/> such that it will be
/// impossible to generate a collection of the size of the specified <paramref name="count"/> value.
/// </exception>
IReadOnlyCollection<int> GenerateDistinctList(int min, int max, int count, bool shuffle);
/// <exception cref="UmbrellaException">An error has occurred while generating the collection.</exception>
IReadOnlyCollection<int> GenerateDistinctCollection(int min, int max, int count, bool shuffle = false);

/// <summary>
/// Gets the next random number based on the specified <paramref name="min"/> and <paramref name="max"/> values.
Expand All @@ -34,8 +36,16 @@ public interface IConcurrentRandomGenerator
/// that is, the range of return values includes <paramref name="min"/> but not <paramref name="max"/>. If <paramref name="min"/>
/// equals <paramref name="max"/>, <paramref name="min"/> is returned.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown if either parameter value is less than zero, or if <paramref name="max"/> is less than <paramref name="min"/>.</exception>
/// <exception cref="UmbrellaException">An error has occurred while generating the random number.</exception>
int Next(int min = 0, int max = 0);

/// <summary>
/// Returns a non-negative random integer.
/// </summary>
/// <returns>A 32-bit signed integer that is greater than or equal to 0 and less than <see cref="int.MaxValue"/>.</returns>
/// <exception cref="UmbrellaException">An error has occurred while generating the random number.</exception>
int Next();

/// <summary>
/// Returns a specified number of random elements from a sequence.
/// </summary>
Expand Down
45 changes: 29 additions & 16 deletions Core/src/Umbrella.Utilities/Numerics/ConcurrentRandomGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,6 +32,23 @@ public ConcurrentRandomGenerator(ILogger<ConcurrentRandomGenerator> logger)
_threadLocalRandom = new ThreadLocal<Random>(CreateRandom);
}

/// <summary>
/// Returns a non-negative random integer.
/// </summary>
/// <returns>A 32-bit signed integer that is greater than or equal to 0 and less than <see cref="int.MaxValue"/>.</returns>
/// <exception cref="UmbrellaException">An error has occurred while generating the random number.</exception>
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);
}
}

/// <summary>
/// Gets the next random number based on the specified <paramref name="min"/> and <paramref name="max"/> values.
/// </summary>
Expand All @@ -41,10 +59,11 @@ public ConcurrentRandomGenerator(ILogger<ConcurrentRandomGenerator> logger)
/// that is, the range of return values includes <paramref name="min"/> but not <paramref name="max"/>. If <paramref name="min"/>
/// equals <paramref name="max"/>, <paramref name="min"/> is returned.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown if either parameter value is less than zero, or if <paramref name="max"/> is less than <paramref name="min"/>.</exception>
/// <exception cref="UmbrellaException">An error has occurred while generating the random number.</exception>
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;
Expand All @@ -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);
}
}

Expand All @@ -83,7 +94,8 @@ public int Next(int min = 0, int max = 0)
/// is less than one or is greater than the difference between <paramref name="min"/> and <paramref name="max"/> such that it will be
/// impossible to generate a collection of the size of the specified <paramref name="count"/> value.
/// </exception>
public IReadOnlyCollection<int> GenerateDistinctList(int min, int max, int count, bool shuffle)
/// <exception cref="UmbrellaException">An error has occurred while generating the collection.</exception>
public IReadOnlyCollection<int> GenerateDistinctCollection(int min, int max, int count, bool shuffle = false)
{
Guard.ArgumentInRange(min, nameof(min), 0);
Guard.ArgumentInRange(max, nameof(max), 0);
Expand Down Expand Up @@ -125,7 +137,7 @@ public IReadOnlyCollection<int> 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);
}
}

Expand All @@ -142,14 +154,15 @@ public IReadOnlyCollection<int> GenerateDistinctList(int min, int max, int count
public IEnumerable<T> TakeRandom<T>(IEnumerable<T> 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)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ArgumentOutOfRangeException>(() => _concurrentRandomGenerator.Next(-1));

[Fact]
public void Next_InvalidMax()
=> Assert.Throws<ArgumentOutOfRangeException>(() => _concurrentRandomGenerator.Next(0, -1));

[Fact]
public void Next_InvalidMinMax()
=> Assert.Throws<ArgumentOutOfRangeException>(() => _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<ArgumentOutOfRangeException>(() => _concurrentRandomGenerator.GenerateDistinctCollection(-1, 0, 1));

[Fact]
public void GenerateDistinctList_InvalidMax()
=> Assert.Throws<ArgumentOutOfRangeException>(() => _concurrentRandomGenerator.GenerateDistinctCollection(0, -1, 1));

[Fact]
public void GenerateDistinctList_InvalidMinMax()
=> Assert.Throws<ArgumentOutOfRangeException>(() => _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<ArgumentOutOfRangeException>(() => _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<ArgumentNullException>(() => _concurrentRandomGenerator.TakeRandom<object>(null, 1).ToArray());

[Fact]
public void TakeRandom_Source_Empty()
{
string[] items = new string[0];

var result = _concurrentRandomGenerator.TakeRandom(items, 1);

Assert.Empty(result);
}

[Theory]
[InlineData(0)] // Zero not allowed
[InlineData(3)] // Greater than the source size
public void TakeRandom_Count_Invalid(int count)
=> Assert.Throws<ArgumentOutOfRangeException>(() => _concurrentRandomGenerator.TakeRandom(new[] { 1, 2 }, count).ToArray());

[Theory]
[InlineData(1, false)]
[InlineData(2, false)]
[InlineData(3, false)]
[InlineData(4, false)]
[InlineData(5, false)]
[InlineData(6, false)]
[InlineData(7, false)]
[InlineData(8, false)]
[InlineData(9, false)]
[InlineData(10, false)]
[InlineData(1, true)]
[InlineData(2, true)]
[InlineData(3, true)]
[InlineData(4, true)]
[InlineData(5, true)]
[InlineData(6, true)]
[InlineData(7, true)]
[InlineData(8, true)]
[InlineData(9, true)]
[InlineData(10, true)]
public void TakeRandom_Valid(int count, bool shuffle)
{
int[] items = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

int[] result = _concurrentRandomGenerator.TakeRandom(items, count, shuffle).ToArray();

Assert.Equal(count, result.Length);
Assert.Equal(count, result.Distinct().Count());
}

private ConcurrentRandomGenerator CreateConcurrentRandomGenerator()
{
var loggerMock = new Mock<ILogger<ConcurrentRandomGenerator>>();

return new ConcurrentRandomGenerator(loggerMock.Object);
}
}
}

0 comments on commit 608a652

Please sign in to comment.