File: NativeDiagnosticChannelTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Native.Core.Tests\Aspire.Hosting.Native.Core.Tests.csproj (Aspire.Hosting.Native.Core.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting.Native.Diagnostics;
using Microsoft.Extensions.Time.Testing;
 
namespace Aspire.Hosting.Native.Core.Tests;
 
public class NativeDiagnosticChannelTests
{
    [Fact]
    public async Task SlowSubscribersOverflowIndependentlyAndDrainTheirRetainedPrefix()
    {
        using var source = new OperationEventSource();
        using var slow = source.Subscribe(capacity: 1);
        using var fast = source.Subscribe(capacity: 1);
        var first = Event(OperationOutcome.Started);
        var second = Event(OperationOutcome.Ok);
        source.Publish(first);
        Assert.Equal(first, await fast.Reader.ReadAsync(TestContext.Current.CancellationToken));
        source.Publish(second);
        Assert.Equal(second, await fast.Reader.ReadAsync(TestContext.Current.CancellationToken));
        Assert.Equal(first, await slow.Reader.ReadAsync(TestContext.Current.CancellationToken));
        await Assert.ThrowsAsync<DiagnosticOverflowException>(() => slow.Reader.Completion);
        source.Publish(first);
        Assert.Equal(first, await fast.Reader.ReadAsync(TestContext.Current.CancellationToken));
    }
 
    [Fact]
    public async Task DisposalCompletesOnlyThatSubscriberAndSourceDisposalCompletesTheRest()
    {
        using var source = new OperationEventSource();
        using var first = source.Subscribe();
        using var second = source.Subscribe();
        var operation = Event(OperationOutcome.Ok);
        source.Publish(operation);
        first.Dispose();
        Assert.Equal(operation, await first.Reader.ReadAsync(TestContext.Current.CancellationToken));
        await first.Reader.Completion;
        Assert.Equal(operation, await second.Reader.ReadAsync(TestContext.Current.CancellationToken));
        source.Dispose();
        await second.Reader.Completion;
        Assert.Throws<ObjectDisposedException>(() => source.Subscribe());
        source.Publish(operation);
    }
 
    [Fact]
    public async Task AwaitingAChannelDoesNotRunAConsumerOnTheProducerStack()
    {
        using var source = new OperationEventSource();
        using var subscription = source.Subscribe();
        var awaited = subscription.Reader.ReadAsync(TestContext.Current.CancellationToken).AsTask();
        var producerThread = Environment.CurrentManagedThreadId;
        var publishing = false;
        var inlineConsumer = false;
        var consumer = awaited.ContinueWith(_ =>
        {
            inlineConsumer = Volatile.Read(ref publishing) && Environment.CurrentManagedThreadId == producerThread;
        }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
        var operation = Event(OperationOutcome.Error);
        Volatile.Write(ref publishing, true);
        source.Publish(operation);
        Volatile.Write(ref publishing, false);
        Assert.Equal(operation, await awaited);
        await consumer;
        Assert.False(inlineConsumer);
        subscription.Dispose();
        await subscription.Reader.Completion;
    }
 
    private static OperationEvent Event(OperationOutcome outcome) => new(
        Guid.NewGuid(), "test.operation", outcome, null, null, null, null, null,
        new FakeTimeProvider().GetUtcNow(), 0, null);
}