< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Health.PowerShellScriptProbe
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Health/PowerShellScriptProbe.cs
Tag: Kestrun/Kestrun@5f1d2b981c9d7292c11fd448428c6ab6c811c5de
Line coverage
57%
Covered lines: 95
Uncovered lines: 69
Coverable lines: 164
Total lines: 490
Line coverage: 57.9%
Branch coverage
60%
Covered branches: 98
Total branches: 163
Branch coverage: 60.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 11/19/2025 - 17:40:50 Line coverage: 0% (0/164) Branch coverage: 0% (0/163) Total lines: 491 Tag: Kestrun/Kestrun@fcf33342333cef0516fe0d0912a86709874fd02612/18/2025 - 21:41:58 Line coverage: 0% (0/164) Branch coverage: 0% (0/163) Total lines: 490 Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff03/26/2026 - 03:54:59 Line coverage: 57.9% (95/164) Branch coverage: 60.1% (98/163) Total lines: 490 Tag: Kestrun/Kestrun@844b5179fb0492dc6b1182bae3ff65fa7365521d 11/19/2025 - 17:40:50 Line coverage: 0% (0/164) Branch coverage: 0% (0/163) Total lines: 491 Tag: Kestrun/Kestrun@fcf33342333cef0516fe0d0912a86709874fd02612/18/2025 - 21:41:58 Line coverage: 0% (0/164) Branch coverage: 0% (0/163) Total lines: 490 Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff03/26/2026 - 03:54:59 Line coverage: 57.9% (95/164) Branch coverage: 60.1% (98/163) Total lines: 490 Tag: Kestrun/Kestrun@844b5179fb0492dc6b1182bae3ff65fa7365521d

Coverage delta

Coverage delta 61 -61

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%4471.43%
get_Logger()100%210%
CheckAsync()0%4260%
CreateConfiguredPowerShell(...)0%620%
InvokeScriptAsync()0%2040%
ProcessOutput(...)0%110100%
TryConvert(...)83.33%7675%
TryConvertFromString(...)100%44100%
TryConvertFromProperties(...)66.67%7675%
TryUnwrapProbeResult(...)100%22100%
TryGetStatus(...)71.43%171476.47%
GetDescription(...)30%1010100%
GetDataDictionary(...)50%4471.43%
TryExtractDataDictionary(...)50%88100%
IsValidDataKey(...)100%11100%
BuildNormalizedData(...)100%1010100%
PromoteData(...)100%22100%
NormalizePsValue(...)71.43%171475%
IsPrimitive(...)100%22100%
CollapseAtDepth(...)0%2040%
NormalizePsPsObject(...)50%44100%
NormalizeDictionary(...)0%4260%
NormalizeEnumerable(...)100%22100%
TryParseStatus(...)87.18%3939100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Health/PowerShellScriptProbe.cs

#LineLine coverage
 1using System.Collections;
 2using System.Management.Automation;
 3using System.Management.Automation.Runspaces;
 4using Kestrun.Languages;
 5using Kestrun.Scripting;
 6using Serilog.Events;
 7using Kestrun.Hosting;
 8
 9namespace Kestrun.Health;
 10
 11/// <summary>
 12/// A health probe implemented via a PowerShell script.
 13/// </summary>
 14/// <param name="host">The Kestrun host instance.</param>
 15/// <param name="name">The name of the probe.</param>
 16/// <param name="tags">The tags associated with the probe.</param>
 17/// <param name="script">The PowerShell script to execute.</param>
 18/// <param name="poolAccessor">A function to access the runspace pool.</param>
 19/// <param name="arguments">The arguments for the script.</param>
 20internal sealed class PowerShellScriptProbe(
 21        KestrunHost host,
 22        string name,
 23        IEnumerable<string>? tags,
 24        string script,
 25        Func<KestrunRunspacePoolManager> poolAccessor,
 026        IReadOnlyDictionary<string, object?>? arguments) : Probe(name, tags), IProbe
 27{
 028    private Serilog.ILogger Logger => host.Logger;
 129    private readonly Func<KestrunRunspacePoolManager> _poolAccessor = poolAccessor ?? throw new ArgumentNullException(na
 130    private readonly IReadOnlyDictionary<string, object?>? _arguments = arguments;
 131    private readonly string _script = string.IsNullOrWhiteSpace(script)
 132        ? throw new ArgumentException("Probe script cannot be null or whitespace.", nameof(script))
 133        : script;
 34
 35    /// <inheritdoc/>
 36    public override async Task<ProbeResult> CheckAsync(CancellationToken ct = default)
 37    {
 038        var pool = _poolAccessor();
 039        Runspace? runspace = null;
 40        try
 41        {
 042            if (Logger.IsEnabled(LogEventLevel.Debug))
 43            {
 044                Logger.Debug("PowerShellScriptProbe {Probe} acquiring runspace", Name);
 45            }
 046            runspace = await pool.AcquireAsync(ct).ConfigureAwait(false);
 047            using var ps = CreateConfiguredPowerShell(runspace);
 048            var output = await InvokeScriptAsync(ps, ct).ConfigureAwait(false);
 049            return ProcessOutput(ps, output);
 50        }
 051        catch (PipelineStoppedException) when (ct.IsCancellationRequested)
 52        {
 053            Logger.Information("PowerShell health probe {Probe} canceled (PipelineStopped).", Name);
 054            return new ProbeResult(ProbeStatus.Degraded, "Canceled");
 55        }
 056        catch (OperationCanceledException) when (ct.IsCancellationRequested)
 57        {
 058            throw;
 59        }
 060        catch (Exception ex)
 61        {
 062            if (ex is PipelineStoppedException)
 63            {
 064                Logger.Warning(ex, "PowerShell health probe {Probe} pipeline stopped.", Name);
 065                return new ProbeResult(ProbeStatus.Degraded, "Canceled");
 66            }
 067            Logger.Error(ex, "PowerShell health probe {Probe} failed.", Name);
 068            return new ProbeResult(ProbeStatus.Unhealthy, $"Exception: {ex.Message}");
 69        }
 70        finally
 71        {
 072            if (runspace is not null)
 73            {
 074                try { pool.Release(runspace); }
 075                catch { runspace.Dispose(); }
 76            }
 77        }
 078    }
 79
 80    /// <summary>
 81    /// Creates and configures a PowerShell instance with the provided runspace and script.
 82    /// </summary>
 83    /// <param name="runspace">The runspace to use for the PowerShell instance.</param>
 84    /// <returns>A configured PowerShell instance.</returns>
 85    private PowerShell CreateConfiguredPowerShell(Runspace runspace)
 86    {
 087        var ps = PowerShell.Create();
 088        ps.Runspace = runspace;
 089        PowerShellExecutionHelpers.SetVariables(ps, _arguments, Logger);
 090        PowerShellExecutionHelpers.AddScript(ps, _script);
 091        if (Logger.IsEnabled(LogEventLevel.Debug))
 92        {
 093            Logger.Debug("PowerShellScriptProbe {Probe} invoking script length={Length}", Name, _script.Length);
 94        }
 095        return ps;
 96    }
 97
 98    /// <summary>
 99    /// Invokes the PowerShell script asynchronously.
 100    /// </summary>
 101    /// <param name="ps">The PowerShell instance to use.</param>
 102    /// <param name="ct">The cancellation token.</param>
 103    /// <returns>A task representing the asynchronous operation, with a list of PSObject as the result.</returns>
 104    private async Task<IReadOnlyList<PSObject>> InvokeScriptAsync(PowerShell ps, CancellationToken ct)
 105    {
 0106        var output = await ps.InvokeAsync(Logger, ct).ConfigureAwait(false);
 0107        if (Logger.IsEnabled(LogEventLevel.Debug))
 108        {
 0109            Logger.Debug("PowerShellScriptProbe {Probe} received {Count} output objects", Name, output.Count);
 110        }
 111        // Materialize to a List to satisfy IReadOnlyList contract and avoid invalid casts.
 0112        return output.Count == 0 ? Array.Empty<PSObject>() : new List<PSObject>(output);
 0113    }
 114
 115    /// <summary>
 116    /// Processes the output from the PowerShell script.
 117    /// </summary>
 118    /// <param name="ps">The PowerShell instance used to invoke the script.</param>
 119    /// <param name="output">The output objects returned by the script.</param>
 120    /// <returns>A ProbeResult representing the outcome of the script execution.</returns>
 121    private ProbeResult ProcessOutput(PowerShell ps, IReadOnlyList<PSObject> output)
 122    {
 0123        if (ps.HadErrors || ps.Streams.Error.Count > 0)
 124        {
 0125            var errors = string.Join("; ", ps.Streams.Error.Select(static e => e.ToString()));
 0126            ps.Streams.Error.Clear();
 0127            return new ProbeResult(ProbeStatus.Unhealthy, errors);
 128        }
 129
 0130        for (var i = output.Count - 1; i >= 0; i--)
 131        {
 0132            if (TryConvert(output[i], out var result))
 133            {
 0134                if (Logger.IsEnabled(LogEventLevel.Debug))
 135                {
 0136                    Logger.Debug("PowerShellScriptProbe {Probe} converted output index={Index} status={Status}", Name, i
 137                }
 0138                return result;
 139            }
 140        }
 0141        return new ProbeResult(ProbeStatus.Unhealthy, "PowerShell probe produced no recognizable result.");
 142    }
 143
 144    /// <summary>
 145    /// Tries to convert a PSObject to a ProbeResult.
 146    /// </summary>
 147    /// <param name="obj">The PSObject to convert.</param>
 148    /// <param name="result">The resulting ProbeResult.</param>
 149    /// <returns>True if the conversion was successful, false otherwise.</returns>
 150    private static bool TryConvert(PSObject obj, out ProbeResult result)
 151    {
 152        // Direct pass-through if already a ProbeResult
 3153        if (TryUnwrapProbeResult(obj, out result))
 154        {
 1155            return true;
 156        }
 157
 158        // Try string-based conversion
 2159        if (TryConvertFromString(obj, out result))
 160        {
 1161            return true;
 162        }
 163
 164        // Try property-based conversion
 1165        if (TryConvertFromProperties(obj, out result))
 166        {
 1167            return true;
 168        }
 169
 0170        result = default!;
 0171        return false;
 172    }
 173
 174    /// <summary>
 175    /// Handles string-based conversion scenario
 176    /// </summary>
 177    /// <param name="obj">The PSObject to convert.</param>
 178    /// <param name="result">The resulting ProbeResult.</param>
 179    /// <returns>True if the conversion was successful, false otherwise.</returns>
 180    private static bool TryConvertFromString(PSObject obj, out ProbeResult result)
 181    {
 2182        if (TryGetStatus(obj, out var status, out var descriptionWhenString, out var statusTextIsRaw))
 183        {
 2184            if (statusTextIsRaw)
 185            {
 186                // We interpreted the entire string object as both status + description
 1187                result = new ProbeResult(status, descriptionWhenString);
 1188                return true;
 189            }
 190        }
 1191        result = default!;
 1192        return false;
 193    }
 194
 195    /// <summary>
 196    /// Handles property-based conversion scenario
 197    /// </summary>
 198    /// <param name="obj">The PSObject to convert.</param>
 199    /// <param name="result">The resulting ProbeResult.</param>
 200    /// <returns>True if the conversion was successful, false otherwise.</returns>
 201    private static bool TryConvertFromProperties(PSObject obj, out ProbeResult result)
 202    {
 1203        if (TryGetStatus(obj, out var status, out var descriptionWhenString, out var statusTextIsRaw))
 204        {
 1205            if (!statusTextIsRaw)
 206            {
 1207                var description = descriptionWhenString ?? GetDescription(obj);
 1208                var data = GetDataDictionary(obj);
 1209                result = new ProbeResult(status, description, data);
 1210                return true;
 211            }
 212        }
 0213        result = default!;
 0214        return false;
 215    }
 216    /// <summary>
 217    /// Tries to unwrap a PSObject that directly wraps a ProbeResult.
 218    /// </summary>
 219    /// <param name="obj">The PSObject to unwrap.</param>
 220    /// <param name="result">The unwrapped ProbeResult.</param>
 221    /// <returns>True if the unwrapping was successful, false otherwise.</returns>
 222    private static bool TryUnwrapProbeResult(PSObject obj, out ProbeResult result)
 223    {
 3224        if (obj.BaseObject is ProbeResult pr)
 225        {
 1226            result = pr;
 1227            return true;
 228        }
 2229        result = default!;
 2230        return false;
 231    }
 232
 233    /// <summary>
 234    /// Parses the status from a PSObject.
 235    /// </summary>
 236    /// <param name="obj">The PSObject to parse.</param>
 237    /// <param name="status">The parsed ProbeStatus.</param>
 238    /// <param name="descriptionOrRaw">The description or raw status string.</param>
 239    /// <param name="isRawString">True if the status was a raw string, false otherwise.</param>
 240    /// <returns>True if the parsing was successful, false otherwise.</returns>
 241    private static bool TryGetStatus(PSObject obj, out ProbeStatus status, out string? descriptionOrRaw, out bool isRawS
 242    {
 3243        var statusValue = obj.Properties["status"]?.Value ?? obj.Properties["Status"]?.Value;
 3244        if (statusValue is null)
 245        {
 1246            if (obj.BaseObject is string statusText && TryParseStatus(statusText, out var parsedFromText))
 247            {
 1248                status = parsedFromText;
 1249                descriptionOrRaw = statusText;
 1250                isRawString = true;
 1251                return true;
 252            }
 0253            status = default;
 0254            descriptionOrRaw = null;
 0255            isRawString = false;
 0256            return false;
 257        }
 258
 2259        status = TryParseStatus(statusValue.ToString(), out var parsed)
 2260            ? parsed
 2261            : ProbeStatus.Unhealthy;
 2262        descriptionOrRaw = null; // description will be resolved separately
 2263        isRawString = false;
 2264        return true;
 265    }
 266    /// <summary>
 267    /// Gets the description from a PSObject.
 268    /// </summary>
 269    /// <param name="obj">The PSObject to extract the description from.</param>
 270    /// <returns>The description string, or null if not found.</returns>
 271    private static string? GetDescription(PSObject obj)
 1272        => obj.Properties["description"]?.Value?.ToString() ?? obj.Properties["Description"]?.Value?.ToString();
 273
 274    /// <summary>
 275    /// Gets the data dictionary from a PSObject.
 276    /// </summary>
 277    /// <param name="obj">The PSObject to extract the data from.</param>
 278    /// <returns>The data dictionary, or null if not found or empty.</returns>
 279    private static Dictionary<string, object>? GetDataDictionary(PSObject obj)
 280    {
 281        // Extract underlying dictionary (case-insensitive property name handling already done)
 1282        var dictionary = TryExtractDataDictionary(obj);
 1283        if (dictionary is null)
 284        {
 0285            return null;
 286        }
 287
 288        // Build normalized temporary dictionary (allows null filtering before final allocation)
 1289        var temp = BuildNormalizedData(dictionary);
 1290        if (temp.Count == 0)
 291        {
 0292            return null; // No meaningful data left after normalization
 293        }
 294
 1295        return PromoteData(temp);
 296    }
 297
 298    private static IDictionary? TryExtractDataDictionary(PSObject obj)
 299    {
 1300        var dataProperty = obj.Properties["data"] ?? obj.Properties["Data"];
 1301        return dataProperty?.Value is IDictionary dict && dict.Count > 0 ? dict : null;
 302    }
 303
 3304    private static bool IsValidDataKey(string? key) => !string.IsNullOrWhiteSpace(key);
 305
 306    /// <summary>
 307    /// Builds a normalized data dictionary from the original dictionary.
 308    /// </summary>
 309    /// <param name="dictionary">The original dictionary to normalize.</param>
 310    /// <returns>A new dictionary with normalized data.</returns>
 311    private static Dictionary<string, object?> BuildNormalizedData(IDictionary dictionary)
 312    {
 1313        var temp = new Dictionary<string, object?>(dictionary.Count, StringComparer.OrdinalIgnoreCase);
 8314        foreach (DictionaryEntry entry in dictionary)
 315        {
 3316            if (entry.Key is null || entry.Value is null)
 317            {
 318                continue;
 319            }
 3320            var key = entry.Key.ToString();
 3321            if (!IsValidDataKey(key))
 322            {
 323                continue;
 324            }
 2325            var normalized = NormalizePsValue(entry.Value);
 2326            if (normalized is not null)
 327            {
 2328                temp[key!] = normalized; // key validated
 329            }
 330        }
 1331        return temp;
 332    }
 333
 334    /// <summary>
 335    /// Promotes the data from a temporary dictionary to a final dictionary.
 336    /// </summary>
 337    /// <param name="temp">The temporary dictionary to promote.</param>
 338    /// <returns>The promoted dictionary.</returns>
 339    private static Dictionary<string, object> PromoteData(Dictionary<string, object?> temp)
 340    {
 1341        var final = new Dictionary<string, object>(temp.Count, StringComparer.OrdinalIgnoreCase);
 6342        foreach (var kvp in temp)
 343        {
 344            // Value cannot be null due to earlier filter
 2345            final[kvp.Key] = kvp.Value!;
 346        }
 1347        return final;
 348    }
 349
 350    /// <summary>
 351    /// Normalizes a value that may originate from PowerShell so JSON serialization stays lean.
 352    /// (Delegates to smaller helpers to keep cyclomatic complexity low.)
 353    /// </summary>
 354    /// <param name="value">The value to normalize.</param>
 355    /// <param name="depth">The current recursion depth.</param>
 356    /// <returns>The normalized value.</returns>
 357    private static object? NormalizePsValue(object? value, int depth = 0)
 358    {
 5359        if (value is null)
 360        {
 0361            return null;
 362        }
 5363        if (depth > 8)
 364        {
 0365            return CollapseAtDepth(value);
 366        }
 5367        if (value is PSObject psObj)
 368        {
 1369            return NormalizePsPsObject(psObj, depth);
 370        }
 4371        if (IsPrimitive(value))
 372        {
 2373            return value;
 374        }
 2375        if (value is IDictionary dict)
 376        {
 0377            return NormalizeDictionary(dict, depth);
 378        }
 379        // Avoid treating strings as IEnumerable<char>
 2380        return value switch
 2381        {
 1382            string s => s,
 1383            IEnumerable seq => NormalizeEnumerable(seq, depth),
 0384            _ => value
 2385        };
 386    }
 387
 388    /// <summary>
 389    /// Determines if a value is a primitive type that can be directly serialized.
 390    /// </summary>
 391    /// <param name="value">The value to check.</param>
 392    /// <returns>True if the value is a primitive type, false otherwise.</returns>
 4393    private static bool IsPrimitive(object value) => value is IFormattable && value.GetType().IsPrimitive;
 394
 395    /// <summary>
 396    /// Collapses a value to its string representation when maximum depth is reached.
 397    /// </summary>
 398    /// <param name="value">The value to collapse.</param>
 399    /// <returns>The string representation of the value.</returns>
 0400    private static string CollapseAtDepth(object value) => value is PSObject pso ? pso.ToString() : value.ToString() ?? 
 401
 402    /// <summary>
 403    /// Normalizes a PowerShell PSObject by extracting its base object and normalizing it.
 404    /// </summary>
 405    /// <param name="psObj">The PSObject to normalize.</param>
 406    /// <param name="depth">The current recursion depth.</param>
 407    private static object? NormalizePsPsObject(PSObject psObj, int depth)
 408    {
 1409        var baseObj = psObj.BaseObject;
 1410        return baseObj is null || ReferenceEquals(baseObj, psObj)
 1411            ? psObj.ToString()
 1412            : NormalizePsValue(baseObj, depth + 1);
 413    }
 414
 415    /// <summary>
 416    /// Normalizes a dictionary by converting its keys to strings and recursively normalizing its values.
 417    /// </summary>
 418    /// <param name="rawDict">The raw dictionary to normalize.</param>
 419    /// <param name="depth">The current recursion depth.</param>
 420    /// <returns>A normalized dictionary with string keys and normalized values.</returns>
 421    private static Dictionary<string, object?> NormalizeDictionary(IDictionary rawDict, int depth)
 422    {
 0423        var result = new Dictionary<string, object?>(rawDict.Count);
 0424        foreach (DictionaryEntry de in rawDict)
 425        {
 0426            if (de.Key is null)
 427            {
 428                continue;
 429            }
 0430            var k = de.Key.ToString();
 0431            if (string.IsNullOrWhiteSpace(k))
 432            {
 433                continue;
 434            }
 0435            result[k] = NormalizePsValue(de.Value, depth + 1);
 436        }
 0437        return result;
 438    }
 439
 440    /// <summary>
 441    /// Normalizes an enumerable by recursively normalizing its items.
 442    /// </summary>
 443    /// <param name="enumerable">The enumerable to normalize.</param>
 444    /// <param name="depth">The current recursion depth.</param>
 445    /// <returns>A list of normalized items.</returns>
 446    private static List<object?> NormalizeEnumerable(IEnumerable enumerable, int depth)
 447    {
 1448        var list = new List<object?>();
 6449        foreach (var item in enumerable)
 450        {
 2451            list.Add(NormalizePsValue(item, depth + 1));
 452        }
 1453        return list;
 454    }
 455
 456    /// <summary>
 457    /// Parses a status string into a ProbeStatus enum.
 458    /// </summary>
 459    /// <param name="value">The status string to parse.</param>
 460    /// <param name="status">The resulting ProbeStatus enum.</param>
 461    /// <returns>True if the parsing was successful, false otherwise.</returns>
 462    private static bool TryParseStatus(string? value, out ProbeStatus status)
 463    {
 7464        if (Enum.TryParse(value, ignoreCase: true, out status))
 465        {
 2466            return true;
 467        }
 468
 5469        switch (value?.ToLowerInvariant())
 470        {
 471            case ProbeStatusLabels.STATUS_OK:
 472            case ProbeStatusLabels.STATUS_HEALTHY:
 1473                status = ProbeStatus.Healthy;
 1474                return true;
 475            case ProbeStatusLabels.STATUS_WARN:
 476            case ProbeStatusLabels.STATUS_WARNING:
 477            case ProbeStatusLabels.STATUS_DEGRADED:
 2478                status = ProbeStatus.Degraded;
 2479                return true;
 480            case ProbeStatusLabels.STATUS_FAIL:
 481            case ProbeStatusLabels.STATUS_FAILED:
 482            case ProbeStatusLabels.STATUS_UNHEALTHY:
 1483                status = ProbeStatus.Unhealthy;
 1484                return true;
 485            default:
 1486                status = ProbeStatus.Unhealthy;
 1487                return false;
 488        }
 489    }
 490}

Methods/Properties

.ctor(Kestrun.Hosting.KestrunHost,System.String,System.Collections.Generic.IEnumerable`1<System.String>,System.String,System.Func`1<Kestrun.Scripting.KestrunRunspacePoolManager>,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Object>)
get_Logger()
CheckAsync()
CreateConfiguredPowerShell(System.Management.Automation.Runspaces.Runspace)
InvokeScriptAsync()
ProcessOutput(System.Management.Automation.PowerShell,System.Collections.Generic.IReadOnlyList`1<System.Management.Automation.PSObject>)
TryConvert(System.Management.Automation.PSObject,Kestrun.Health.ProbeResult&)
TryConvertFromString(System.Management.Automation.PSObject,Kestrun.Health.ProbeResult&)
TryConvertFromProperties(System.Management.Automation.PSObject,Kestrun.Health.ProbeResult&)
TryUnwrapProbeResult(System.Management.Automation.PSObject,Kestrun.Health.ProbeResult&)
TryGetStatus(System.Management.Automation.PSObject,Kestrun.Health.ProbeStatus&,System.String&,System.Boolean&)
GetDescription(System.Management.Automation.PSObject)
GetDataDictionary(System.Management.Automation.PSObject)
TryExtractDataDictionary(System.Management.Automation.PSObject)
IsValidDataKey(System.String)
BuildNormalizedData(System.Collections.IDictionary)
PromoteData(System.Collections.Generic.Dictionary`2<System.String,System.Object>)
NormalizePsValue(System.Object,System.Int32)
IsPrimitive(System.Object)
CollapseAtDepth(System.Object)
NormalizePsPsObject(System.Management.Automation.PSObject,System.Int32)
NormalizeDictionary(System.Collections.IDictionary,System.Int32)
NormalizeEnumerable(System.Collections.IEnumerable,System.Int32)
TryParseStatus(System.String,Kestrun.Health.ProbeStatus&)