File: TestingAppHostEntryPointProbe.cs
Web Access
Project: src\tests\TestingAppHost1\TestingAppHost1.AppHost\TestingAppHost1.AppHost.csproj (TestingAppHost1.AppHost)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Concurrent;
 
public static class TestingAppHostEntryPointProbe
{
    private const string ArgumentPrefix = "--entry-point-exit-probe=";
    private const string BreakpointArgumentPrefix = "--entry-point-breakpoint-probe=";
    private static readonly ConcurrentDictionary<string, TaskCompletionSource> s_probes = new();
    private static readonly ConcurrentDictionary<string, BreakpointSignal> s_breakpoints = new();
 
    public static Probe Create()
    {
        var id = Guid.NewGuid().ToString("N");
        var exited = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        if (!s_probes.TryAdd(id, exited))
        {
            throw new InvalidOperationException($"Could not create entry-point probe '{id}'.");
        }
 
        return new Probe(id, exited.Task);
    }
 
    public static IDisposable Track(string[] args)
    {
        // The test passes the probe as:
        //   --entry-point-exit-probe=<32-character GUID>
        var argument = args.FirstOrDefault(arg => arg.StartsWith(ArgumentPrefix, StringComparison.Ordinal));
        if (argument is null)
        {
            return EmptyDisposable.Instance;
        }
 
        var id = argument[ArgumentPrefix.Length..];
        if (!s_probes.TryGetValue(id, out var exited))
        {
            throw new InvalidOperationException($"Entry-point probe '{id}' was not registered.");
        }
 
        return new ExitSignal(exited);
    }
 
    public static BreakpointProbe CreateBreakpoint()
    {
        var id = Guid.NewGuid().ToString("N");
        var signal = new BreakpointSignal(
            new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously),
            new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously));
        if (!s_breakpoints.TryAdd(id, signal))
        {
            throw new InvalidOperationException($"Could not create entry-point breakpoint probe '{id}'.");
        }
 
        return new BreakpointProbe(id, signal.Reached, signal.Continue);
    }
 
    public static async Task WaitAtBreakpointAsync(string[] args)
    {
        // The test passes the probe as:
        //   --entry-point-breakpoint-probe=<32-character GUID>
        var argument = args.FirstOrDefault(arg => arg.StartsWith(BreakpointArgumentPrefix, StringComparison.Ordinal));
        if (argument is null)
        {
            return;
        }
 
        var id = argument[BreakpointArgumentPrefix.Length..];
        if (!s_breakpoints.TryGetValue(id, out var signal))
        {
            throw new InvalidOperationException($"Entry-point breakpoint probe '{id}' was not registered.");
        }
 
        signal.Reached.TrySetResult();
        try
        {
            await signal.Continue.Task.ConfigureAwait(false);
        }
        finally
        {
            s_breakpoints.TryRemove(id, out _);
        }
    }
 
    public sealed class Probe(string id, Task exited) : IDisposable
    {
        public string Id { get; } = id;
 
        public Task Exited { get; } = exited;
 
        public void Dispose()
        {
            s_probes.TryRemove(Id, out _);
        }
    }
 
    public sealed class BreakpointProbe : IDisposable
    {
        private readonly TaskCompletionSource _continue;
 
        internal BreakpointProbe(string id, TaskCompletionSource reached, TaskCompletionSource @continue)
        {
            Id = id;
            _continue = @continue;
            Reached = reached.Task;
        }
 
        public string Id { get; }
 
        public Task Reached { get; }
 
        public void Continue()
        {
            _continue.TrySetResult();
        }
 
        public void Dispose()
        {
            Continue();
            if (Reached.IsCompleted)
            {
                s_breakpoints.TryRemove(Id, out _);
            }
        }
    }
 
    private sealed class ExitSignal(TaskCompletionSource exited) : IDisposable
    {
        public void Dispose()
        {
            exited.TrySetResult();
        }
    }
 
    private sealed record BreakpointSignal(TaskCompletionSource Reached, TaskCompletionSource Continue);
 
    private sealed class EmptyDisposable : IDisposable
    {
        public static EmptyDisposable Instance { get; } = new();
 
        public void Dispose()
        {
        }
    }
}