Skip to content

Commit f78251d

Browse files
authored
Prevent infinite loop in Command::Base#step & add configurable wait time (#217)
1 parent 1a11420 commit f78251d

File tree

4 files changed

+109
-8
lines changed

4 files changed

+109
-8
lines changed

.rubocop.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@ RSpec/ExampleLength:
2020

2121
RSpec/MultipleExpectations:
2222
Enabled: false
23+
24+
RSpec/NestedGroups:
25+
Enabled: true
26+
Max: 5

lib/command/base.rb

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -474,21 +474,19 @@ def step_finish(success, abort_on_error: true)
474474
end
475475
end
476476

477-
def step(message, abort_on_error: true, retry_on_failure: false) # rubocop:disable Metrics/MethodLength
477+
def step(message, abort_on_error: true, retry_on_failure: false, max_retry_count: 1000, wait: 1, &block) # rubocop:disable Metrics/MethodLength
478478
progress.print("#{message}...")
479479

480480
Shell.use_tmp_stderr do
481481
success = false
482482

483483
begin
484-
if retry_on_failure
485-
until (success = yield)
486-
progress.print(".")
487-
Kernel.sleep(1)
484+
success =
485+
if retry_on_failure
486+
with_retry(max_retry_count: max_retry_count, wait: wait, &block)
487+
else
488+
yield
488489
end
489-
else
490-
success = yield
491-
end
492490
rescue RuntimeError => e
493491
Shell.write_to_tmp_stderr(e.message)
494492
end
@@ -497,6 +495,22 @@ def step(message, abort_on_error: true, retry_on_failure: false) # rubocop:disab
497495
end
498496
end
499497

498+
def with_retry(max_retry_count:, wait:)
499+
retry_count = 0
500+
success = false
501+
502+
while !success && retry_count <= max_retry_count
503+
success = yield
504+
break if success
505+
506+
progress.print(".")
507+
Kernel.sleep(wait)
508+
retry_count += 1
509+
end
510+
511+
success
512+
end
513+
500514
def cp
501515
@cp ||= Controlplane.new(config)
502516
end

spec/command/base_spec.rb

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# frozen_string_literal: true
2+
3+
require "spec_helper"
4+
5+
describe Command::Base do
6+
let(:config) { instance_double(Command::Config) }
7+
let(:command) { described_class.new(config) }
8+
9+
around do |example|
10+
suppress_output { example.run }
11+
end
12+
13+
describe "#step" do
14+
let(:message) { "test message" }
15+
let(:common_options) { { abort_on_error: false } }
16+
17+
context "with retry_on_failure: true" do
18+
let(:options) { common_options.merge(retry_on_failure: true, wait: 0) }
19+
20+
it "retries block until success" do
21+
run_count = 0
22+
23+
command.step(message, **options) do
24+
run_count += 1
25+
true if run_count == 3
26+
end
27+
28+
expect(run_count).to eq(3)
29+
end
30+
31+
it "does not exceed default max_retry_count" do
32+
run_count = 0
33+
34+
command.step(message, **options) do
35+
run_count += 1
36+
false
37+
end
38+
39+
expect(run_count).to eq(1001)
40+
end
41+
42+
context "with max_retry_count option" do # rubocop:disable RSpec/MultipleMemoizedHelpers
43+
let(:options_with_max_retry_count) { common_options.merge(retry_on_failure: true, wait: 0, max_retry_count: 1) }
44+
45+
it "retries block specified times" do
46+
run_count = 0
47+
48+
command.step(message, **options_with_max_retry_count) do
49+
run_count += 1
50+
false
51+
end
52+
53+
expect(run_count).to eq(2)
54+
end
55+
end
56+
end
57+
58+
context "with retry_on_failure: false" do
59+
let(:options) { common_options.merge(retry_on_failure: false) }
60+
61+
it "does not retry block" do
62+
run_count = 0
63+
64+
command.step(message, **options) do
65+
run_count += 1
66+
false
67+
end
68+
69+
expect(run_count).to eq(1)
70+
end
71+
end
72+
end
73+
end

spec/support/command_helpers.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,16 @@ def spawn_cpflow_command(*args, stty_rows: nil, stty_cols: nil, wait_for_process
207207
end
208208
end
209209

210+
def suppress_output
211+
original_stderr = replace_stderr
212+
original_stdout = replace_stdout
213+
214+
yield
215+
ensure
216+
restore_stderr(original_stderr)
217+
restore_stdout(original_stdout)
218+
end
219+
210220
def replace_stderr
211221
original_stderr = $stderr
212222
$stderr = Tempfile.create

0 commit comments

Comments
 (0)