< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.KestrunHostManager
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/KestrunHostManager.cs
Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff
Line coverage
80%
Covered lines: 76
Uncovered lines: 18
Coverable lines: 94
Total lines: 354
Line coverage: 80.8%
Branch coverage
80%
Covered branches: 56
Total branches: 70
Branch coverage: 80%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 08/26/2025 - 14:53:17 Line coverage: 82% (64/78) Branch coverage: 75.8% (47/62) Total lines: 303 Tag: Kestrun/Kestrun@78d1e497d8ba989d121b57aa39aa3c6b22de743109/03/2025 - 13:55:51 Line coverage: 81.8% (63/77) Branch coverage: 75.8% (47/62) Total lines: 298 Tag: Kestrun/Kestrun@80ce2a54be2f719c7be1c21a92a8156bfdc48eb409/06/2025 - 18:30:33 Line coverage: 85.3% (70/82) Branch coverage: 80% (56/70) Total lines: 321 Tag: Kestrun/Kestrun@aeddbedb8a96e9137aac94c2d5edd011b57ac87109/16/2025 - 04:01:29 Line coverage: 85.3% (70/82) Branch coverage: 80% (56/70) Total lines: 322 Tag: Kestrun/Kestrun@e5263347b0baba68d9fd62ffbf60a7dd87f994bb10/13/2025 - 16:52:37 Line coverage: 86.2% (75/87) Branch coverage: 80% (56/70) Total lines: 341 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e11/26/2025 - 04:09:52 Line coverage: 86.2% (75/87) Branch coverage: 80% (56/70) Total lines: 339 Tag: Kestrun/Kestrun@783d423774fc9827a03321203e642119023bb30012/18/2025 - 21:41:58 Line coverage: 80.8% (76/94) Branch coverage: 80% (56/70) Total lines: 354 Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff 08/26/2025 - 14:53:17 Line coverage: 82% (64/78) Branch coverage: 75.8% (47/62) Total lines: 303 Tag: Kestrun/Kestrun@78d1e497d8ba989d121b57aa39aa3c6b22de743109/03/2025 - 13:55:51 Line coverage: 81.8% (63/77) Branch coverage: 75.8% (47/62) Total lines: 298 Tag: Kestrun/Kestrun@80ce2a54be2f719c7be1c21a92a8156bfdc48eb409/06/2025 - 18:30:33 Line coverage: 85.3% (70/82) Branch coverage: 80% (56/70) Total lines: 321 Tag: Kestrun/Kestrun@aeddbedb8a96e9137aac94c2d5edd011b57ac87109/16/2025 - 04:01:29 Line coverage: 85.3% (70/82) Branch coverage: 80% (56/70) Total lines: 322 Tag: Kestrun/Kestrun@e5263347b0baba68d9fd62ffbf60a7dd87f994bb10/13/2025 - 16:52:37 Line coverage: 86.2% (75/87) Branch coverage: 80% (56/70) Total lines: 341 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e11/26/2025 - 04:09:52 Line coverage: 86.2% (75/87) Branch coverage: 80% (56/70) Total lines: 339 Tag: Kestrun/Kestrun@783d423774fc9827a03321203e642119023bb30012/18/2025 - 21:41:58 Line coverage: 80.8% (76/94) Branch coverage: 80% (56/70) Total lines: 354 Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
get_InstanceNames()100%11100%
get_KestrunRoot()100%11100%
set_KestrunRoot(...)100%6453.84%
Create(...)87.5%8890%
Create(...)100%210%
Create(...)70%101086.66%
Contains(...)100%11100%
TryGet(...)100%11100%
Get(...)100%22100%
get_Default()100%44100%
SetDefault(...)100%22100%
IsRunning(...)50%44100%
StartAsync()50%11650%
StopAsync()83.33%6687.5%
Stop(...)50%2266.66%
StopAllAsync()100%44100%
Destroy(...)78.57%151485.71%
DestroyAll()100%44100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/KestrunHostManager.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using Kestrun.Hosting;
 3using Serilog;
 4using Serilog.Events;
 5
 6namespace Kestrun;
 7
 8/// <summary>
 9/// Provides management functionality for KestrunHost instances, including creation, retrieval, starting, stopping, and 
 10/// </summary>
 11public static class KestrunHostManager
 12{
 13    /// <summary>
 14    /// Holds the collection of KestrunHost instances, keyed by their names.
 15    /// </summary>
 116    private static readonly ConcurrentDictionary<string, KestrunHost> _instances = new(StringComparer.OrdinalIgnoreCase)
 17
 18    /// <summary>
 19    /// The name of the default KestrunHost instance, if one has been set.
 20    /// </summary>
 21    private static string? _defaultName;
 22
 23    /// <summary>
 24    /// The root path for Kestrun operations.
 25    /// </summary>
 26    private static string? _kestrunRoot;
 27
 28    /// <summary>
 29    /// Gets the collection of names for all KestrunHost instances.
 30    /// </summary>
 331    public static IReadOnlyCollection<string> InstanceNames => (IReadOnlyCollection<string>)_instances.Keys;
 32
 33    /// <summary>
 34    /// Gets or sets the root path for Kestrun operations.
 35    /// </summary>
 36    public static string? KestrunRoot
 37    {
 438        get => _kestrunRoot;
 39        set
 40        {
 2141            if (string.IsNullOrWhiteSpace(value))
 42            {
 143                throw new ArgumentException("Kestrun root path cannot be null or empty.", nameof(value));
 44            }
 45
 46            // NOTE:
 47            // On Unix/macOS, Directory.GetCurrentDirectory() can throw if the process CWD was deleted.
 48            // We still want to allow setting KestrunRoot (and attempt to set CWD) in that scenario.
 49            try
 50            {
 2051                if (Directory.GetCurrentDirectory() != value)
 52                {
 253                    Directory.SetCurrentDirectory(value);
 54                }
 2055            }
 056            catch (Exception ex) when (ex is IOException
 057                                       or UnauthorizedAccessException
 058                                       or DirectoryNotFoundException
 059                                       or FileNotFoundException)
 60            {
 61                // If the current working directory is missing/unavailable (common on Unix when deleted),
 62                // we can still attempt to set it to the provided root.
 063                Directory.SetCurrentDirectory(value);
 064            }
 2065            _kestrunRoot = value;
 2066        }
 67    }
 68
 69    /// <summary>
 70    /// Creates a new KestrunHost instance using the provided factory function.
 71    /// </summary>
 72    /// <param name="name">The name of the KestrunHost instance to create.</param>
 73    /// <param name="factory">A factory function that returns a new KestrunHost instance.</param>
 74    /// <param name="setAsDefault">Whether to set this instance as the default.</param>
 75    /// <param name="enablePowershellMiddleware">Whether to enable PowerShell middleware for this instance.</param>
 76    /// <returns>The created KestrunHost instance.</returns>
 77    public static KestrunHost Create(string name, Func<KestrunHost> factory, bool setAsDefault = false, bool enablePower
 78    {
 2179        if (string.IsNullOrWhiteSpace(name))
 80        {
 081            throw new ArgumentException("Instance name is required.", nameof(name));
 82        }
 83
 2184        if (_instances.ContainsKey(name))
 85        {
 286            throw new InvalidOperationException($"A KestrunHost instance with the name '{name}' already exists.");
 87        }
 88
 1989        var host = factory();
 1990        host.PowershellMiddlewareEnabled = enablePowershellMiddleware;
 1991        _instances[name] = host;
 92
 1993        if (setAsDefault || _defaultName == null)
 94        {
 1295            _defaultName = name;
 96        }
 97
 1998        return host;
 99    }
 100
 101    /// <summary>
 102    /// Creates a new KestrunHost instance with the specified name and optional module paths, using the default logger.
 103    /// </summary>
 104    /// <param name="name">The name of the KestrunHost instance to create.</param>
 105    /// <param name="modulePathsObj">Optional array of module paths to load.</param>
 106    /// <param name="setAsDefault">Whether to set this instance as the default.</param>
 107    /// <param name="enablePowershellMiddleware">Whether to enable PowerShell middleware for this instance.</param>
 108    /// <returns>The created KestrunHost instance.</returns>
 109    public static KestrunHost Create(string name,
 110         string[]? modulePathsObj = null, bool setAsDefault = false, bool enablePowershellMiddleware = false) =>
 0111         Create(name, Log.Logger, modulePathsObj, setAsDefault, enablePowershellMiddleware);
 112
 113    /// <summary>
 114    /// Creates a new KestrunHost instance with the specified name, logger, root path, and optional module paths.
 115    /// </summary>
 116    /// <param name="name">The name of the KestrunHost instance to create.</param>
 117    /// <param name="logger">The Serilog logger to use for the host.</param>
 118    /// <param name="modulePathsObj">Optional array of module paths to load.</param>
 119    /// <param name="setAsDefault">Whether to set this instance as the default.</param>
 120    /// <param name="enablePowershellMiddleware">Whether to enable PowerShell middleware for this instance.</param>
 121    /// <returns>The created KestrunHost instance.</returns>
 122    public static KestrunHost Create(string name, Serilog.ILogger logger,
 123         string[]? modulePathsObj = null, bool setAsDefault = false, bool enablePowershellMiddleware = false)
 124    {
 2125        if (string.IsNullOrWhiteSpace(name))
 126        {
 0127            throw new ArgumentException("Instance name is required.", nameof(name));
 128        }
 129
 2130        if (_instances.ContainsKey(name))
 131        {
 0132            throw new InvalidOperationException($"A KestrunHost instance with the name '{name}' already exists.");
 133        }
 134
 2135        if (KestrunRoot is null)
 136        {
 1137            throw new InvalidOperationException("Kestrun root path must be set before creating a KestrunHost instance.")
 138        }
 139
 1140        var host = new KestrunHost(name, logger, KestrunRoot, modulePathsObj)
 1141        {
 1142            PowershellMiddlewareEnabled = enablePowershellMiddleware,
 1143            DefaultHost = setAsDefault
 1144        };
 145
 1146        _instances[name] = host;
 147
 1148        if (setAsDefault || _defaultName == null)
 149        {
 1150            _defaultName = name;
 151        }
 152
 1153        return host;
 154    }
 155
 156    /// <summary>
 157    /// Determines whether a KestrunHost instance with the specified name exists.
 158    /// </summary>
 159    /// <param name="name">The name of the KestrunHost instance to check for existence.</param>
 160    /// <returns>True if an instance with the specified name exists; otherwise, false.</returns>
 4161    public static bool Contains(string name) => _instances.ContainsKey(name);
 162
 163    /// <summary>
 164    /// Attempts to retrieve a KestrunHost instance by its name.
 165    /// </summary>
 166    /// <param name="name">The name of the KestrunHost instance to retrieve.</param>
 167    /// <param name="host">When this method returns, contains the KestrunHost instance associated with the specified nam
 168    /// <returns>True if the instance was found; otherwise, false.</returns>
 169    public static bool TryGet(string name, out KestrunHost? host) =>
 8170        _instances.TryGetValue(name, out host);
 171
 172    /// <summary>
 173    /// Retrieves a KestrunHost instance by its name.
 174    /// </summary>
 175    /// <param name="name">The name of the KestrunHost instance to retrieve.</param>
 176    /// <returns>The KestrunHost instance if found; otherwise, null.</returns>
 177    public static KestrunHost? Get(string name) =>
 4178        _instances.TryGetValue(name, out var host) ? host : null;
 179
 180    /// <summary>
 181    /// Gets the default KestrunHost instance, if one has been set.
 182    /// </summary>
 7183    public static KestrunHost? Default => _defaultName != null && _instances.TryGetValue(_defaultName, out var host) ? h
 184
 185    /// <summary>
 186    /// Sets the specified KestrunHost instance as the default.
 187    /// </summary>
 188    /// <param name="name">The name of the KestrunHost instance to set as default.</param>
 189    /// <exception cref="InvalidOperationException">Thrown if no instance with the specified name exists.</exception>
 190    public static void SetDefault(string name)
 191    {
 4192        if (!_instances.ContainsKey(name))
 193        {
 2194            throw new InvalidOperationException($"No KestrunHost instance named '{name}' exists.");
 195        }
 196
 2197        _defaultName = name;
 2198    }
 199
 200    /// <summary>
 201    /// Determines whether the specified KestrunHost instance is currently running.
 202    /// </summary>
 203    /// <param name="name">The name of the KestrunHost instance to check.</param>
 204    /// <returns>True if the instance is running; otherwise, false.</returns>
 1205    public static bool IsRunning(string name) => TryGet(name, out var host) && host != null && host.IsRunning;
 206
 207    /// <summary>
 208    /// Starts the specified KestrunHost instance asynchronously.
 209    /// </summary>
 210    /// <param name="name">The name of the KestrunHost instance to start.</param>
 211    /// <param name="ct">A cancellation token to observe while waiting for the task to complete.</param>
 212    public static async Task StartAsync(string name, CancellationToken ct = default)
 213    {
 1214        if (Log.IsEnabled(LogEventLevel.Debug))
 215        {
 1216            Log.Debug("Starting (Async) KestrunHost instance '{Name}'", name);
 217        }
 218
 1219        if (TryGet(name, out var host))
 220        {
 0221            if (host is not null)
 222            {
 0223                await host.StartAsync(ct);
 224            }
 225            else
 226            {
 0227                throw new InvalidOperationException($"KestrunHost instance '{name}' is null.");
 228            }
 229        }
 230        else
 231        {
 1232            throw new InvalidOperationException($"No KestrunHost instance named '{name}' exists.");
 233        }
 0234    }
 235
 236    /// <summary>
 237    /// Stops the specified KestrunHost instance asynchronously.
 238    /// </summary>
 239    /// <param name="name">The name of the KestrunHost instance to stop.</param>
 240    /// <param name="ct">A cancellation token to observe while waiting for the task to complete.</param>
 241    public static async Task StopAsync(string name, CancellationToken ct = default)
 242    {
 2243        if (Log.IsEnabled(LogEventLevel.Debug))
 244        {
 2245            Log.Debug("Stopping (Async) KestrunHost instance '{Name}'", name);
 246        }
 247
 2248        if (TryGet(name, out var host))
 249        {
 1250            if (host is not null)
 251            {
 1252                await host.StopAsync(ct);
 253            }
 254            else
 255            {
 0256                throw new InvalidOperationException($"KestrunHost instance '{name}' is null.");
 257            }
 258        }
 259        else
 260        {
 1261            throw new InvalidOperationException($"No KestrunHost instance named '{name}' exists.");
 262        }
 1263    }
 264
 265    /// <summary>
 266    /// Stops the specified KestrunHost instance synchronously.
 267    /// </summary>
 268    /// <param name="name">The name of the KestrunHost instance to stop.</param>
 269    public static void Stop(string name)
 270    {
 2271        if (_instances.TryGetValue(name, out var host))
 272        {
 0273            host.Stop();
 274        }
 2275    }
 276
 277    /// <summary>
 278    /// Stops all KestrunHost instances asynchronously.
 279    /// </summary>
 280    /// <param name="ct">A cancellation token to observe while waiting for the tasks to complete.</param>
 281    public static async Task StopAllAsync(CancellationToken ct = default)
 282    {
 2283        if (Log.IsEnabled(LogEventLevel.Debug))
 284        {
 2285            Log.Debug("Stopping all KestrunHost instances (Async)");
 286        }
 287
 12288        foreach (var kv in _instances)
 289        {
 4290            await kv.Value.StopAsync(ct);
 291        }
 2292    }
 293
 294    /// <summary>
 295    /// Destroys the specified KestrunHost instance and disposes its resources.
 296    /// </summary>
 297    /// <param name="name">The name of the KestrunHost instance to destroy.</param>
 298    /// <param name="disposeLogger">Whether to dispose the Serilog logger if this was the last instance.</param>
 299    /// <remarks>
 300    /// If this is the last instance, the logger will be disposed to release any resources.
 301    /// </remarks>
 302    public static void Destroy(string name, bool disposeLogger = false)
 303    {
 20304        if (Log.IsEnabled(LogEventLevel.Debug))
 305        {
 20306            Log.Debug("Destroying KestrunHost instance '{Name}'", name);
 307        }
 308
 20309        if (_instances.TryRemove(name, out var host))
 310        {
 20311            host.Dispose();
 20312            if (_defaultName == name)
 313            {
 19314                _defaultName = _instances.Keys.FirstOrDefault();
 315            }
 316
 20317            if (Log.IsEnabled(LogEventLevel.Debug))
 318            {
 20319                Log.Debug("KestrunHost instance '{Name}' destroyed", name);
 320            }
 321        }
 322        else
 323        {
 0324            if (Log.IsEnabled(LogEventLevel.Warning))
 325            {
 0326                Log.Warning("No KestrunHost instance named '{Name}' exists to destroy", name);
 327            }
 328        }
 329
 330        // If no more instances, clear default and close logger
 20331        if (_instances.IsEmpty)
 332        {
 13333            _defaultName = null;
 334            // If no more instances, close logger
 13335            if (disposeLogger) { Log.CloseAndFlush(); }
 336        }
 20337    }
 338
 339    /// <summary>
 340    /// Destroys all KestrunHost instances and disposes their resources.
 341    /// </summary>
 342    public static void DestroyAll()
 343    {
 19344        if (Log.IsEnabled(LogEventLevel.Debug))
 345        {
 19346            Log.Debug("Destroying all KestrunHost instances");
 347        }
 348
 72349        foreach (var name in _instances.Keys.ToList())
 350        {
 17351            Destroy(name);
 352        }
 19353    }
 354}