< Summary - ZhEaIsNsAaBn.Libraries coverage (affected: 23 packages)

Information
Class: ZhEaIsNsAaBn.DbSeeder.Design.DesignTimeHostAbortedException
Assembly: ZhEaIsNsAaBn.DbSeeder.Design
File(s): /home/runner/work/ZhEaIsNsAaBn.Libraries.V1/ZhEaIsNsAaBn.Libraries.V1/src/DataBasePackages/ZhEaIsNsAaBn.DbSeeder.Design/DesignTime/DesignTimeHost.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 1
Coverable lines: 1
Total lines: 151
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%210%

File(s)

/home/runner/work/ZhEaIsNsAaBn.Libraries.V1/ZhEaIsNsAaBn.Libraries.V1/src/DataBasePackages/ZhEaIsNsAaBn.DbSeeder.Design/DesignTime/DesignTimeHost.cs

#LineLine coverage
 1#region Usings
 2using System.Diagnostics;
 3using System.Reflection;
 4#endregion
 5
 6namespace ZhEaIsNsAaBn.DbSeeder.Design;
 7
 8/// <summary>
 9/// The app's host, the way <c>dotnet ef</c> gets it: run the entry point on its own thread, catch the host when
 10/// <c>Microsoft.Extensions.Hosting</c> reports <c>HostBuilt</c> (generic host, <c>Host.CreateApplicationBuilder</c>,
 11/// <c>WebApplication</c>), then abort the entry point so the app never starts. Hosted services don't run.
 12/// </summary>
 13/// <remarks>Microsoft's <c>HostFactoryResolver.Sources</c> isn't on nuget.org, so this is the same mechanism, written h
 14internal sealed class DesignTimeHost : IAsyncDisposable
 15{
 16    private const string HostingListener = "Microsoft.Extensions.Hosting";
 17    private const string HostBuiltEvent  = "HostBuilt";
 18
 19    private readonly object _host;
 20
 21
 22    private DesignTimeHost( object host, IServiceProvider services )
 23    {
 24        _host    = host;
 25        Services = services;
 26    }
 27
 28
 29    public IServiceProvider Services { get; }
 30
 31
 32    /// <summary>The host, <see langword="null"/> without an entry point or when the entry point ends without building o
 33    /// <exception cref="InvalidOperationException">The entry point failed, or no host was built within <paramref name="
 34    public static async Task<DesignTimeHost?> BuildAsync( Assembly assembly, string[] args, TimeSpan timeout )
 35    {
 36        if ( assembly.EntryPoint is not { } entryPoint ) { return null; }
 37
 38
 39        var built = new TaskCompletionSource<object?>( TaskCreationOptions.RunContinuationsAsynchronously );
 40
 41        using var subscription = DiagnosticListener.AllListeners.Subscribe( new ListenerObserver( built ) );
 42
 43        var thread = new Thread( () =>
 44        {
 45            try
 46            {
 47                entryPoint.Invoke( null, entryPoint.GetParameters().Length == 0 ? null : [ args ] );
 48                built.TrySetResult( null );   // ended without a host (or the host was already caught: first result wins
 49            }
 50            catch ( TargetInvocationException exception ) when ( built.Task.IsCompleted || IsAbort( exception.InnerExcep
 51            {
 52                // our own abort, after HostBuilt
 53            }
 54            catch ( TargetInvocationException exception ) { built.TrySetException( exception.InnerException ?? exception
 55        } )
 56        {
 57            IsBackground = true,
 58            Name         = "dbseeder: app entry point"
 59        };
 60
 61        thread.Start();
 62
 63        if ( await Task.WhenAny( built.Task, Task.Delay( timeout ) ).ConfigureAwait( false ) != built.Task )
 64        {
 65            throw new InvalidOperationException( $"The app built no host within {timeout.TotalSeconds:0} s." );
 66        }
 67
 68        object? host;
 69
 70        try { host = await built.Task.ConfigureAwait( false ); }
 71        catch ( Exception exception ) when ( exception is not OperationCanceledException )
 72        {
 73            throw new InvalidOperationException( exception.Message, exception );
 74        }
 75
 76        if ( host is null ) { return null; }
 77
 78
 79        var services = host.GetType().GetInterfaces().FirstOrDefault( i => i.FullName == "Microsoft.Extensions.Hosting.I
 80                           as IServiceProvider
 81                    ?? throw new InvalidOperationException( $"The host ({host.GetType().FullName}) has no services." );
 82
 83        return new DesignTimeHost( host, services );
 84    }
 85
 86    public async ValueTask DisposeAsync()
 87    {
 88        switch ( _host )
 89        {
 90            case IAsyncDisposable asyncDisposable: await asyncDisposable.DisposeAsync().ConfigureAwait( false ); break;
 91            case IDisposable disposable:           disposable.Dispose(); break;
 92        }
 93    }
 94
 95
 96    /// Hosting's own HostAbortedException when it's there (apps' catch-all logging skips it), else ours.
 97    private static Exception Abort()
 98        => Type.GetType( "Microsoft.Extensions.Hosting.HostAbortedException, Microsoft.Extensions.Hosting.Abstractions",
 99           && Activator.CreateInstance( type ) is Exception hosting
 100               ? hosting
 101               : new DesignTimeHostAbortedException();
 102
 103    private static bool IsAbort( Exception? exception )
 104        => exception is DesignTimeHostAbortedException || exception?.GetType().FullName == "Microsoft.Extensions.Hosting
 105
 106
 107    private sealed class ListenerObserver : IObserver<DiagnosticListener>
 108    {
 109        private readonly TaskCompletionSource<object?> _built;
 110
 111
 112        public ListenerObserver( TaskCompletionSource<object?> built ) => _built = built;
 113
 114
 115        public void OnNext( DiagnosticListener value )
 116        {
 117            if ( value.Name == HostingListener ) { value.Subscribe( new EventObserver( _built ) ); }
 118        }
 119
 120        public void OnCompleted() { }
 121
 122        public void OnError( Exception error ) { }
 123    }
 124
 125    private sealed class EventObserver : IObserver<KeyValuePair<string, object?>>
 126    {
 127        private readonly TaskCompletionSource<object?> _built;
 128
 129
 130        public EventObserver( TaskCompletionSource<object?> built ) => _built = built;
 131
 132
 133        public void OnNext( KeyValuePair<string, object?> value )
 134        {
 135            if ( value.Key != HostBuiltEvent || !_built.TrySetResult( value.Value ) ) { return; }
 136
 137            // Stop the entry point before it runs the app (thrown out of the app's Build()).
 138            throw Abort();
 139        }
 140
 141        public void OnCompleted() { }
 142
 143        public void OnError( Exception error ) { }
 144    }
 145}
 146
 147/// Thrown out of the app's <c>Build()</c> when Hosting has no <c>HostAbortedException</c>.
 148internal sealed class DesignTimeHostAbortedException : Exception
 149{
 0150    public DesignTimeHostAbortedException() : base( "dotnet dbseeder stopped the app after its host was built." ) { }
 151}

Methods/Properties

.ctor()