File: UtilityTest\SpecializedTasksTests.cs
Web Access
Project: src\src\Workspaces\CoreTest\Microsoft.CodeAnalysis.Workspaces.UnitTests.csproj (Microsoft.CodeAnalysis.Workspaces.UnitTests)
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
using Xunit;
 
#pragma warning disable IDE0039 // Use local function
 
namespace Microsoft.CodeAnalysis.UnitTests;
 
[SuppressMessage("Usage", "VSTHRD104:Offer async methods", Justification = "This class tests specific behavior of tasks.")]
public sealed class SpecializedTasksTests
{
    private sealed record StateType;
    private sealed record IntermediateType;
    private sealed record ResultType;
 
    [Fact]
    public void WhenAll_Null()
    {
#pragma warning disable CA2012 // Use ValueTasks correctly (the instance is never created)
        Assert.Throws<ArgumentNullException>(() => SpecializedTasks.WhenAll<int>((IEnumerable<ValueTask<int>>)null!));
#pragma warning restore CA2012 // Use ValueTasks correctly
    }
 
    [Fact]
    public void WhenAll_Empty()
    {
        var whenAll = SpecializedTasks.WhenAll(SpecializedCollections.EmptyEnumerable<ValueTask<int>>());
        Debug.Assert(whenAll.IsCompleted);
        Assert.True(whenAll.IsCompletedSuccessfully);
        Assert.Same(Array.Empty<int>(), whenAll.Result);
    }
 
    [Fact]
    public void WhenAll_AllCompletedSuccessfully()
    {
        var whenAll = SpecializedTasks.WhenAll([new ValueTask<int>(0), new ValueTask<int>(1)]);
        Debug.Assert(whenAll.IsCompleted);
        Assert.True(whenAll.IsCompletedSuccessfully);
        Assert.Equal((int[])[0, 1], whenAll.Result);
    }
 
    [Fact]
    public async Task WhenAll_CompletedButCanceled()
    {
        var whenAll = SpecializedTasks.WhenAll([new ValueTask<int>(Task.FromCanceled<int>(new CancellationToken(true)))]);
        Assert.True(whenAll.IsCompleted);
        Assert.False(whenAll.IsCompletedSuccessfully);
        await Assert.ThrowsAsync<TaskCanceledException>(async () => await whenAll);
    }
 
    [Fact]
    public void WhenAll_NotYetCompleted()
    {
        var completionSource = new TaskCompletionSource<int>();
        var whenAll = SpecializedTasks.WhenAll([new ValueTask<int>(completionSource.Task)]);
        Assert.False(whenAll.IsCompleted);
        completionSource.SetResult(0);
        Assert.True(whenAll.IsCompleted);
        Debug.Assert(whenAll.IsCompleted);
        Assert.Equal((int[])[0], whenAll.Result);
    }
 
    [Fact]
    public void Transform_ArgumentValidation()
    {
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = (_, _) => new(new IntermediateType());
        Func<IntermediateType, StateType, ResultType> transform = (_, _) => new();
        var arg = new StateType();
        var cancellationToken = new CancellationToken(canceled: false);
 
#pragma warning disable CA2012 // Use ValueTasks correctly (the instance is never created)
        Assert.Throws<ArgumentNullException>("func", () => SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(null!, transform, arg, cancellationToken));
        Assert.Throws<ArgumentNullException>("transform", () => SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync<StateType, IntermediateType, ResultType>(func, null!, arg, cancellationToken));
#pragma warning restore CA2012 // Use ValueTasks correctly
    }
 
    [Fact]
    public void Transform_SyncCompletedFunction_CompletedTransform()
    {
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = (_, _) => new(new IntermediateType());
        Func<IntermediateType, StateType, ResultType> transform = (_, _) => new();
        var arg = new StateType();
        var cancellationToken = new CancellationToken(canceled: false);
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.True(task.IsCompletedSuccessfully);
        Assert.NotNull(task.Result);
    }
 
    [Fact]
    public void Transform_SyncCompletedFunction_CancellationRequested_IgnoresTransform()
    {
        using var cts = new CancellationTokenSource();
        cts.Cancel();
 
        var executedTransform = false;
 
        var cancellationToken = cts.Token;
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = (_, _) => new(new IntermediateType());
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.True(task.IsCanceled);
        var exception = Assert.Throws<TaskCanceledException>(() => task.Result);
        Assert.Equal(cancellationToken, exception.CancellationToken);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public async Task Transform_AsyncCompletedFunction_CompletedTransform()
    {
        var gate = new ManualResetEventSlim();
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = async (_, _) =>
        {
            await Task.Yield();
            gate.Wait(CancellationToken.None);
            return new IntermediateType();
        };
        Func<IntermediateType, StateType, ResultType> transform = (_, _) => new();
        var arg = new StateType();
        var cancellationToken = new CancellationToken(canceled: false);
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.False(task.IsCompleted);
 
        gate.Set();
        Assert.NotNull(await task);
    }
 
    [Fact]
    public async Task Transform_AsyncCompletedFunction_CancellationRequested_IgnoresTransform()
    {
        using var cts = new CancellationTokenSource();
        cts.Cancel();
 
        var executedTransform = false;
 
        var cancellationToken = cts.Token;
        var gate = new ManualResetEventSlim();
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = async (_, _) =>
        {
            await Task.Yield();
            gate.Wait(CancellationToken.None);
            return new IntermediateType();
        };
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.False(task.IsCompleted);
 
        gate.Set();
        var exception = await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
        Assert.Equal(cancellationToken, exception.CancellationToken);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public void Transform_SyncCanceledFunction_IgnoresTransform()
    {
        using var cts = new CancellationTokenSource();
        cts.Cancel();
 
        var executedTransform = false;
 
        var cancellationToken = cts.Token;
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = (_, _) => new(Task.FromCanceled<IntermediateType>(cancellationToken));
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.True(task.IsCanceled);
        var exception = Assert.Throws<TaskCanceledException>(() => task.Result);
        Assert.Equal(cancellationToken, exception.CancellationToken);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public async Task Transform_AsyncCanceledFunction_IgnoresTransform()
    {
        using var cts = new CancellationTokenSource();
        cts.Cancel();
 
        var executedTransform = false;
 
        var cancellationToken = cts.Token;
        var gate = new ManualResetEventSlim();
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = async (_, _) =>
        {
            await Task.Yield();
            gate.Wait(CancellationToken.None);
            cts.Token.ThrowIfCancellationRequested();
            throw ExceptionUtilities.Unreachable();
        };
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.False(task.IsCompleted);
 
        gate.Set();
        var exception = await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
        Assert.Equal(cancellationToken, exception.CancellationToken);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public void Transform_SyncCanceledFunction_NotRequested_IgnoresTransform()
    {
        using var unexpectedCts = new CancellationTokenSource();
        unexpectedCts.Cancel();
 
        var executedTransform = false;
 
        var cancellationToken = new CancellationToken(canceled: false);
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = (_, _) => new(Task.FromCanceled<IntermediateType>(unexpectedCts.Token));
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.True(task.IsCanceled);
        var exception = Assert.Throws<TaskCanceledException>(() => task.Result);
 
        // ⚠ Due to the way cancellation is handled in ContinueWith, the resulting exception fails to preserve the
        // cancellation token applied when the intermediate task was cancelled.
        Assert.Equal(cancellationToken, exception.CancellationToken);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public async Task Transform_AsyncCanceledFunction_NotRequested_IgnoresTransform()
    {
        using var unexpectedCts = new CancellationTokenSource();
        unexpectedCts.Cancel();
 
        var executedTransform = false;
 
        var cancellationToken = new CancellationToken(canceled: false);
        var gate = new ManualResetEventSlim();
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = async (_, _) =>
        {
            await Task.Yield();
            gate.Wait(CancellationToken.None);
            unexpectedCts.Token.ThrowIfCancellationRequested();
            throw ExceptionUtilities.Unreachable();
        };
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.False(task.IsCompleted);
 
        gate.Set();
        var exception = await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
        Assert.True(task.IsCanceled);
 
        // ⚠ Due to the way cancellation is handled in ContinueWith, the resulting exception fails to preserve the
        // cancellation token applied when the intermediate task was cancelled.
        Assert.Equal(cancellationToken, exception.CancellationToken);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public void Transform_SyncCanceledFunction_MismatchToken_IgnoresTransform()
    {
        using var cts = new CancellationTokenSource();
        cts.Cancel();
 
        using var unexpectedCts = new CancellationTokenSource();
        unexpectedCts.Cancel();
 
        var executedTransform = false;
 
        var cancellationToken = cts.Token;
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = (_, _) => new(Task.FromCanceled<IntermediateType>(unexpectedCts.Token));
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.True(task.IsCanceled);
        var exception = Assert.Throws<TaskCanceledException>(() => task.Result);
        Assert.Equal(cancellationToken, exception.CancellationToken);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public async Task Transform_AsyncCanceledFunction_MismatchToken_IgnoresTransform()
    {
        using var cts = new CancellationTokenSource();
        cts.Cancel();
 
        using var unexpectedCts = new CancellationTokenSource();
        unexpectedCts.Cancel();
 
        var executedTransform = false;
 
        var cancellationToken = cts.Token;
        var gate = new ManualResetEventSlim();
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = async (_, _) =>
        {
            await Task.Yield();
            gate.Wait(CancellationToken.None);
            unexpectedCts.Token.ThrowIfCancellationRequested();
            throw ExceptionUtilities.Unreachable();
        };
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.False(task.IsCompleted);
 
        gate.Set();
        var exception = await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
        Assert.True(task.IsCanceled);
        Assert.Equal(cancellationToken, exception.CancellationToken);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public void Transform_SyncDirectFaultedFunction_IgnoresTransform()
    {
        var executedTransform = false;
 
        var fault = ExceptionUtilities.Unreachable();
        var cancellationToken = new CancellationToken(canceled: false);
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = (_, _) => throw fault;
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
#pragma warning disable CA2012 // Use ValueTasks correctly (the instance is never created)
        var exception = Assert.Throws<InvalidOperationException>(() => SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken));
#pragma warning restore CA2012 // Use ValueTasks correctly
        Assert.Same(fault, exception);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public void Transform_SyncFaultedFunction_IgnoresTransform()
    {
        var executedTransform = false;
 
        var fault = ExceptionUtilities.Unreachable();
        var cancellationToken = new CancellationToken(canceled: false);
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = (_, _) => new(Task.FromException<IntermediateType>(fault));
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.True(task.IsFaulted);
        var exception = Assert.Throws<InvalidOperationException>(() => task.Result);
        Assert.Same(fault, exception);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public async Task Transform_AsyncFaultedFunction_IgnoresTransform()
    {
        var executedTransform = false;
 
        var fault = ExceptionUtilities.Unreachable();
        var cancellationToken = new CancellationToken(canceled: false);
        var gate = new ManualResetEventSlim();
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = async (_, _) =>
        {
            await Task.Yield();
            gate.Wait(CancellationToken.None);
            throw fault;
        };
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.False(task.IsCompleted);
 
        gate.Set();
        var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => task.AsTask());
        Assert.Same(fault, exception);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public void Transform_SyncDirectFaultedFunction_CancellationRequested_IgnoresTransform()
    {
        using var cts = new CancellationTokenSource();
        cts.Cancel();
 
        var executedTransform = false;
 
        var fault = ExceptionUtilities.Unreachable();
        var cancellationToken = cts.Token;
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = (_, _) => throw fault;
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
#pragma warning disable CA2012 // Use ValueTasks correctly (the instance is never created)
        var exception = Assert.Throws<InvalidOperationException>(() => SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken));
#pragma warning restore CA2012 // Use ValueTasks correctly
        Assert.Same(fault, exception);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public void Transform_SyncFaultedFunction_CancellationRequested_IgnoresTransform()
    {
        using var cts = new CancellationTokenSource();
        cts.Cancel();
 
        var executedTransform = false;
 
        var fault = ExceptionUtilities.Unreachable();
        var cancellationToken = cts.Token;
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = (_, _) => new(Task.FromException<IntermediateType>(fault));
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.True(task.IsCanceled);
        var exception = Assert.Throws<TaskCanceledException>(() => task.Result);
        Assert.Equal(cancellationToken, exception.CancellationToken);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public async Task Transform_AsyncFaultedFunction_CancellationRequested_IgnoresTransform()
    {
        using var cts = new CancellationTokenSource();
        cts.Cancel();
 
        var executedTransform = false;
 
        var fault = ExceptionUtilities.Unreachable();
        var cancellationToken = cts.Token;
        var gate = new ManualResetEventSlim();
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = async (_, _) =>
        {
            await Task.Yield();
            gate.Wait(CancellationToken.None);
            throw fault;
        };
        Func<IntermediateType, StateType, ResultType> transform = (_, _) =>
        {
            executedTransform = true;
            return new ResultType();
        };
        var arg = new StateType();
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.False(task.IsCompleted);
 
        gate.Set();
        var exception = await Assert.ThrowsAsync<TaskCanceledException>(() => task.AsTask());
        Assert.True(task.IsCanceled);
        Assert.Equal(cancellationToken, exception.CancellationToken);
        Assert.False(executedTransform);
    }
 
    [Fact]
    public void Transform_SyncCompletedFunction_FaultedTransform()
    {
        var fault = ExceptionUtilities.Unreachable();
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = (_, _) => new(new IntermediateType());
        Func<IntermediateType, StateType, ResultType> transform = (_, _) => throw fault;
        var arg = new StateType();
        var cancellationToken = new CancellationToken(canceled: false);
 
#pragma warning disable CA2012 // Use ValueTasks correctly (the instance is never created)
        var exception = Assert.Throws<InvalidOperationException>(() => SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken));
#pragma warning restore CA2012 // Use ValueTasks correctly
        Assert.Same(fault, exception);
    }
 
    [Fact]
    public async Task Transform_AsyncCompletedFunction_FaultedTransform()
    {
        var fault = ExceptionUtilities.Unreachable();
        var gate = new ManualResetEventSlim();
        Func<StateType, CancellationToken, ValueTask<IntermediateType>> func = async (_, _) =>
        {
            await Task.Yield();
            gate.Wait(CancellationToken.None);
            return new IntermediateType();
        };
        Func<IntermediateType, StateType, ResultType> transform = (_, _) => throw fault;
        var arg = new StateType();
        var cancellationToken = new CancellationToken(canceled: false);
 
        var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve();
        Assert.False(task.IsCompleted);
 
        gate.Set();
        var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => task.AsTask());
        Assert.Same(fault, exception);
    }
}