// 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.Collections.Generic;
using System.Reflection;
using Xunit;
namespace System.Threading.Tasks.Tests
{
public class UnwrapTests
{
/// Tests unwrap argument validation.
[Fact]
public void ArgumentValidation()
{
Assert.Throws(() => { ((Task)null).Unwrap(); });
Assert.Throws(() => { ((Task>)null).Unwrap(); });
Assert.Throws(() => { ((Task>)null).Unwrap(); });
}
///
/// Tests Unwrap when both the outer task and non-generic inner task have completed by the time Unwrap is called.
///
/// Will be run with a RanToCompletion, Faulted, and Canceled task.
[Theory]
[MemberData(nameof(CompletedNonGenericTasks))]
public void NonGeneric_Completed_Completed(Task inner)
{
Task outer = Task.FromResult(inner);
Task unwrappedInner = outer.Unwrap();
Assert.True(unwrappedInner.IsCompleted);
AssertTasksAreEqual(inner, unwrappedInner);
}
///
/// Tests Unwrap when both the outer task and non-generic inner task have completed by the time Unwrap is called.
///
/// Will be run with a RanToCompletion, Faulted, and Canceled task.
[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, ".NET Core optimization to return the exact same object")]
[Theory]
[MemberData(nameof(CompletedNonGenericTasks))]
public void NonGeneric_Completed_Completed_OptimizeToUseSameInner(Task inner)
{
Task outer = Task.FromResult(inner);
Task unwrappedInner = outer.Unwrap();
Assert.True(unwrappedInner.IsCompleted);
Assert.Same(inner, unwrappedInner);
}
///
/// Tests Unwrap when both the outer task and generic inner task have completed by the time Unwrap is called.
///
/// The inner task.
[Theory]
[MemberData(nameof(CompletedStringTasks))]
public void Generic_Completed_Completed(Task inner)
{
Task> outer = Task.FromResult(inner);
Task unwrappedInner = outer.Unwrap();
Assert.True(unwrappedInner.IsCompleted);
AssertTasksAreEqual(inner, unwrappedInner);
}
///
/// Tests Unwrap when both the outer task and generic inner task have completed by the time Unwrap is called.
///
/// The inner task.
[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, ".NET Core optimization to return the exact same object")]
[Theory]
[MemberData(nameof(CompletedStringTasks))]
public void Generic_Completed_Completed_OptimizeToUseSameInner(Task inner)
{
Task> outer = Task.FromResult(inner);
Task unwrappedInner = outer.Unwrap();
Assert.True(unwrappedInner.IsCompleted);
Assert.Same(inner, unwrappedInner);
}
///
/// Tests Unwrap when the non-generic inner task has completed but the outer task has not completed by the time Unwrap is called.
///
/// The inner task.
[Theory]
[MemberData(nameof(CompletedNonGenericTasks))]
public void NonGeneric_NotCompleted_Completed(Task inner)
{
var outerTcs = new TaskCompletionSource();
Task outer = outerTcs.Task;
Task unwrappedInner = outer.Unwrap();
Assert.False(unwrappedInner.IsCompleted);
outerTcs.SetResult(inner);
AssertTasksAreEqual(inner, unwrappedInner);
}
///
/// Tests Unwrap when the generic inner task has completed but the outer task has not completed by the time Unwrap is called.
///
/// The inner task.
[Theory]
[MemberData(nameof(CompletedStringTasks))]
public void Generic_NotCompleted_Completed(Task inner)
{
var outerTcs = new TaskCompletionSource>();
Task> outer = outerTcs.Task;
Task unwrappedInner = outer.Unwrap();
Assert.False(unwrappedInner.IsCompleted);
outerTcs.SetResult(inner);
AssertTasksAreEqual(inner, unwrappedInner);
}
///
/// Tests Unwrap when the non-generic inner task has not yet completed but the outer task has completed by the time Unwrap is called.
///
/// How the inner task should be completed.
[Theory]
[InlineData(TaskStatus.RanToCompletion)]
[InlineData(TaskStatus.Faulted)]
[InlineData(TaskStatus.Canceled)]
public void NonGeneric_Completed_NotCompleted(TaskStatus innerStatus)
{
var innerTcs = new TaskCompletionSource();
Task inner = innerTcs.Task;
Task outer = Task.FromResult(inner);
Task unwrappedInner = outer.Unwrap();
Assert.False(unwrappedInner.IsCompleted);
switch (innerStatus)
{
case TaskStatus.RanToCompletion:
innerTcs.SetResult(true);
break;
case TaskStatus.Faulted:
innerTcs.SetException(new InvalidProgramException());
break;
case TaskStatus.Canceled:
innerTcs.SetCanceled();
break;
}
AssertTasksAreEqual(inner, unwrappedInner);
}
///
/// Tests Unwrap when the non-generic inner task has not yet completed but the outer task has completed by the time Unwrap is called.
///
/// How the inner task should be completed.
[Theory]
[InlineData(TaskStatus.RanToCompletion)]
[InlineData(TaskStatus.Faulted)]
[InlineData(TaskStatus.Canceled)]
public void Generic_Completed_NotCompleted(TaskStatus innerStatus)
{
var innerTcs = new TaskCompletionSource();
Task inner = innerTcs.Task;
Task> outer = Task.FromResult(inner);
Task unwrappedInner = outer.Unwrap();
Assert.False(unwrappedInner.IsCompleted);
switch (innerStatus)
{
case TaskStatus.RanToCompletion:
innerTcs.SetResult(42);
break;
case TaskStatus.Faulted:
innerTcs.SetException(new InvalidProgramException());
break;
case TaskStatus.Canceled:
innerTcs.SetCanceled();
break;
}
AssertTasksAreEqual(inner, unwrappedInner);
}
///
/// Tests Unwrap when neither the non-generic inner task nor the outer task has completed by the time Unwrap is called.
///
/// Whether the outer task or the inner task completes first.
/// How the inner task should be completed.
[Theory]
[InlineData(true, TaskStatus.RanToCompletion)]
[InlineData(true, TaskStatus.Canceled)]
[InlineData(true, TaskStatus.Faulted)]
[InlineData(false, TaskStatus.RanToCompletion)]
[InlineData(false, TaskStatus.Canceled)]
[InlineData(false, TaskStatus.Faulted)]
public void NonGeneric_NotCompleted_NotCompleted(bool outerCompletesFirst, TaskStatus innerStatus)
{
var innerTcs = new TaskCompletionSource();
Task inner = innerTcs.Task;
var outerTcs = new TaskCompletionSource();
Task outer = outerTcs.Task;
Task unwrappedInner = outer.Unwrap();
Assert.False(unwrappedInner.IsCompleted);
if (outerCompletesFirst)
{
outerTcs.SetResult(inner);
Assert.False(unwrappedInner.IsCompleted);
}
switch (innerStatus)
{
case TaskStatus.RanToCompletion:
innerTcs.SetResult(true);
break;
case TaskStatus.Faulted:
innerTcs.SetException(new InvalidOperationException());
break;
case TaskStatus.Canceled:
innerTcs.TrySetCanceled(CreateCanceledToken());
break;
}
if (!outerCompletesFirst)
{
Assert.False(unwrappedInner.IsCompleted);
outerTcs.SetResult(inner);
}
AssertTasksAreEqual(inner, unwrappedInner);
}
///
/// Tests Unwrap when neither the generic inner task nor the outer task has completed by the time Unwrap is called.
///
/// Whether the outer task or the inner task completes first.
/// How the inner task should be completed.
[Theory]
[InlineData(true, TaskStatus.RanToCompletion)]
[InlineData(true, TaskStatus.Canceled)]
[InlineData(true, TaskStatus.Faulted)]
[InlineData(false, TaskStatus.RanToCompletion)]
[InlineData(false, TaskStatus.Canceled)]
[InlineData(false, TaskStatus.Faulted)]
public void Generic_NotCompleted_NotCompleted(bool outerCompletesFirst, TaskStatus innerStatus)
{
var innerTcs = new TaskCompletionSource();
Task inner = innerTcs.Task;
var outerTcs = new TaskCompletionSource>();
Task> outer = outerTcs.Task;
Task unwrappedInner = outer.Unwrap();
Assert.False(unwrappedInner.IsCompleted);
if (outerCompletesFirst)
{
outerTcs.SetResult(inner);
Assert.False(unwrappedInner.IsCompleted);
}
switch (innerStatus)
{
case TaskStatus.RanToCompletion:
innerTcs.SetResult(42);
break;
case TaskStatus.Faulted:
innerTcs.SetException(new InvalidOperationException());
break;
case TaskStatus.Canceled:
innerTcs.TrySetCanceled(CreateCanceledToken());
break;
}
if (!outerCompletesFirst)
{
Assert.False(unwrappedInner.IsCompleted);
outerTcs.SetResult(inner);
}
AssertTasksAreEqual(inner, unwrappedInner);
}
///
/// Tests Unwrap when the outer task for a non-generic inner fails in some way.
///
/// Whether the outer task completes before Unwrap is called.
/// How the outer task should be completed (RanToCompletion means returning null).
[Theory]
[InlineData(true, TaskStatus.RanToCompletion)]
[InlineData(true, TaskStatus.Faulted)]
[InlineData(true, TaskStatus.Canceled)]
[InlineData(false, TaskStatus.RanToCompletion)]
[InlineData(false, TaskStatus.Faulted)]
[InlineData(false, TaskStatus.Canceled)]
public void NonGeneric_UnsuccessfulOuter(bool outerCompletesBeforeUnwrap, TaskStatus outerStatus)
{
var outerTcs = new TaskCompletionSource();
Task outer = outerTcs.Task;
Task unwrappedInner = null;
if (!outerCompletesBeforeUnwrap)
unwrappedInner = outer.Unwrap();
switch (outerStatus)
{
case TaskStatus.RanToCompletion:
outerTcs.SetResult(null);
break;
case TaskStatus.Canceled:
outerTcs.TrySetCanceled(CreateCanceledToken());
break;
case TaskStatus.Faulted:
outerTcs.SetException(new InvalidCastException());
break;
}
if (outerCompletesBeforeUnwrap)
unwrappedInner = outer.Unwrap();
WaitNoThrow(unwrappedInner);
switch (outerStatus)
{
case TaskStatus.RanToCompletion:
Assert.True(unwrappedInner.IsCanceled);
break;
default:
AssertTasksAreEqual(outer, unwrappedInner);
break;
}
}
///
/// Tests Unwrap when the outer task for a generic inner fails in some way.
///
/// Whether the outer task completes before Unwrap is called.
/// How the outer task should be completed (RanToCompletion means returning null).
[Theory]
[InlineData(true, TaskStatus.RanToCompletion)]
[InlineData(true, TaskStatus.Faulted)]
[InlineData(true, TaskStatus.Canceled)]
[InlineData(false, TaskStatus.RanToCompletion)]
[InlineData(false, TaskStatus.Faulted)]
[InlineData(false, TaskStatus.Canceled)]
public void Generic_UnsuccessfulOuter(bool outerCompletesBeforeUnwrap, TaskStatus outerStatus)
{
var outerTcs = new TaskCompletionSource>();
Task> outer = outerTcs.Task;
Task unwrappedInner = null;
if (!outerCompletesBeforeUnwrap)
unwrappedInner = outer.Unwrap();
switch (outerStatus)
{
case TaskStatus.RanToCompletion:
outerTcs.SetResult(null); // cancellation
break;
case TaskStatus.Canceled:
outerTcs.TrySetCanceled(CreateCanceledToken());
break;
case TaskStatus.Faulted:
outerTcs.SetException(new InvalidCastException());
break;
}
if (outerCompletesBeforeUnwrap)
unwrappedInner = outer.Unwrap();
WaitNoThrow(unwrappedInner);
switch (outerStatus)
{
case TaskStatus.RanToCompletion:
Assert.True(unwrappedInner.IsCanceled);
break;
default:
AssertTasksAreEqual(outer, unwrappedInner);
break;
}
}
///
/// Test Unwrap when the outer task for a non-generic inner task is marked as AttachedToParent.
///
[Fact]
public void NonGeneric_AttachedToParent()
{
Exception error = new InvalidTimeZoneException();
Task parent = Task.Factory.StartNew(() =>
{
var outerTcs = new TaskCompletionSource(TaskCreationOptions.AttachedToParent);
Task outer = outerTcs.Task;
Task inner = Task.FromException(error);
Task unwrappedInner = outer.Unwrap();
Assert.Equal(TaskCreationOptions.AttachedToParent, unwrappedInner.CreationOptions);
outerTcs.SetResult(inner);
AssertTasksAreEqual(inner, unwrappedInner);
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
WaitNoThrow(parent);
Assert.Equal(TaskStatus.Faulted, parent.Status);
Assert.Same(error, parent.Exception.Flatten().InnerException);
}
///
/// Test Unwrap when the outer task for a generic inner task is marked as AttachedToParent.
///
[Fact]
public void Generic_AttachedToParent()
{
Exception error = new InvalidTimeZoneException();
Task parent = Task.Factory.StartNew(() =>
{
var outerTcs = new TaskCompletionSource>(TaskCreationOptions.AttachedToParent);
Task> outer = outerTcs.Task;
Task