Skip to content

Commit 4d80224

Browse files
committed
feat: add ProgressReporter#update and #finish
1 parent 8dab9c9 commit 4d80224

2 files changed

Lines changed: 90 additions & 0 deletions

File tree

‎lib/bench/progress_reporter.rb‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,36 @@
11
# lib/bench/progress_reporter.rb
22
module Bench
33
class ProgressReporter
4+
def initialize(expected_total:, io: $stdout, window: 12, plain_interval: 10)
5+
@expected_total = expected_total
6+
@io = io
7+
@window = window
8+
@plain_interval = plain_interval
9+
@tty = io.respond_to?(:tty?) && io.tty?
10+
@last_plain_t = nil
11+
end
12+
13+
def update(samples)
14+
return if samples.empty?
15+
16+
latest = samples.last
17+
eta = self.class.eta_seconds(samples, @expected_total, window: @window)
18+
line = self.class.format_line(latest["completed"], @expected_total, eta)
19+
20+
if @tty
21+
@io.print("\r#{line}\e[K")
22+
else
23+
t = latest["t"]
24+
return if @last_plain_t && (t - @last_plain_t) < @plain_interval
25+
@last_plain_t = t
26+
@io.puts(line)
27+
end
28+
end
29+
30+
def finish
31+
@io.print("\n") if @tty
32+
end
33+
434
def self.format_duration(seconds)
535
total = seconds.round
636
hours, remainder = total.divmod(3600)

‎test/progress_reporter_test.rb‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,63 @@ def test_formats_line_with_zero_expected_total
7474
assert_equal "0/0 completed (0.0%) | ETA calculating...", line
7575
end
7676
end
77+
78+
require "stringio"
79+
80+
class FakeTTY
81+
attr_reader :writes
82+
83+
def initialize
84+
@writes = []
85+
end
86+
87+
def tty? = true
88+
def print(str) = @writes << str
89+
end
90+
91+
class ProgressReporterInstanceTest < Minitest::Test
92+
def test_update_is_noop_with_empty_samples
93+
io = StringIO.new
94+
Bench::ProgressReporter.new(expected_total: 500, io: io).update([])
95+
assert_equal "", io.string
96+
end
97+
98+
def test_non_tty_prints_immediately_then_throttles_by_sample_time
99+
io = StringIO.new
100+
reporter = Bench::ProgressReporter.new(expected_total: 500, io: io, plain_interval: 10)
101+
102+
reporter.update([{ "t" => 0.0, "completed" => 10 }])
103+
reporter.update([{ "t" => 0.0, "completed" => 10 }, { "t" => 5.0, "completed" => 60 }])
104+
reporter.update([
105+
{ "t" => 0.0, "completed" => 10 }, { "t" => 5.0, "completed" => 60 },
106+
{ "t" => 11.0, "completed" => 120 }
107+
])
108+
109+
lines = io.string.lines
110+
assert_equal 2, lines.length
111+
assert_includes lines[0], "10/500 completed"
112+
assert_includes lines[1], "120/500 completed"
113+
end
114+
115+
def test_tty_redraws_in_place_on_every_update
116+
io = FakeTTY.new
117+
reporter = Bench::ProgressReporter.new(expected_total: 500, io: io)
118+
119+
reporter.update([{ "t" => 0.0, "completed" => 10 }])
120+
reporter.update([{ "t" => 0.1, "completed" => 12 }])
121+
122+
assert_equal 2, io.writes.length
123+
assert_match(/\A\r10\/500 completed.*\e\[K\z/, io.writes[0])
124+
assert_match(/\A\r12\/500 completed.*\e\[K\z/, io.writes[1])
125+
end
126+
127+
def test_finish_prints_newline_on_tty_only
128+
tty_io = FakeTTY.new
129+
Bench::ProgressReporter.new(expected_total: 500, io: tty_io).finish
130+
assert_equal ["\n"], tty_io.writes
131+
132+
plain_io = StringIO.new
133+
Bench::ProgressReporter.new(expected_total: 500, io: plain_io).finish
134+
assert_equal "", plain_io.string
135+
end
136+
end

0 commit comments

Comments
 (0)