diff --git a/lib/selective-ruby-core.rb b/lib/selective-ruby-core.rb index 2377913..1192906 100644 --- a/lib/selective-ruby-core.rb +++ b/lib/selective-ruby-core.rb @@ -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 diff --git a/lib/selective/ruby/core/controller.rb b/lib/selective/ruby/core/controller.rb index 64208a4..261f762 100644 --- a/lib/selective/ruby/core/controller.rb +++ b/lib/selective/ruby/core/controller.rb @@ -8,6 +8,7 @@ module Core class Controller include Helper @@selective_suppress_reporting = false + @@report_at_finish = {} REQUIRED_CONFIGURATION = { "host" => "SELECTIVE_HOST", @@ -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 } @@ -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 @@ -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! @@ -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 @@ -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 @@ -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) @@ -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 diff --git a/lib/selective/ruby/core/file_correlator.rb b/lib/selective/ruby/core/file_correlator.rb index 1d71a4d..32a81f5 100644 --- a/lib/selective/ruby/core/file_correlator.rb +++ b/lib/selective/ruby/core/file_correlator.rb @@ -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 diff --git a/lib/selective/ruby/core/named_pipe.rb b/lib/selective/ruby/core/named_pipe.rb index 24fb303..47e5264 100644 --- a/lib/selective/ruby/core/named_pipe.rb +++ b/lib/selective/ruby/core/named_pipe.rb @@ -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) @@ -44,7 +42,7 @@ def write(message) write_pipe.write("\n") write_pipe.flush rescue Errno::EPIPE - raise PipeClosedError + raise ConnectionLostError end end @@ -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 diff --git a/spec/selective/ruby/core/controller_spec.rb b/spec/selective/ruby/core/controller_spec.rb index c2a2651..afc53e1 100644 --- a/spec/selective/ruby/core/controller_spec.rb +++ b/spec/selective/ruby/core/controller_spec.rb @@ -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 @@ -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 @@ -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 diff --git a/spec/selective/ruby/core/named_pipe_spec.rb b/spec/selective/ruby/core/named_pipe_spec.rb index a59a604..3d8d376 100644 --- a/spec/selective/ruby/core/named_pipe_spec.rb +++ b/spec/selective/ruby/core/named_pipe_spec.rb @@ -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 @@ -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