< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Tool.ConsoleProgressBar
Assembly: Kestrun.Tool
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun.Tool/ConsoleBar.cs
Tag: Kestrun/Kestrun@09cad9a8fdafda7aca15f5f5e888b4bbcc8f0674
Line coverage
60%
Covered lines: 35
Uncovered lines: 23
Coverable lines: 58
Total lines: 120
Line coverage: 60.3%
Branch coverage
40%
Covered branches: 18
Total branches: 44
Branch coverage: 40.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 03/26/2026 - 03:54:59 Line coverage: 60.3% (35/58) Branch coverage: 40.9% (18/44) Total lines: 120 Tag: Kestrun/Kestrun@844b5179fb0492dc6b1182bae3ff65fa7365521d 03/26/2026 - 03:54:59 Line coverage: 60.3% (35/58) Branch coverage: 40.9% (18/44) Total lines: 120 Tag: Kestrun/Kestrun@844b5179fb0492dc6b1182bae3ff65fa7365521d

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%44100%
.cctor()100%11100%
Report(...)12.5%1961611.11%
Complete(...)33.33%19628.57%
Dispose()50%7666.67%
GetPercent(...)100%44100%
Render(...)37.5%8893.33%
BuildBar(...)100%11100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun.Tool/ConsoleBar.cs

#LineLine coverage
 1namespace Kestrun.Tool;
 2
 3/// <summary>
 4/// Writes in-place progress updates for module operations.
 5/// </summary>
 46internal sealed class ConsoleProgressBar(string label, long? total, Func<long, long?, string>? detailFormatter) : IDispo
 7{
 8    private const int ProgressBarWidth = 28;
 19    private static readonly TimeSpan RenderThrottle = TimeSpan.FromMilliseconds(80);
 10
 411    private readonly string _label = label;
 412    private readonly long? _total = total.HasValue && total.Value > 0 ? total : null;
 413    private readonly Func<long, long?, string>? _detailFormatter = detailFormatter;
 414    private readonly bool _enabled = !Console.IsOutputRedirected;
 15    private int _lastRenderedLength;
 416    private int _lastPercent = -1;
 417    private long _lastValue = -1;
 418    private DateTime _lastRenderUtc = DateTime.MinValue;
 19    private bool _hasRendered;
 20    private bool _isComplete;
 21
 22    public void Report(long value, bool force = false)
 23    {
 124        if (!_enabled || _isComplete)
 25        {
 126            return;
 27        }
 28
 029        var sanitizedValue = Math.Max(0, value);
 030        var percent = GetPercent(sanitizedValue);
 031        var now = DateTime.UtcNow;
 32
 033        if (!force
 034            && sanitizedValue == _lastValue
 035            && percent == _lastPercent)
 36        {
 037            return;
 38        }
 39
 040        if (!force
 041            && percent == _lastPercent
 042            && now - _lastRenderUtc < RenderThrottle)
 43        {
 044            return;
 45        }
 46
 047        _lastValue = sanitizedValue;
 048        _lastPercent = percent;
 049        _lastRenderUtc = now;
 050        Render(sanitizedValue, percent);
 051    }
 52
 53    public void Complete(long value)
 54    {
 155        if (!_enabled || _isComplete)
 56        {
 157            return;
 58        }
 59
 060        var completionValue = _total ?? Math.Max(0, value);
 061        Report(completionValue, force: true);
 062        Console.WriteLine();
 063        _isComplete = true;
 064    }
 65
 66    public void Dispose()
 67    {
 168        if (_enabled && _hasRendered && !_isComplete)
 69        {
 070            Console.WriteLine();
 71        }
 172    }
 73
 74    /// <summary>
 75    /// Returns the percentage (0-100) for the given value based on the total, or -1 if percentage cannot be calculated.
 76    /// </summary>
 77    /// <param name="value">The current value.</param>
 78    /// <returns>The percentage (0-100) or -1 if percentage cannot be calculated.</returns>
 79    private int GetPercent(long value)
 80    {
 381        if (!_total.HasValue || _total.Value <= 0)
 82        {
 183            return -1;
 84        }
 85        // Use long math to avoid overflow, then clamp to 0-100.
 286        return (int)Math.Clamp(value * 100L / _total.Value, 0, 100);
 87    }
 88
 89    private void Render(long value, int percent)
 90    {
 191        var detail = _detailFormatter is null
 192            ? _total.HasValue
 193                ? $"{value}/{_total.Value}"
 194                : value.ToString()
 195            : _detailFormatter(value, _total);
 96
 197        var line = percent >= 0
 198            ? $"{_label} {BuildBar(percent)} {percent,3}% {detail}"
 199            : $"{_label} {detail}";
 100
 1101        if (line.Length < _lastRenderedLength)
 102        {
 0103            line = line.PadRight(_lastRenderedLength);
 104        }
 105
 1106        _lastRenderedLength = line.Length;
 1107        _hasRendered = true;
 108
 1109        Console.Write('\r');
 1110        Console.Write(line);
 1111    }
 112
 113    private static string BuildBar(int percent)
 114    {
 4115        var filled = (int)Math.Round(percent / 100d * ProgressBarWidth, MidpointRounding.AwayFromZero);
 4116        filled = Math.Clamp(filled, 0, ProgressBarWidth);
 4117        return $"[{new string('#', filled)}{new string('-', ProgressBarWidth - filled)}]";
 118    }
 119}
 120