Skip to content

Commit

Permalink
Adding a bunch of tests for UtilPack, with the goal of catching error…
Browse files Browse the repository at this point in the history
… and upping code coverage. Already discovered and fixed few bugs.
  • Loading branch information
stazz committed Dec 27, 2018
1 parent b42521e commit 1944df9
Show file tree
Hide file tree
Showing 26 changed files with 1,038 additions and 585 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

<PropertyGroup>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<TreatSpecificWarningsAsErrors />
</PropertyGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.1' ">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
</PropertyGroup>

<PropertyGroup>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<TreatSpecificWarningsAsErrors />
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions Source/Code/UtilPack.JSON/UtilPack.JSON.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
</PropertyGroup>

<PropertyGroup>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<TreatSpecificWarningsAsErrors />
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
</PropertyGroup>

Expand Down
3 changes: 0 additions & 3 deletions Source/Code/UtilPack.Logging/UtilPack.Logging.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@

<PropertyGroup>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<TreatSpecificWarningsAsErrors />
<LangVersion>latest</LangVersion>
<DefineConstants>$(DefineConstants);INTERNALIZE</DefineConstants>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
</PropertyGroup>

<PropertyGroup>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<TreatSpecificWarningsAsErrors />
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
</PropertyGroup>

Expand Down
2 changes: 0 additions & 2 deletions Source/Code/UtilPack.TabularData/UtilPack.TabularData.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

<PropertyGroup>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<TreatSpecificWarningsAsErrors />
</PropertyGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
Expand Down
23 changes: 19 additions & 4 deletions Source/Code/UtilPack/ArgumentValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ namespace UtilPack
#endif
static class ArgumentValidator
{
/// <summary>
/// This message will be the error message of exception thrown by <see cref="ValidateNotNullReference"/>.
/// </summary>
public const String NULLREF_MESSAGE = "Extension method 'this' parameter is null.";

/// <summary>
/// The parameter name will be suffixed by this string when throwing an error inside <see cref="ValidateNotEmpty(String, String)"/>.
/// </summary>
public const String EMPTY_STRING_SUFFIX = " was empty string.";

/// <summary>
/// The parameter name will be suffixed by this string when throwing an error inside <see cref="ValidateNotEmpty{T}(String, IEnumerable{T})"/> or <see cref="ValidateNotEmpty{T}(String, T[])"/>.
/// </summary>
public const String EMPTY_SUFFIX = " was empty.";

/// <summary>
/// Checks whether a method parameter is <c>null</c>.
/// </summary>
Expand Down Expand Up @@ -75,7 +90,7 @@ public static T ValidateNotNullReference<T>( T value )
{
if ( value == null )
{
throw new NullReferenceException( "Extension method 'this' parameter is null." );
throw new NullReferenceException( NULLREF_MESSAGE );
}
return value;
}
Expand All @@ -97,7 +112,7 @@ public static IEnumerable<T> ValidateNotEmpty<T>( String parameterName, IEnumera
ValidateNotNull( parameterName, value );
if ( !value.Any() )
{
throw new ArgumentException( parameterName + " was empty." );
throw new ArgumentException( parameterName + EMPTY_SUFFIX );
}
return value;
}
Expand All @@ -119,7 +134,7 @@ public static T[] ValidateNotEmpty<T>( String parameterName, T[] value )
ValidateNotNull( parameterName, value );
if ( value.Length <= 0 )
{
throw new ArgumentException( parameterName + " was empty." );
throw new ArgumentException( parameterName + EMPTY_SUFFIX );
}
return value;
}
Expand All @@ -140,7 +155,7 @@ public static String ValidateNotEmpty( String parameterName, String value )
ValidateNotNull( parameterName, value );
if ( value.Length == 0 )
{
throw new ArgumentException( parameterName + " was empty string." );
throw new ArgumentException( parameterName + EMPTY_STRING_SUFFIX );
}
return value;
}
Expand Down
191 changes: 143 additions & 48 deletions Source/Code/UtilPack/Asynchrony.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
using System.Threading.Tasks;
using UtilPack;

using TTaskExt = System.Threading.Tasks.
#if NET40
TaskEx
#else
Task
#endif
;

namespace UtilPack
{
/// <summary>
Expand Down Expand Up @@ -64,7 +72,7 @@ static class TaskUtils
/// <summary>
/// Gets the task which is completed.
/// </summary>
public static System.Threading.Tasks.Task CompletedTask { get; }
public static Task CompletedTask { get; }

/// <summary>
/// Gets the task which is completed to a boolean value <c>true</c>.
Expand All @@ -80,25 +88,13 @@ static class TaskUtils

static TaskUtils()
{
var src = new System.Threading.Tasks.TaskCompletionSource<Object>();
var src = new TaskCompletionSource<Object>();
src.SetResult( null );

CompletedTask = src.Task;

True =
#if NET40
TaskEx
#else
Task
#endif
.FromResult( true );
False =
#if NET40
TaskEx
#else
Task
#endif
.FromResult( false );
True = TTaskExt.FromResult( true );
False = TTaskExt.FromResult( false );
}

/// <summary>
Expand Down Expand Up @@ -147,9 +143,122 @@ public static Task<T> FromCanceled<T>( CancellationToken token )
{
throw new ArgumentException( nameof( token ) );
}
return new Task<T>( () => default( T ), token, TaskCreationOptions.None );
return new Task<T>( () => default, token, TaskCreationOptions.None );
}

}

public static partial class UtilPackExtensions
{
/// <summary>
/// This is helper method to return task which will throw a <see cref="TimeoutException"/> if this task has not completed in given timeout.
/// </summary>
/// <param name="task">This task.</param>
/// <param name="timeout">The maximum of time to wait for this task to complete.</param>
/// <param name="token">The <see cref="CancellationToken"/> to use.</param>
/// <returns>A task which either completes when this task does, or throws an <see cref="TimeoutException"/>.</returns>
/// <exception cref="TimeoutException">If given amount of time has passed before this task completes.</exception>
/// <exception cref="OperationCanceledException">If <paramref name="token"/> gets canceled before this task completes and before timeout has expired.</exception>
#if !NET40
[System.Runtime.CompilerServices.MethodImpl( System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining )]
#endif
public static Task TimeoutAfter( this Task task, TimeSpan timeout, CancellationToken token )
{
if ( task.IsCompleted || timeout == TimeSpan.FromMilliseconds( -1 ) )
{
// The task is done or will never timeout anyway
return task;
}
else if ( timeout == TimeSpan.Zero || token.IsCancellationRequested )
{
// We have already timeouted
#if NET40 || NET45 || NETSTANDARD1_0 || NETSTANDARD1_1
var tsc = new TaskCompletionSource<Boolean>();
tsc.SetException( new TimeoutException() );
return tsc.Task;
#else
return Task.FromException( new TimeoutException() );
#endif
}
else
{
return task.TimeoutAfterImpl( timeout, token );
}

}

private static async Task TimeoutAfterImpl( this Task task, TimeSpan timeout, CancellationToken token )
{
using ( var linked = CancellationTokenSource.CreateLinkedTokenSource( token ) )
{

if ( ReferenceEquals( task, await TTaskExt.WhenAny( task, TTaskExt.Delay( timeout, linked.Token ) ) ) )
{
// Task completed before the timeout, cancel timeout (thus disposing of timer)
linked.Cancel();
await task;
}
else
{
throw new TimeoutException();
}
}
}

/// <summary>
/// This is helper method to return task which will throw a <see cref="TimeoutException"/> if this task has not completed in given timeout.
/// </summary>
/// <param name="task">This task.</param>
/// <param name="timeout">The maximum of time to wait for this task to complete.</param>
/// <param name="token">The <see cref="CancellationToken"/> to use.</param>
/// <returns>A task which either completes when this task does, or throws an <see cref="TimeoutException"/>.</returns>
/// <exception cref="TimeoutException">If given amount of time has passed before this task completes.</exception>
/// <exception cref="OperationCanceledException">If <paramref name="token"/> gets canceled before this task completes and before timeout has expired.</exception>
#if !NET40
[System.Runtime.CompilerServices.MethodImpl( System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining )]
#endif
public static Task<T> TimeoutAfter<T>( this Task<T> task, TimeSpan timeout, CancellationToken token )
{
if ( task.IsCompleted || timeout == TimeSpan.FromMilliseconds( -1 ) )
{
// The task is done or will never timeout anyway
return task;
}
else if ( timeout == TimeSpan.Zero || token.IsCancellationRequested )
{
// We have already timeouted
#if NET40 || NET45 || NETSTANDARD1_0 || NETSTANDARD1_1
var tsc = new TaskCompletionSource<T>();
tsc.SetException( new TimeoutException() );
return tsc.Task;
#else
return Task.FromException<T>( new TimeoutException() );
#endif
}
else
{
return task.TimeoutAfterImpl( timeout, token );
}

}

private static async Task<T> TimeoutAfterImpl<T>( this Task<T> task, TimeSpan timeout, CancellationToken token )
{
using ( var linked = CancellationTokenSource.CreateLinkedTokenSource( token ) )
{

if ( ReferenceEquals( task, await TTaskExt.WhenAny( task, TTaskExt.Delay( timeout, linked.Token ) ) ) )
{
// Task completed before the timeout, cancel timeout (thus disposing of timer)
linked.Cancel();
return await task;
}
else
{
throw new TimeoutException();
}
}
}
}

/// <summary>
Expand Down Expand Up @@ -252,27 +361,9 @@ public void Dispose()
public AsyncLock()
{
this._semaphore = new SemaphoreSlim( 1, 1 );
this._completed =
#if NET40
TaskEx
#else
Task
#endif
.FromResult( new LockUseScope( this ) );
this._completedNullable =
#if NET40
TaskEx
#else
Task
#endif
.FromResult<LockUseScope?>( new LockUseScope( this ) );
this._notCompletedNullable =
#if NET40
TaskEx
#else
Task
#endif
.FromResult<LockUseScope?>( null );
this._completed = TTaskExt.FromResult( new LockUseScope( this ) );
this._completedNullable = TTaskExt.FromResult<LockUseScope?>( new LockUseScope( this ) );
this._notCompletedNullable = TTaskExt.FromResult<LockUseScope?>( null );
}

/// <summary>
Expand Down Expand Up @@ -429,13 +520,7 @@ public static async Task InvokeAndWaitForAwaitables<TEventArgs, TActualEventArgs
if ( ( awaitables = args?.GetAwaitableArray() ) != null )
{

await
#if NET40
TaskEx
#else
Task
#endif
.WhenAll( awaitables );
await TTaskExt.WhenAll( awaitables );
}
}
}
Expand Down Expand Up @@ -492,7 +577,10 @@ public static async Task DisposeAsyncSafely( this IAsyncDisposableWithToken disp
/// <remarks>
/// At least current implementation is most likely not very efficient...
/// </remarks>
public static async Task WaitAsync( this SemaphoreSlim semaphore, Int32 tick = 100 )
public static async Task WaitAsync(
this SemaphoreSlim semaphore,
Int32 tick = 100
)
{
while ( !semaphore.Wait( 0 ) )
{
Expand All @@ -512,12 +600,19 @@ public static async Task WaitAsync( this SemaphoreSlim semaphore, Int32 tick = 1
/// <remarks>
/// At least current implementation is most likely not very efficient...
/// </remarks>
public static async Task<Boolean> WaitAsync( this SemaphoreSlim semaphore, TimeSpan timeout, CancellationToken token = default, Int32 tick = 100 )
public static async Task<Boolean> WaitAsync(
this SemaphoreSlim semaphore,
TimeSpan timeout,
CancellationToken token = default,
Int32 tick = 100
)
{
var curTimeout = 0L;
Boolean retVal;
while ( !( retVal = semaphore.Wait( 0 ) ) && curTimeout < timeout.TotalMilliseconds )
var noTimeout = TimeSpan.FromMilliseconds( -1 ) == timeout;
while ( !( retVal = semaphore.Wait( 0 ) ) && ( noTimeout || curTimeout < timeout.TotalMilliseconds ) )
{
// Delay is not precise (waits _at least_ given amount), TODO refactor this to use DateTime.UtcNow
await TaskEx.Delay( tick, token );
curTimeout += tick;
}
Expand Down
Loading

0 comments on commit 1944df9

Please sign in to comment.