< 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@ca54e35c77799b76774b3805b6f075cdbc0c5fbe
Line coverage
81%
Covered lines: 79
Uncovered lines: 18
Coverable lines: 97
Total lines: 367
Line coverage: 81.4%
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 09/08/2025 - 20:34:03 Line coverage: 85.3% (70/82) Branch coverage: 80% (56/70) Total lines: 321 Tag: Kestrun/Kestrun@3790ee5884494a7a2a829344a47743e0bf492e7209/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@0d738bf294e6281b936d031e1979d928007495ff01/08/2026 - 02:20:28 Line coverage: 81.4% (79/97) Branch coverage: 80% (56/70) Total lines: 367 Tag: Kestrun/Kestrun@4bc17b7e465c315de6386907c417e44fcb0fd3eb 09/08/2025 - 20:34:03 Line coverage: 85.3% (70/82) Branch coverage: 80% (56/70) Total lines: 321 Tag: Kestrun/Kestrun@3790ee5884494a7a2a829344a47743e0bf492e7209/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@0d738bf294e6281b936d031e1979d928007495ff01/08/2026 - 02:20:28 Line coverage: 81.4% (79/97) Branch coverage: 80% (56/70) Total lines: 367 Tag: Kestrun/Kestrun@4bc17b7e465c315de6386907c417e44fcb0fd3eb

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
get_InstanceNames()100%11100%
get_EntryScriptPath()100%11100%
get_KestrunRoot()100%11100%
set_KestrunRoot(...)100%6453.84%
Create(...)87.5%8890.9%
Create(...)100%210%
Create(...)70%101087.5%
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 the path of the entry script that invoked the KestrunHostManager.
 35    /// </summary>
 8236    public static string EntryScriptPath { get; private set; } = string.Empty;
 37    /// <summary>
 38    /// Gets or sets the root path for Kestrun operations.
 39    /// </summary>
 40    public static string? KestrunRoot
 41    {
 442        get => _kestrunRoot;
 43        set
 44        {
 2145            if (string.IsNullOrWhiteSpace(value))
 46            {
 147                throw new ArgumentException("Kestrun root path cannot be null or empty.", nameof(value));
 48            }
 49
 50            // NOTE:
 51            // On Unix/macOS, Directory.GetCurrentDirectory() can throw if the process CWD was deleted.
 52            // We still want to allow setting KestrunRoot (and attempt to set CWD) in that scenario.
 53            try
 54            {
 2055                if (Directory.GetCurrentDirectory() != value)
 56                {
 257                    Directory.SetCurrentDirectory(value);
 58                }
 2059            }
 060            catch (Exception ex) when (ex is IOException
 061                                       or UnauthorizedAccessException
 062                                       or DirectoryNotFoundException
 063                                       or FileNotFoundException)
 64            {
 65                // If the current working directory is missing/unavailable (common on Unix when deleted),
 66                // we can still attempt to set it to the provided root.
 067                Directory.SetCurrentDirectory(value);
 068            }
 2069            _kestrunRoot = value;
 2070        }
 71    }
 72
 73    /// <summary>
 74    /// Creates a new KestrunHost instance using the provided factory function.
 75    /// </summary>
 76    /// <param name="name">The name of the KestrunHost instance to create.</param>
 77    /// <param name="entryScriptPath">The path of the entry script that invoked this creation.</param>
 78    /// <param name="factory">A factory function that returns a new KestrunHost instance.</param>
 79    /// <param name="setAsDefault">Whether to set this instance as the default.</param>
 80    /// <param name="enablePowershellMiddleware">Whether to enable PowerShell middleware for this instance.</param>
 81    /// <returns>The created KestrunHost instance.</returns>
 82    public static KestrunHost Create(string name, string entryScriptPath, Func<KestrunHost> factory, bool setAsDefault =
 83    {
 2184        if (string.IsNullOrWhiteSpace(name))
 85        {
 086            throw new ArgumentException("Instance name is required.", nameof(name));
 87        }
 88
 2189        if (_instances.ContainsKey(name))
 90        {
 291            throw new InvalidOperationException($"A KestrunHost instance with the name '{name}' already exists.");
 92        }
 93
 1994        var host = factory();
 1995        host.PowershellMiddlewareEnabled = enablePowershellMiddleware;
 1996        _instances[name] = host;
 97
 1998        if (setAsDefault || _defaultName == null)
 99        {
 12100            _defaultName = name;
 101        }
 102
 103        // Set the entry script path
 19104        EntryScriptPath = entryScriptPath;
 105
 19106        return host;
 107    }
 108
 109    /// <summary>
 110    /// Creates a new KestrunHost instance with the specified name and optional module paths, using the default logger.
 111    /// </summary>
 112    /// <param name="name">The name of the KestrunHost instance to create.</param>
 113    /// <param name="entryScriptPath">The path of the entry script that invoked this creation.</param>
 114    /// <param name="modulePathsObj">Optional array of module paths to load.</param>
 115    /// <param name="setAsDefault">Whether to set this instance as the default.</param>
 116    /// <param name="enablePowershellMiddleware">Whether to enable PowerShell middleware for this instance.</param>
 117    /// <returns>The created KestrunHost instance.</returns>
 118    public static KestrunHost Create(string name, string entryScriptPath,
 119         string[]? modulePathsObj = null, bool setAsDefault = false, bool enablePowershellMiddleware = false) =>
 0120         Create(name, Log.Logger, entryScriptPath, modulePathsObj, setAsDefault, enablePowershellMiddleware);
 121
 122    /// <summary>
 123    /// Creates a new KestrunHost instance with the specified name, logger, root path, and optional module paths.
 124    /// </summary>
 125    /// <param name="name">The name of the KestrunHost instance to create.</param>
 126    /// <param name="logger">The Serilog logger to use for the host.</param>
 127    /// <param name="entryScriptPath">The path of the entry script that invoked this creation.</param>
 128    /// <param name="modulePathsObj">Optional array of module paths to load.</param>
 129    /// <param name="setAsDefault">Whether to set this instance as the default.</param>
 130    /// <param name="enablePowershellMiddleware">Whether to enable PowerShell middleware for this instance.</param>
 131    /// <returns>The created KestrunHost instance.</returns>
 132    public static KestrunHost Create(string name, Serilog.ILogger logger, string entryScriptPath,
 133         string[]? modulePathsObj = null, bool setAsDefault = false, bool enablePowershellMiddleware = false)
 134    {
 2135        if (string.IsNullOrWhiteSpace(name))
 136        {
 0137            throw new ArgumentException("Instance name is required.", nameof(name));
 138        }
 139
 2140        if (_instances.ContainsKey(name))
 141        {
 0142            throw new InvalidOperationException($"A KestrunHost instance with the name '{name}' already exists.");
 143        }
 144
 2145        if (KestrunRoot is null)
 146        {
 1147            throw new InvalidOperationException("Kestrun root path must be set before creating a KestrunHost instance.")
 148        }
 149
 1150        var host = new KestrunHost(name, logger, KestrunRoot, modulePathsObj)
 1151        {
 1152            PowershellMiddlewareEnabled = enablePowershellMiddleware,
 1153            DefaultHost = setAsDefault
 1154        };
 155
 1156        _instances[name] = host;
 157
 1158        if (setAsDefault || _defaultName == null)
 159        {
 1160            _defaultName = name;
 161        }
 162
 163        // Set the entry script path
 1164        EntryScriptPath = entryScriptPath;
 165
 1166        return host;
 167    }
 168
 169    /// <summary>
 170    /// Determines whether a KestrunHost instance with the specified name exists.
 171    /// </summary>
 172    /// <param name="name">The name of the KestrunHost instance to check for existence.</param>
 173    /// <returns>True if an instance with the specified name exists; otherwise, false.</returns>
 4174    public static bool Contains(string name) => _instances.ContainsKey(name);
 175
 176    /// <summary>
 177    /// Attempts to retrieve a KestrunHost instance by its name.
 178    /// </summary>
 179    /// <param name="name">The name of the KestrunHost instance to retrieve.</param>
 180    /// <param name="host">When this method returns, contains the KestrunHost instance associated with the specified nam
 181    /// <returns>True if the instance was found; otherwise, false.</returns>
 182    public static bool TryGet(string name, out KestrunHost? host) =>
 8183        _instances.TryGetValue(name, out host);
 184
 185    /// <summary>
 186    /// Retrieves a KestrunHost instance by its name.
 187    /// </summary>
 188    /// <param name="name">The name of the KestrunHost instance to retrieve.</param>
 189    /// <returns>The KestrunHost instance if found; otherwise, null.</returns>
 190    public static KestrunHost? Get(string name) =>
 4191        _instances.TryGetValue(name, out var host) ? host : null;
 192
 193    /// <summary>
 194    /// Gets the default KestrunHost instance, if one has been set.
 195    /// </summary>
 7196    public static KestrunHost? Default => _defaultName != null && _instances.TryGetValue(_defaultName, out var host) ? h
 197
 198    /// <summary>
 199    /// Sets the specified KestrunHost instance as the default.
 200    /// </summary>
 201    /// <param name="name">The name of the KestrunHost instance to set as default.</param>
 202    /// <exception cref="InvalidOperationException">Thrown if no instance with the specified name exists.</exception>
 203    public static void SetDefault(string name)
 204    {
 4205        if (!_instances.ContainsKey(name))
 206        {
 2207            throw new InvalidOperationException($"No KestrunHost instance named '{name}' exists.");
 208        }
 209
 2210        _defaultName = name;
 2211    }
 212
 213    /// <summary>
 214    /// Determines whether the specified KestrunHost instance is currently running.
 215    /// </summary>
 216    /// <param name="name">The name of the KestrunHost instance to check.</param>
 217    /// <returns>True if the instance is running; otherwise, false.</returns>
 1218    public static bool IsRunning(string name) => TryGet(name, out var host) && host != null && host.IsRunning;
 219
 220    /// <summary>
 221    /// Starts the specified KestrunHost instance asynchronously.
 222    /// </summary>
 223    /// <param name="name">The name of the KestrunHost instance to start.</param>
 224    /// <param name="ct">A cancellation token to observe while waiting for the task to complete.</param>
 225    public static async Task StartAsync(string name, CancellationToken ct = default)
 226    {
 1227        if (Log.IsEnabled(LogEventLevel.Debug))
 228        {
 1229            Log.Debug("Starting (Async) KestrunHost instance '{Name}'", name);
 230        }
 231
 1232        if (TryGet(name, out var host))
 233        {
 0234            if (host is not null)
 235            {
 0236                await host.StartAsync(ct);
 237            }
 238            else
 239            {
 0240                throw new InvalidOperationException($"KestrunHost instance '{name}' is null.");
 241            }
 242        }
 243        else
 244        {
 1245            throw new InvalidOperationException($"No KestrunHost instance named '{name}' exists.");
 246        }
 0247    }
 248
 249    /// <summary>
 250    /// Stops the specified KestrunHost instance asynchronously.
 251    /// </summary>
 252    /// <param name="name">The name of the KestrunHost instance to stop.</param>
 253    /// <param name="ct">A cancellation token to observe while waiting for the task to complete.</param>
 254    public static async Task StopAsync(string name, CancellationToken ct = default)
 255    {
 2256        if (Log.IsEnabled(LogEventLevel.Debug))
 257        {
 2258            Log.Debug("Stopping (Async) KestrunHost instance '{Name}'", name);
 259        }
 260
 2261        if (TryGet(name, out var host))
 262        {
 1263            if (host is not null)
 264            {
 1265                await host.StopAsync(ct);
 266            }
 267            else
 268            {
 0269                throw new InvalidOperationException($"KestrunHost instance '{name}' is null.");
 270            }
 271        }
 272        else
 273        {
 1274            throw new InvalidOperationException($"No KestrunHost instance named '{name}' exists.");
 275        }
 1276    }
 277
 278    /// <summary>
 279    /// Stops the specified KestrunHost instance synchronously.
 280    /// </summary>
 281    /// <param name="name">The name of the KestrunHost instance to stop.</param>
 282    public static void Stop(string name)
 283    {
 2284        if (_instances.TryGetValue(name, out var host))
 285        {
 0286            host.Stop();
 287        }
 2288    }
 289
 290    /// <summary>
 291    /// Stops all KestrunHost instances asynchronously.
 292    /// </summary>
 293    /// <param name="ct">A cancellation token to observe while waiting for the tasks to complete.</param>
 294    public static async Task StopAllAsync(CancellationToken ct = default)
 295    {
 2296        if (Log.IsEnabled(LogEventLevel.Debug))
 297        {
 2298            Log.Debug("Stopping all KestrunHost instances (Async)");
 299        }
 300
 12301        foreach (var kv in _instances)
 302        {
 4303            await kv.Value.StopAsync(ct);
 304        }
 2305    }
 306
 307    /// <summary>
 308    /// Destroys the specified KestrunHost instance and disposes its resources.
 309    /// </summary>
 310    /// <param name="name">The name of the KestrunHost instance to destroy.</param>
 311    /// <param name="disposeLogger">Whether to dispose the Serilog logger if this was the last instance.</param>
 312    /// <remarks>
 313    /// If this is the last instance, the logger will be disposed to release any resources.
 314    /// </remarks>
 315    public static void Destroy(string name, bool disposeLogger = false)
 316    {
 20317        if (Log.IsEnabled(LogEventLevel.Debug))
 318        {
 20319            Log.Debug("Destroying KestrunHost instance '{Name}'", name);
 320        }
 321
 20322        if (_instances.TryRemove(name, out var host))
 323        {
 20324            host.Dispose();
 20325            if (_defaultName == name)
 326            {
 19327                _defaultName = _instances.Keys.FirstOrDefault();
 328            }
 329
 20330            if (Log.IsEnabled(LogEventLevel.Debug))
 331            {
 20332                Log.Debug("KestrunHost instance '{Name}' destroyed", name);
 333            }
 334        }
 335        else
 336        {
 0337            if (Log.IsEnabled(LogEventLevel.Warning))
 338            {
 0339                Log.Warning("No KestrunHost instance named '{Name}' exists to destroy", name);
 340            }
 341        }
 342
 343        // If no more instances, clear default and close logger
 20344        if (_instances.IsEmpty)
 345        {
 13346            _defaultName = null;
 347            // If no more instances, close logger
 13348            if (disposeLogger) { Log.CloseAndFlush(); }
 349        }
 20350    }
 351
 352    /// <summary>
 353    /// Destroys all KestrunHost instances and disposes their resources.
 354    /// </summary>
 355    public static void DestroyAll()
 356    {
 19357        if (Log.IsEnabled(LogEventLevel.Debug))
 358        {
 19359            Log.Debug("Destroying all KestrunHost instances");
 360        }
 361
 72362        foreach (var name in _instances.Keys.ToList())
 363        {
 17364            Destroy(name);
 365        }
 19366    }
 367}