Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/selective-ruby-core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Selective
module Ruby
module Core
class Error < StandardError; end
class ConnectionLostError < StandardError; end

ROOT_GEM_PATH = Gem.loaded_specs["selective-ruby-core"].full_gem_path

Expand Down
59 changes: 37 additions & 22 deletions lib/selective/ruby/core/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module Core
class Controller
include Helper
@@selective_suppress_reporting = false
@@report_at_finish = {}

REQUIRED_CONFIGURATION = {
"host" => "SELECTIVE_HOST",
Expand All @@ -33,7 +34,7 @@ def start(reconnect: false)
handle_termination_signals(transport_pid)
wait_for_connectivity
run_main_loop
rescue NamedPipe::PipeClosedError
rescue ConnectionLostError
retry!
rescue => e
with_error_handling { raise e }
Expand All @@ -57,12 +58,16 @@ def self.suppress_reporting?
@@selective_suppress_reporting
end

def self.report_at_finish
@@report_at_finish
end

private

attr_reader :runner, :pipe, :transport_pid, :retries, :logger, :runner_id, :diff

def get_runner_id
runner_id = build_env.delete("runner_id")
runner_id = build_env["runner_id"]
return generate_runner_id if runner_id.nil? || runner_id.empty?

runner_id
Expand Down Expand Up @@ -90,10 +95,10 @@ def run_main_loop
def retry!
@retries += 1

with_error_handling { raise "Too many retries" } if retries > 4
with_error_handling { raise "Too many retries" } if retries > 10

puts("Retrying in #{retries} seconds...")
sleep(retries)
puts("Retrying in #{retries} seconds...") if debug?
sleep(retries > 4 ? 4 : retries)
kill_transport

pipe.reset!
Expand All @@ -109,29 +114,30 @@ def generate_runner_id
end

def transport_url(reconnect: false)
@transport_url ||= begin
api_key = build_env.delete("api_key")
host = build_env.delete("host")
run_id = build_env.delete("run_id")
run_attempt = build_env.delete("run_attempt")
base_transport_url_params[:reconnect] = true if reconnect
query_string = URI.encode_www_form(base_transport_url_params)
"#{build_env["host"]}/transport/websocket?#{query_string}"
end

def base_transport_url_params
@base_transport_url_params ||= begin
api_key = build_env["api_key"]
run_id = build_env["run_id"]
run_attempt = build_env["run_attempt"]

params = {
metadata = build_env.reject { |k,v| %w(host runner_id api_key run_id run_attempt).include?(k) }

{
"api_key" => api_key,
"run_id" => run_id,
"run_attempt" => run_attempt,
"api_key" => api_key,
"runner_id" => runner_id,
"language" => "ruby",
"core_version" => Selective::Ruby::Core::VERSION,
"framework" => runner.framework,
"framework_version" => runner.framework_version,
"framework_wrapper_version" => runner.wrapper_version,
}.merge(metadata: build_env.to_json)

prams[:reconnect] = true if reconnect

query_string = URI.encode_www_form(params)

"#{host}/transport/websocket?#{query_string}"
}.merge(metadata: metadata.to_json)
end
end

Expand Down Expand Up @@ -224,7 +230,7 @@ def kill_transport(signal: "TERM")

sleep(1)
end
rescue NamedPipe::PipeClosedError, IOError
rescue ConnectionLostError, IOError
# If the pipe is close, move straight to killing
# it forcefully.
end
Expand Down Expand Up @@ -270,8 +276,11 @@ def handle_run_test_cases(data)
runner.run_test_cases(data[:test_case_ids])
end

# Todo: Rename this command to match the method name
# on the runner wrapper. We should do something similar
# to normalize handle_print_notice and handle_print_message
def handle_remove_failed_test_case_result(data)
runner.remove_failed_test_case_result(data[:test_case_id])
runner.remove_test_case_result(data[:test_case_id])
end

def handle_print_message(data)
Expand All @@ -281,7 +290,13 @@ def handle_print_message(data)
def handle_close(data)
exit_status = data[:exit_status]
self.class.restore_reporting!
with_error_handling { runner.finish } unless exit_status.is_a?(Integer)

with_error_handling do
Selective::Ruby::Core::Controller.report_at_finish[:connection_retries] = @retries
write({type: "report_at_finish", data: Selective::Ruby::Core::Controller.report_at_finish})

runner.finish unless exit_status.is_a?(Integer)
end

kill_transport
pipe.delete_pipes
Expand Down
2 changes: 1 addition & 1 deletion lib/selective/ruby/core/file_correlator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class FileCorrelatorError < StandardError; end
FILE_CORRELATION_COLLECTOR_PATH = File.join(ROOT_GEM_PATH, "lib", "bin", "file_correlation_collector.sh")

def initialize(diff, num_commits, target_branch)
@diff = diff
@diff = diff.reject {|f| f =~ /^spec\// }
@num_commits = num_commits
@target_branch = target_branch
end
Expand Down
6 changes: 2 additions & 4 deletions lib/selective/ruby/core/named_pipe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ module Selective
module Ruby
module Core
class NamedPipe
class PipeClosedError < StandardError; end

attr_reader :read_pipe_path, :write_pipe_path

def initialize(read_pipe_path, write_pipe_path, skip_reset: false)
Expand Down Expand Up @@ -44,7 +42,7 @@ def write(message)
write_pipe.write("\n")
write_pipe.flush
rescue Errno::EPIPE
raise PipeClosedError
raise ConnectionLostError
end
end

Expand All @@ -54,7 +52,7 @@ def read
message = read_pipe.gets.chomp
rescue NoMethodError => e
if e.name == :chomp
raise PipeClosedError
raise ConnectionLostError
else
raise e
end
Expand Down
9 changes: 4 additions & 5 deletions spec/selective/ruby/core/controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@

it "handles the remove_failed_test_case_result command" do
test_case_id = "spec/abc/123_spec.rb"
allow(runner).to receive(:remove_failed_test_case_result)
allow(runner).to receive(:remove_test_case_result)

send_commands(controller, [
{command: "remove_failed_test_case_result", test_case_id: test_case_id}
])

expect(runner).to have_received(:remove_failed_test_case_result).once.with(test_case_id)
expect(runner).to have_received(:remove_test_case_result).once.with(test_case_id)
end

it "handles the print_message command" do
Expand All @@ -66,9 +66,9 @@
])
end

context "when a NamedPipe::PipeClosedError occurs" do
context "when a ConnectionLostError occurs" do
before do
allow(Selective::Ruby::Core::NamedPipe).to receive(:new).and_raise(Selective::Ruby::Core::NamedPipe::PipeClosedError)
allow(Selective::Ruby::Core::NamedPipe).to receive(:new).and_raise(Selective::Ruby::Core::ConnectionLostError)

# The retry method calls start again, so we have to do some fancy mocking
# to ensure we do not end up in an endless loop. Normally the process would
Expand All @@ -87,7 +87,6 @@

it "increments the retries counter" do
expect { controller.start }.to change { controller.retries }.by(1)
expect(controller).to have_received(:puts).with("Retrying in 1 seconds...")
end
end

Expand Down
4 changes: 2 additions & 2 deletions spec/selective/ruby/core/named_pipe_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
describe "#read" do
it "raises an error when the pipe has been closed" do
reverse_pipe.write_pipe.close
expect { named_pipe.read }.to raise_error(described_class::PipeClosedError)
expect { named_pipe.read }.to raise_error(Selective::Ruby::Core::ConnectionLostError)
end

it "raises NoMethodError if not chomp" do
Expand All @@ -26,7 +26,7 @@
describe "#write" do
it "raises an error when the pipe has been closed" do
reverse_pipe.read_pipe.close
expect { named_pipe.write("hello") }.to raise_error(described_class::PipeClosedError)
expect { named_pipe.write("hello") }.to raise_error(Selective::Ruby::Core::ConnectionLostError)
end
end

Expand Down