From ec9b1b1b09e232e90a6997b90cd5f2f1eaefd05f Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Thu, 25 Jan 2018 19:28:58 +0300 Subject: [PATCH 01/98] Fix Cucumber parallel formatter --- Gemfile | 3 +- .../cucumber/parallel_formatter.rb | 3 +- lib/report_portal/cucumber/parallel_report.rb | 34 ++++++++++++++----- lib/report_portal/cucumber/report.rb | 8 ++--- lib/reportportal.rb | 22 ++++++++++-- 5 files changed, 53 insertions(+), 17 deletions(-) diff --git a/Gemfile b/Gemfile index 9658cd7..4b45f0c 100644 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,8 @@ gem 'rest-client' gem 'rubytree', git: 'https://github.com/razboev/RubyTree' gem 'rspec' gem 'rake' -gem 'parallel_tests' +gem 'parallel_tests', '2.15' +gem 'sys-proctable' gem 'logging' gem 'log4r' diff --git a/lib/report_portal/cucumber/parallel_formatter.rb b/lib/report_portal/cucumber/parallel_formatter.rb index 3a7080d..721d6fa 100644 --- a/lib/report_portal/cucumber/parallel_formatter.rb +++ b/lib/report_portal/cucumber/parallel_formatter.rb @@ -30,8 +30,9 @@ def initialize(config) ENV['REPORT_PORTAL_USED'] = 'true' @queue = Queue.new + start_launch_time = ReportPortal.now @thread = Thread.new do - @report = ReportPortal::Cucumber::ParallelReport.new + @report = ReportPortal::Cucumber::ParallelReport.new(start_launch_time) loop do method_arr = @queue.pop @report.public_send(*method_arr) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 6a4b1c9..14ef37c 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -16,31 +16,35 @@ # You should have received a copy of the GNU Lesser General Public License # along with Report Portal. If not, see . +require 'sys/proctable' + require_relative 'report' module ReportPortal module Cucumber class ParallelReport < Report - FILE_WITH_LAUNCH_ID = Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{Process.ppid}.lck" - def parallel? true end - def initialize + def initialize(desired_time) @root_node = Tree::TreeNode.new('') - @last_used_time ||= 0 + ReportPortal.last_used_time = 0 if ParallelTests.first_process? - File.open(FILE_WITH_LAUNCH_ID, 'w') do |f| + File.open(file_with_launch_id, 'w') do |f| f.flock(File::LOCK_EX) - start_launch + start_launch(desired_time) f.write(ReportPortal.launch_id) f.flush f.flock(File::LOCK_UN) end else - File.open(FILE_WITH_LAUNCH_ID, 'r') do |f| + loop do + break if File.exist?(file_with_launch_id) + sleep 0.5 + end + File.open(file_with_launch_id, 'r') do |f| f.flock(File::LOCK_SH) ReportPortal.launch_id = f.read f.flock(File::LOCK_UN) @@ -54,7 +58,7 @@ def done(desired_time = ReportPortal.now) if ParallelTests.first_process? ParallelTests.wait_for_other_processes_to_finish - File.delete(FILE_WITH_LAUNCH_ID) + File.delete(file_with_launch_id) unless attach_to_launch? $stdout.puts "Finishing launch #{ReportPortal.launch_id}" @@ -64,6 +68,20 @@ def done(desired_time = ReportPortal.now) end end end + + def file_with_launch_id + Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{pid_of_parallel_tests}.lck" + end + + def pid_of_parallel_tests + pid = Process.pid + loop do + current_process = Sys::ProcTable.ps(pid) + raise 'Could not get parallel_cucumber in process tree' if current_process.nil? + return current_process.pid if current_process.cmdline[/bin(?:\/|\\)parallel_cucumber/] + pid = Sys::ProcTable.ps(pid).ppid + end + end end end end diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 70b42a5..7d40b9f 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -37,7 +37,7 @@ def attach_to_launch? end def initialize - @last_used_time = 0 + ReportPortal.last_used_time = 0 @root_node = Tree::TreeNode.new('') start_launch end @@ -160,10 +160,10 @@ def embed(src, mime_type, label, desired_time = ReportPortal.now) # * that process/thread can't start the next test until it's done with the previous one def time_to_send(desired_time) time_to_send = desired_time - if time_to_send <= @last_used_time - time_to_send = @last_used_time + 1 + if time_to_send <= ReportPortal.last_used_time + time_to_send = ReportPortal.last_used_time + 1 end - @last_used_time = time_to_send + ReportPortal.last_used_time = time_to_send end def same_feature_as_previous_test_case?(feature) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 6628f01..80b088c 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -41,7 +41,7 @@ module ReportPortal end class << self - attr_accessor :launch_id, :current_scenario + attr_accessor :launch_id, :current_scenario, :last_used_time def now (Time.now.to_f * 1000).to_i @@ -82,9 +82,18 @@ def start_item(item_node) item = item_node.content data = { start_time: item.start_time, name: item.name[0, 255], type: item.type.to_s, launch_id: @launch_id, description: item.description } data[:tags] = item.tags unless item.tags.empty? - do_request(url) do |resource| - JSON.parse(resource.post(data.to_json, content_type: :json, &@response_handler))['id'] + begin + url = "item" + url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? + response = resource[url].post(data.to_json, content_type: :json) + rescue RestClient::Exception => e + if JSON.parse(e.response)['message'][/Start time of child .+ item should be same or later than start time .+ of the parent/] + data[:start_time] += 1 + ReportPortal.last_used_time = data[:start_time] + retry + end end + JSON.parse(response)['id'] end def finish_item(item, status = nil, end_time = nil, force_issue = nil) @@ -184,6 +193,13 @@ def close_child_items(parent_id) private + def resource + props = { :headers => {:Authorization => "Bearer #{Settings.instance.uuid}"}} + verify_ssl = Settings.instance.disable_ssl_verification + props[:verify_ssl] = !verify_ssl unless verify_ssl.nil? + RestClient::Resource.new(Settings.instance.project_url, props) + end + def create_resource(url) props = { :headers => {:Authorization => "Bearer #{Settings.instance.uuid}"}} verify_ssl = Settings.instance.disable_ssl_verification From a41828b34d4654ff668806616c03ee0865ecb8ae Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Mon, 29 Jan 2018 17:52:14 +0300 Subject: [PATCH 02/98] Fix parallel_tests version and possible endless loop --- Gemfile | 2 +- lib/report_portal/cucumber/parallel_report.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 4b45f0c..9cde941 100644 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,7 @@ gem 'rest-client' gem 'rubytree', git: 'https://github.com/razboev/RubyTree' gem 'rspec' gem 'rake' -gem 'parallel_tests', '2.15' +gem 'parallel_tests' gem 'sys-proctable' gem 'logging' diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 14ef37c..65e6d84 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -40,8 +40,12 @@ def initialize(desired_time) f.flock(File::LOCK_UN) end else + start_time = monotonic_time loop do break if File.exist?(file_with_launch_id) + if monotonic_time - start_time > wait_time_for_launch_start + raise "File with launch id wasn't created during #{wait_time_for_launch_start} seconds" + end sleep 0.5 end File.open(file_with_launch_id, 'r') do |f| @@ -69,6 +73,8 @@ def done(desired_time = ReportPortal.now) end end + private + def file_with_launch_id Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{pid_of_parallel_tests}.lck" end @@ -82,6 +88,14 @@ def pid_of_parallel_tests pid = Sys::ProcTable.ps(pid).ppid end end + + def wait_time_for_launch_start + 60 + end + + def monotonic_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end end end end From 97e1f5d7b6cb87c6055409d1c3c118328be961d1 Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Tue, 30 Jan 2018 15:01:52 +0300 Subject: [PATCH 03/98] Unify common code in Cucumber Formatter and ParallelFormatter --- lib/report_portal/cucumber/formatter.rb | 43 +++++++++++++------ .../cucumber/parallel_formatter.rb | 28 +++--------- lib/report_portal/cucumber/parallel_report.rb | 1 + 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index 6d0ab48..91f9334 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -27,39 +27,54 @@ class Formatter def initialize(config) ENV['REPORT_PORTAL_USED'] = 'true' - @queue = Queue.new @thread = Thread.new do - @report = ReportPortal::Cucumber::Report.new + initialize_report loop do - method_arr = @queue.pop - @report.public_send(*method_arr) + method_arr = queue.pop + report.public_send(*method_arr) end end @thread.abort_on_exception = true @io = config.out_stream - [:test_case_started, :test_case_finished, :test_step_started, :test_step_finished].each do |event_name| - config.on_event event_name do |event| - @queue.push([event_name, event, ReportPortal.now]) - end - end - config.on_event :test_run_finished, &method(:on_test_run_finished) + handle_cucumber_events(config) end def puts(message) - @queue.push([:puts, message, ReportPortal.now]) + queue.push([:puts, message, ReportPortal.now]) @io.puts(message) @io.flush end def embed(*args) - @queue.push([:embed, *args, ReportPortal.now]) + queue.push([:embed, *args, ReportPortal.now]) + end + + private + + def queue + @queue ||= Queue.new + end + + def initialize_report + @report = ReportPortal::Cucumber::Report.new + end + + attr_reader :report + + def handle_cucumber_events(config) + [:test_case_started, :test_case_finished, :test_step_started, :test_step_finished].each do |event_name| + config.on_event(event_name) do |event| + queue.push([event_name, event, ReportPortal.now]) + end + end + config.on_event :test_run_finished, &method(:on_test_run_finished) end def on_test_run_finished(_event) - @queue.push([:done, ReportPortal.now]) - sleep 0.03 while !@queue.empty? || @queue.num_waiting == 0 # TODO: how to interrupt launch if the user aborted execution + queue.push([:done, ReportPortal.now]) + sleep 0.03 while !queue.empty? || queue.num_waiting == 0 # TODO: how to interrupt launch if the user aborted execution @thread.kill end end diff --git a/lib/report_portal/cucumber/parallel_formatter.rb b/lib/report_portal/cucumber/parallel_formatter.rb index 721d6fa..84a1f3c 100644 --- a/lib/report_portal/cucumber/parallel_formatter.rb +++ b/lib/report_portal/cucumber/parallel_formatter.rb @@ -18,36 +18,20 @@ require_relative 'formatter' require_relative 'parallel_report' -require 'parallel_tests/gherkin/io' module ReportPortal module Cucumber class ParallelFormatter < Formatter - #include ::ParallelTests::Gherkin::Io - # @api private def initialize(config) - ENV['REPORT_PORTAL_USED'] = 'true' - - @queue = Queue.new - start_launch_time = ReportPortal.now - @thread = Thread.new do - @report = ReportPortal::Cucumber::ParallelReport.new(start_launch_time) - loop do - method_arr = @queue.pop - @report.public_send(*method_arr) - end - end - @thread.abort_on_exception = true + @start_launch_time = ReportPortal.now + super(config) + end - @io = config.out_stream + private - [:test_case_started, :test_case_finished, :test_step_started, :test_step_finished].each do |event_name| - config.on_event event_name do |event| - @queue.push([event_name, event, ReportPortal.now]) - end - end - config.on_event :test_run_finished, &method(:on_test_run_finished) + def initialize_report + @report = ReportPortal::Cucumber::ParallelReport.new(@start_launch_time) end end end diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 65e6d84..8b6d4b0 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Report Portal. If not, see . +require 'parallel_tests' require 'sys/proctable' require_relative 'report' From 69da60cac1522c152ad6e8da9f8a5cae125bdbd8 Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Tue, 30 Jan 2018 15:55:05 +0300 Subject: [PATCH 04/98] Add debug logging to debug some issue --- lib/report_portal/cucumber/formatter.rb | 6 ++++++ lib/report_portal/cucumber/parallel_formatter.rb | 2 +- lib/report_portal/cucumber/parallel_report.rb | 10 ++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index 91f9334..7e7a94b 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -29,8 +29,10 @@ def initialize(config) @thread = Thread.new do initialize_report + logger.debug("Report initialized") loop do method_arr = queue.pop + logger.debug("Handled #{method_arr.first}") report.public_send(*method_arr) end end @@ -57,6 +59,10 @@ def queue @queue ||= Queue.new end + def logger + @logger ||= Logger.new("#{ENV['TEST_ENV_NUMBER']}_log_#{Time.now.to_i}.txt") + end + def initialize_report @report = ReportPortal::Cucumber::Report.new end diff --git a/lib/report_portal/cucumber/parallel_formatter.rb b/lib/report_portal/cucumber/parallel_formatter.rb index 84a1f3c..238b165 100644 --- a/lib/report_portal/cucumber/parallel_formatter.rb +++ b/lib/report_portal/cucumber/parallel_formatter.rb @@ -31,7 +31,7 @@ def initialize(config) private def initialize_report - @report = ReportPortal::Cucumber::ParallelReport.new(@start_launch_time) + @report = ReportPortal::Cucumber::ParallelReport.new(@start_launch_time, logger) end end end diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 8b6d4b0..f3e3fff 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -28,11 +28,12 @@ def parallel? true end - def initialize(desired_time) + def initialize(desired_time, logger) @root_node = Tree::TreeNode.new('') ReportPortal.last_used_time = 0 if ParallelTests.first_process? + logger.debug('Create launch file') File.open(file_with_launch_id, 'w') do |f| f.flock(File::LOCK_EX) start_launch(desired_time) @@ -40,20 +41,25 @@ def initialize(desired_time) f.flush f.flock(File::LOCK_UN) end + logger.debug('Created launch file') else + logger.debug('Wait for launch file started') start_time = monotonic_time loop do break if File.exist?(file_with_launch_id) + logger.debug('File does not yet exist') if monotonic_time - start_time > wait_time_for_launch_start raise "File with launch id wasn't created during #{wait_time_for_launch_start} seconds" end - sleep 0.5 + sleep 1.5 end + logger.debug('Wait for file to exist finished') File.open(file_with_launch_id, 'r') do |f| f.flock(File::LOCK_SH) ReportPortal.launch_id = f.read f.flock(File::LOCK_UN) end + logger.debug('Launch id was read from the file') end end From 75ad0f9311c021bc19895aaa83e695d47bdd07a7 Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Tue, 30 Jan 2018 16:24:04 +0300 Subject: [PATCH 05/98] Set report_on_exception = true for Report Portal thread on Ruby 2.4+ --- lib/report_portal/cucumber/formatter.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index 7e7a94b..278f14f 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -36,7 +36,11 @@ def initialize(config) report.public_send(*method_arr) end end - @thread.abort_on_exception = true + if @thread.respond_to?(:report_on_exception) + @thread.report_on_exception = true + else + @thread.abort_on_exception = true + end @io = config.out_stream From 3def39ee58526f7840d0ae67c67208a178d843b5 Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Tue, 30 Jan 2018 18:15:31 +0300 Subject: [PATCH 06/98] Add more debug logging --- lib/report_portal/cucumber/formatter.rb | 2 +- lib/report_portal/cucumber/parallel_report.rb | 1 + lib/report_portal/cucumber/report.rb | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index 278f14f..0d3bc61 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -36,7 +36,7 @@ def initialize(config) report.public_send(*method_arr) end end - if @thread.respond_to?(:report_on_exception) + if @thread.respond_to?(:report_on_exception) # report_on_exception defined only on Ruby 2.4 + @thread.report_on_exception = true else @thread.abort_on_exception = true diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index f3e3fff..29df9c9 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -29,6 +29,7 @@ def parallel? end def initialize(desired_time, logger) + @logger = logger @root_node = Tree::TreeNode.new('') ReportPortal.last_used_time = 0 diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 7d40b9f..db5eae4 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -171,6 +171,7 @@ def same_feature_as_previous_test_case?(feature) end def start_feature_with_parentage(feature, desired_time) + @logger.debug("Start feature #{feature}") parent_node = @root_node child_node = nil path_components = feature.location.file.split(File::SEPARATOR) @@ -188,19 +189,23 @@ def start_feature_with_parentage(feature, desired_time) tags = feature.tags.map(&:name) type = :TEST end + @logger.debug("Start item #{name}") # TODO: multithreading # Parallel formatter always executes scenarios inside the same feature in the same process if parallel? && index < path_components.size - 1 && # is folder? (id_of_created_item = ReportPortal.item_id_of(name, parent_node)) # get id for folder from report portal # get child id from other process + @logger.debug("Id of parent item from another process: #{id_of_created_item}") item = ReportPortal::TestItem.new(name, type, id_of_created_item, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node + @logger.debug("Item already created") else item = ReportPortal::TestItem.new(name, type, nil, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node item.id = ReportPortal.start_item(child_node) # TODO: multithreading + @logger.debug("Item #{item.id} started") end end parent_node = child_node From 25c5159f729c06364321c0236f4fe06112e9a4bd Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Tue, 30 Jan 2018 18:40:19 +0300 Subject: [PATCH 07/98] Remove retrying of requests to Report Portal --- lib/report_portal/cucumber/report.rb | 1 + lib/reportportal.rb | 115 +++++++++------------------ 2 files changed, 39 insertions(+), 77 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index db5eae4..73e066d 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -204,6 +204,7 @@ def start_feature_with_parentage(feature, desired_time) item = ReportPortal::TestItem.new(name, type, nil, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node + @logger.debug("Try to start item in RP") item.id = ReportPortal.start_item(child_node) # TODO: multithreading @logger.debug("Item #{item.id} started") end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 80b088c..8d8cc9e 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -29,17 +29,6 @@ module ReportPortal TestItem = Struct.new(:name, :type, :id, :start_time, :description, :closed, :tags) LOG_LEVELS = { error: 'ERROR', warn: 'WARN', info: 'INFO', debug: 'DEBUG', trace: 'TRACE', fatal: 'FATAL', unknown: 'UNKNOWN' } - @response_handler = proc do |response, request, result, &block| - if (200..207).include? response.code - response - else - p "ReportPortal API returned #{response}" - p "Offending request method/URL: #{request.args[:method].upcase} #{request.args[:url]}" - p "Offending request payload: #{request.args[:payload]}}" - response.return!(request, result, &block) - end - end - class << self attr_accessor :launch_id, :current_scenario, :last_used_time @@ -61,44 +50,36 @@ def status_to_level(status) end def start_launch(description, start_time = now) - url = "#{Settings.instance.project_url}/launch" data = { name: Settings.instance.launch, start_time: start_time, tags: Settings.instance.tags, description: description, mode: Settings.instance.launch_mode } - @launch_id = do_request(url) do |resource| - JSON.parse(resource.post(data.to_json, content_type: :json, &@response_handler))['id'] - end + response = project_resource['launch'].post(data.to_json) + @launch_id = JSON.parse(response)['id'] end def finish_launch(end_time = now) - url = "#{Settings.instance.project_url}/launch/#{@launch_id}/finish" data = { end_time: end_time } - do_request(url) do |resource| - resource.put data.to_json, content_type: :json, &@response_handler - end + project_resource["launch/#{@launch_id}/finish"].put(data.to_json) end def start_item(item_node) - url = "#{Settings.instance.project_url}/item" - url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? item = item_node.content data = { start_time: item.start_time, name: item.name[0, 255], type: item.type.to_s, launch_id: @launch_id, description: item.description } data[:tags] = item.tags unless item.tags.empty? begin - url = "item" + url = 'item' url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? - response = resource[url].post(data.to_json, content_type: :json) + response = project_resource[url].post(data.to_json) rescue RestClient::Exception => e - if JSON.parse(e.response)['message'][/Start time of child .+ item should be same or later than start time .+ of the parent/] - data[:start_time] += 1 - ReportPortal.last_used_time = data[:start_time] - retry - end + response_message = JSON.parse(e.response)['message'] + raise unless response_message[/Start time of child .+ item should be same or later than start time .+ of the parent/] + data[:start_time] += 1 + ReportPortal.last_used_time = data[:start_time] + retry end JSON.parse(response)['id'] end def finish_item(item, status = nil, end_time = nil, force_issue = nil) unless item.nil? || item.id.nil? || item.closed - url = "#{Settings.instance.project_url}/item/#{item.id}" data = { end_time: end_time.nil? ? now : end_time } data[:status] = status unless status.nil? if force_issue && status != :passed # TODO: check for :passed status is probably not needed @@ -106,9 +87,7 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) elsif status == :skipped data[:issue] = { issue_type: 'NOT_ISSUE' } end - do_request(url) do |resource| - resource.put data.to_json, content_type: :json, &@response_handler - end + project_resource["item/#{item.id}"].put(data.to_json) item.closed = true end end @@ -117,16 +96,12 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) def send_log(status, message, time) unless @current_scenario.nil? || @current_scenario.closed # it can be nil if scenario outline in expand mode is executed - url = "#{Settings.instance.project_url}/log" data = { item_id: @current_scenario.id, time: time, level: status_to_level(status), message: message.to_s } - do_request(url) do |resource| - resource.post(data.to_json, content_type: :json, &@response_handler) - end + project_resource['log'].post(data.to_json) end end - def send_file(status, path, label = nil, time = now, mime_type='image/png') - url = "#{Settings.instance.project_url}/log" + def send_file(status, path, label = nil, time = now, mime_type = 'image/png') unless File.file?(path) extension = ".#{MIME::Types[mime_type].first.extensions.first}" temp = Tempfile.open(['file',extension]) @@ -139,39 +114,35 @@ def send_file(status, path, label = nil, time = now, mime_type='image/png') label ||= File.basename(file) json = { level: status_to_level(status), message: label, item_id: @current_scenario.id, time: time, file: { name: File.basename(file) } } data = { :json_request_part => [json].to_json, label => file, :multipart => true, :content_type => 'application/json' } - do_request(url) do |resource| - resource.post(data, { content_type: 'multipart/form-data' }, &@response_handler) - end + project_resource['log'].post(data, content_type: 'multipart/form-data') end end # needed for parallel formatter def item_id_of(name, parent_node) if parent_node.is_root? # folder without parent folder - url = "#{Settings.instance.project_url}/item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{URI.escape(name)}&filter.size.path=0" + url = "item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{URI.escape(name)}&filter.size.path=0" else - url = "#{Settings.instance.project_url}/item?filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" + url = "item?filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" end - do_request(url) do |resource| - data = JSON.parse(resource.get) - if data.key? 'content' - data['content'].empty? ? nil : data['content'][0]['id'] - else - nil # item isn't started yet - end + data = JSON.parse(project_resource[url].get) + if data.key? 'content' + data['content'].empty? ? nil : data['content'][0]['id'] + else + nil # item isn't started yet end end # needed for parallel formatter def close_child_items(parent_id) if parent_id.nil? - url = "#{Settings.instance.project_url}/item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" + url = "item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" else - url = "#{Settings.instance.project_url}/item?filter.eq.parent=#{parent_id}&page.page=1&page.size=100" + url = "item?filter.eq.parent=#{parent_id}&page.page=1&page.size=100" end ids = [] loop do - response = do_request(url) { |r| JSON.parse(r.get) } + response = JSON.parse(project_resource[url].get) if response.key?('links') link = response['links'].find { |i| i['rel'] == 'next' } url = link.nil? ? nil : link['href'] @@ -193,31 +164,21 @@ def close_child_items(parent_id) private - def resource - props = { :headers => {:Authorization => "Bearer #{Settings.instance.uuid}"}} - verify_ssl = Settings.instance.disable_ssl_verification - props[:verify_ssl] = !verify_ssl unless verify_ssl.nil? - RestClient::Resource.new(Settings.instance.project_url, props) - end - - def create_resource(url) - props = { :headers => {:Authorization => "Bearer #{Settings.instance.uuid}"}} + def project_resource + options = {} + options[:headers] = { + :Authorization => "Bearer #{Settings.instance.uuid}", + content_type: :json + } verify_ssl = Settings.instance.disable_ssl_verification - props[:verify_ssl] = !verify_ssl unless verify_ssl.nil? - RestClient::Resource.new url, props - end - - def do_request(url) - resource = create_resource(url) - tries = 3 - begin - yield resource - rescue - p "Request to #{url} produced an exception: #{$!.class}: #{$!}" - $!.backtrace.each { |l| p l } - retry unless (tries -= 1).zero? - p "Failed to execute request to #{url} after 3 attempts." - nil + options[:verify_ssl] = !verify_ssl unless verify_ssl.nil? + RestClient::Resource.new(Settings.instance.project_url, options) do |response, request, _, &block| + unless (200..207).include?(response.code) + p "ReportPortal API returned #{response}" + p "Offending request method/URL: #{request.args[:method].upcase} #{request.args[:url]}" + p "Offending request payload: #{request.args[:payload]}}" + end + response.return!(&block) end end end From f18bb5ea6b09142755c6faf7ff73b0a4c5e0d71e Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Tue, 30 Jan 2018 19:16:58 +0300 Subject: [PATCH 08/98] Set last_used_time to a time of the parent item --- lib/reportportal.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 8d8cc9e..3f32e9d 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -70,8 +70,10 @@ def start_item(item_node) response = project_resource[url].post(data.to_json) rescue RestClient::Exception => e response_message = JSON.parse(e.response)['message'] - raise unless response_message[/Start time of child .+ item should be same or later than start time .+ of the parent/] - data[:start_time] += 1 + m = response_message.match(/Start time of child (.+) item should be same or later than start time \[\'(.+)\'\] of the parent/) + raise unless m + time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') + data[:start_time] = (time.to_f * 1000).to_i + 1000 ReportPortal.last_used_time = data[:start_time] retry end From 396a9617cf7e566923ec2c093fd9b2de6918f584 Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Wed, 31 Jan 2018 15:18:51 +0300 Subject: [PATCH 09/98] Remove log debug statements --- lib/report_portal/cucumber/formatter.rb | 6 ------ lib/report_portal/cucumber/parallel_formatter.rb | 2 +- lib/report_portal/cucumber/parallel_report.rb | 9 +-------- lib/report_portal/cucumber/report.rb | 6 ------ 4 files changed, 2 insertions(+), 21 deletions(-) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index 0d3bc61..dec478e 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -29,10 +29,8 @@ def initialize(config) @thread = Thread.new do initialize_report - logger.debug("Report initialized") loop do method_arr = queue.pop - logger.debug("Handled #{method_arr.first}") report.public_send(*method_arr) end end @@ -63,10 +61,6 @@ def queue @queue ||= Queue.new end - def logger - @logger ||= Logger.new("#{ENV['TEST_ENV_NUMBER']}_log_#{Time.now.to_i}.txt") - end - def initialize_report @report = ReportPortal::Cucumber::Report.new end diff --git a/lib/report_portal/cucumber/parallel_formatter.rb b/lib/report_portal/cucumber/parallel_formatter.rb index 238b165..84a1f3c 100644 --- a/lib/report_portal/cucumber/parallel_formatter.rb +++ b/lib/report_portal/cucumber/parallel_formatter.rb @@ -31,7 +31,7 @@ def initialize(config) private def initialize_report - @report = ReportPortal::Cucumber::ParallelReport.new(@start_launch_time, logger) + @report = ReportPortal::Cucumber::ParallelReport.new(@start_launch_time) end end end diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 29df9c9..0f85abf 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -28,13 +28,11 @@ def parallel? true end - def initialize(desired_time, logger) - @logger = logger + def initialize(desired_time) @root_node = Tree::TreeNode.new('') ReportPortal.last_used_time = 0 if ParallelTests.first_process? - logger.debug('Create launch file') File.open(file_with_launch_id, 'w') do |f| f.flock(File::LOCK_EX) start_launch(desired_time) @@ -42,25 +40,20 @@ def initialize(desired_time, logger) f.flush f.flock(File::LOCK_UN) end - logger.debug('Created launch file') else - logger.debug('Wait for launch file started') start_time = monotonic_time loop do break if File.exist?(file_with_launch_id) - logger.debug('File does not yet exist') if monotonic_time - start_time > wait_time_for_launch_start raise "File with launch id wasn't created during #{wait_time_for_launch_start} seconds" end sleep 1.5 end - logger.debug('Wait for file to exist finished') File.open(file_with_launch_id, 'r') do |f| f.flock(File::LOCK_SH) ReportPortal.launch_id = f.read f.flock(File::LOCK_UN) end - logger.debug('Launch id was read from the file') end end diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 73e066d..7d40b9f 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -171,7 +171,6 @@ def same_feature_as_previous_test_case?(feature) end def start_feature_with_parentage(feature, desired_time) - @logger.debug("Start feature #{feature}") parent_node = @root_node child_node = nil path_components = feature.location.file.split(File::SEPARATOR) @@ -189,24 +188,19 @@ def start_feature_with_parentage(feature, desired_time) tags = feature.tags.map(&:name) type = :TEST end - @logger.debug("Start item #{name}") # TODO: multithreading # Parallel formatter always executes scenarios inside the same feature in the same process if parallel? && index < path_components.size - 1 && # is folder? (id_of_created_item = ReportPortal.item_id_of(name, parent_node)) # get id for folder from report portal # get child id from other process - @logger.debug("Id of parent item from another process: #{id_of_created_item}") item = ReportPortal::TestItem.new(name, type, id_of_created_item, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node - @logger.debug("Item already created") else item = ReportPortal::TestItem.new(name, type, nil, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node - @logger.debug("Try to start item in RP") item.id = ReportPortal.start_item(child_node) # TODO: multithreading - @logger.debug("Item #{item.id} started") end end parent_node = child_node From e1c4805d7181658da243ce1872e4218a7b88cc0a Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Thu, 1 Feb 2018 12:52:41 +0300 Subject: [PATCH 10/98] Set description to cmd args of parallel_tests process --- lib/report_portal/cucumber/parallel_report.rb | 18 ++++++++++++------ lib/report_portal/cucumber/report.rb | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 0f85abf..2a7885f 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -31,11 +31,12 @@ def parallel? def initialize(desired_time) @root_node = Tree::TreeNode.new('') ReportPortal.last_used_time = 0 + set_parallel_tests_vars if ParallelTests.first_process? File.open(file_with_launch_id, 'w') do |f| f.flock(File::LOCK_EX) - start_launch(desired_time) + start_launch(desired_time, @cmd_args_of_parallel_tests) f.write(ReportPortal.launch_id) f.flush f.flock(File::LOCK_UN) @@ -47,7 +48,7 @@ def initialize(desired_time) if monotonic_time - start_time > wait_time_for_launch_start raise "File with launch id wasn't created during #{wait_time_for_launch_start} seconds" end - sleep 1.5 + sleep 0.5 end File.open(file_with_launch_id, 'r') do |f| f.flock(File::LOCK_SH) @@ -77,15 +78,20 @@ def done(desired_time = ReportPortal.now) private def file_with_launch_id - Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{pid_of_parallel_tests}.lck" + Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lck" end - def pid_of_parallel_tests + def set_parallel_tests_vars pid = Process.pid loop do current_process = Sys::ProcTable.ps(pid) - raise 'Could not get parallel_cucumber in process tree' if current_process.nil? - return current_process.pid if current_process.cmdline[/bin(?:\/|\\)parallel_cucumber/] + raise 'Could not find parallel_cucumber/parallel_test in ancestors of current process' if current_process.nil? + match = current_process.cmdline.match(/bin(?:\/|\\)parallel_(?:cucumber|test)(.+)/) + if match + @pid_of_parallel_tests = current_process.pid + @cmd_args_of_parallel_tests = match[1].strip.split + break + end pid = Sys::ProcTable.ps(pid).ppid end end diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 7d40b9f..78182be 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -42,7 +42,7 @@ def initialize start_launch end - def start_launch(desired_time = ReportPortal.now) + def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) if attach_to_launch? ReportPortal.launch_id = if ReportPortal::Settings.instance.launch_id @@ -54,7 +54,7 @@ def start_launch(desired_time = ReportPortal.now) $stdout.puts "Attaching to launch #{ReportPortal.launch_id}" else description = ReportPortal::Settings.instance.description - description ||= ARGV.map { |arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]") }.join(' ') + description ||= cmd_args.map { |arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]") }.join(' ') ReportPortal.start_launch(description, time_to_send(desired_time)) end end From 623fd05d10abc960de6223738df6288bcb44b673 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:26:46 -0500 Subject: [PATCH 11/98] Pin parallel_tests Gem to 2.15.0 and sys-proctable Gem to 1.1.5 --- Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 9cde941..0ee81e1 100644 --- a/Gemfile +++ b/Gemfile @@ -23,8 +23,8 @@ gem 'rest-client' gem 'rubytree', git: 'https://github.com/razboev/RubyTree' gem 'rspec' gem 'rake' -gem 'parallel_tests' -gem 'sys-proctable' +gem 'parallel_tests', '2.15.0' +gem 'sys-proctable', '1.1.5' gem 'logging' gem 'log4r' From 80c43801608bfa21df5511964745b4f252da9fa3 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:28:07 -0500 Subject: [PATCH 12/98] Fix URLs to include required "filter.eq.launch" parameter --- lib/reportportal.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 3f32e9d..8962759 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -125,7 +125,7 @@ def item_id_of(name, parent_node) if parent_node.is_root? # folder without parent folder url = "item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{URI.escape(name)}&filter.size.path=0" else - url = "item?filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" + url = "item?filter.eq.launch=#{@launch_id}&filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" end data = JSON.parse(project_resource[url].get) if data.key? 'content' @@ -140,7 +140,7 @@ def close_child_items(parent_id) if parent_id.nil? url = "item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" else - url = "item?filter.eq.parent=#{parent_id}&page.page=1&page.size=100" + url = "item?filter.eq.launch=#{@launch_id}&filter.eq.parent=#{parent_id}&page.page=1&page.size=100" end ids = [] loop do From 11531810f475888fcb1b42b0dd584cc9d360ea6c Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:29:15 -0500 Subject: [PATCH 13/98] Fix matcher to detect when start time needs to be updated --- lib/reportportal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 8962759..ac4332c 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -70,7 +70,7 @@ def start_item(item_node) response = project_resource[url].post(data.to_json) rescue RestClient::Exception => e response_message = JSON.parse(e.response)['message'] - m = response_message.match(/Start time of child (.+) item should be same or later than start time \[\'(.+)\'\] of the parent/) + m = response_message.match(/Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'/) raise unless m time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') data[:start_time] = (time.to_f * 1000).to_i + 1000 From 05b83ba2638cd6f3c4f8728eb3d120118ddcc8a8 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:30:20 -0500 Subject: [PATCH 14/98] Clean up formatting in report.rb --- lib/report_portal/cucumber/report.rb | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 78182be..19d9344 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -28,6 +28,7 @@ module ReportPortal module Cucumber # @api private class Report + def parallel? false end @@ -45,16 +46,16 @@ def initialize def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) if attach_to_launch? ReportPortal.launch_id = - if ReportPortal::Settings.instance.launch_id - ReportPortal::Settings.instance.launch_id - else - file_path = ReportPortal::Settings.instance.file_with_launch_id || (Pathname(Dir.tmpdir) + 'rp_launch_id.tmp') - File.read(file_path) - end + if ReportPortal::Settings.instance.launch_id + ReportPortal::Settings.instance.launch_id + else + file_path = ReportPortal::Settings.instance.file_with_launch_id || (Pathname(Dir.tmpdir) + 'rp_launch_id.tmp') + File.read(file_path) + end $stdout.puts "Attaching to launch #{ReportPortal.launch_id}" else description = ReportPortal::Settings.instance.description - description ||= cmd_args.map { |arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]") }.join(' ') + description ||= cmd_args.map {|arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]")}.join(' ') ReportPortal.start_launch(description, time_to_send(desired_time)) end end @@ -98,7 +99,7 @@ def test_step_started(event, desired_time = ReportPortal.now) if step_source.multiline_arg.doc_string? message << %(\n"""\n#{step_source.multiline_arg.content}\n""") elsif step_source.multiline_arg.data_table? - message << step_source.multiline_arg.raw.reduce("\n") { |acc, row| acc << "| #{row.join(' | ')} |\n" } + message << step_source.multiline_arg.raw.reduce("\n") {|acc, row| acc << "| #{row.join(' | ')} |\n"} end ReportPortal.send_log(:trace, message, time_to_send(desired_time)) end @@ -120,7 +121,7 @@ def test_step_finished(event, desired_time = ReportPortal.now) end if status != :passed - log_level = (status == :skipped)? :warn : :error + log_level = (status == :skipped) ? :warn : :error step_type = if step?(test_step) 'Step' else @@ -147,7 +148,7 @@ def puts(message, desired_time = ReportPortal.now) end def embed(src, mime_type, label, desired_time = ReportPortal.now) - ReportPortal.send_file(:info, src, label, time_to_send(desired_time),mime_type) + ReportPortal.send_file(:info, src, label, time_to_send(desired_time), mime_type) end private @@ -183,8 +184,9 @@ def start_feature_with_parentage(feature, desired_time) tags = [] type = :SUITE else + # TODO: Consider adding feature description and comments. name = "#{feature.keyword}: #{feature.name}" - description = feature.file # TODO: consider adding feature description and comments + description = feature.file tags = feature.tags.map(&:name) type = :TEST end @@ -200,7 +202,7 @@ def start_feature_with_parentage(feature, desired_time) item = ReportPortal::TestItem.new(name, type, nil, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node - item.id = ReportPortal.start_item(child_node) # TODO: multithreading + item.id = ReportPortal.start_item(child_node) end end parent_node = child_node From 62b388da4eb50cddb3c0de62701070575e2376b2 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:31:15 -0500 Subject: [PATCH 15/98] Implement a folder creation lock file to address duplicate folders being created --- lib/report_portal/cucumber/parallel_report.rb | 10 ++++++ lib/report_portal/cucumber/report.rb | 31 ++++++++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 2a7885f..3e75915 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -18,12 +18,15 @@ require 'parallel_tests' require 'sys/proctable' +require 'fileutils' require_relative 'report' module ReportPortal module Cucumber class ParallelReport < Report + $folder_creation_tracking_file = ".reportportal/folder_creation_tracking.lck" + def parallel? true end @@ -41,6 +44,13 @@ def initialize(desired_time) f.flush f.flock(File::LOCK_UN) end + $folder_creation_tracking_file = ".reportportal/folder_creation_tracking_#{ReportPortal.launch_id}.lck" + FileUtils.mkdir_p('.reportportal') + File.open($folder_creation_tracking_file, 'w') do |f| + f.flock(File::LOCK_EX) + f.flush + f.flock(File::LOCK_UN) + end else start_time = monotonic_time loop do diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 19d9344..e798753 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -190,11 +190,32 @@ def start_feature_with_parentage(feature, desired_time) tags = feature.tags.map(&:name) type = :TEST end - # TODO: multithreading # Parallel formatter always executes scenarios inside the same feature in the same process - if parallel? && - index < path_components.size - 1 && # is folder? - (id_of_created_item = ReportPortal.item_id_of(name, parent_node)) # get id for folder from report portal - # get child id from other process + is_created = false + if parallel? && name.include?("Folder:") + folder_name = name.gsub("Folder: ", "") + $folder_creation_tracking_file = ".reportportal/folder_creation_tracking_#{ReportPortal.launch_id}.lck" + File.open($folder_creation_tracking_file, 'r+') do |f| + f.flock(File::LOCK_SH) + report_portal_folders = f.read + if report_portal_folders + report_portal_folders_array = report_portal_folders.split(/\n/) + if report_portal_folders_array.include?(folder_name) + is_created = true + end + end + f.flock(File::LOCK_UN) + end + unless is_created + File.open($folder_creation_tracking_file, 'a') do |f| + f.flock(File::LOCK_EX) + f.write("\n#{folder_name}") + f.flush + f.flock(File::LOCK_UN) + end + end + end + if parallel? && index < path_components.size - 1 && is_created + id_of_created_item = ReportPortal.item_id_of(name, parent_node) item = ReportPortal::TestItem.new(name, type, id_of_created_item, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node From 4ea36e63d2981d44bf8b4415a75888e015116909 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:32:58 -0500 Subject: [PATCH 16/98] Delay each process by its ID number to create a staggered start to reporting in Report Portal (addresses load issues) --- lib/report_portal/cucumber/parallel_report.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 3e75915..5ac1355 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -65,6 +65,8 @@ def initialize(desired_time) ReportPortal.launch_id = f.read f.flock(File::LOCK_UN) end + sleep_time = ENV['TEST_ENV_NUMBER'].to_i + sleep(ENV['TEST_ENV_NUMBER'].to_i) end end From 3c58d208358e4b05d662d35d7f3cf3231e300655 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:33:12 -0500 Subject: [PATCH 17/98] Formatting and cleanup in parallel_report.rb --- lib/report_portal/cucumber/parallel_report.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 5ac1355..0f0a8fe 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -35,7 +35,6 @@ def initialize(desired_time) @root_node = Tree::TreeNode.new('') ReportPortal.last_used_time = 0 set_parallel_tests_vars - if ParallelTests.first_process? File.open(file_with_launch_id, 'w') do |f| f.flock(File::LOCK_EX) @@ -56,7 +55,7 @@ def initialize(desired_time) loop do break if File.exist?(file_with_launch_id) if monotonic_time - start_time > wait_time_for_launch_start - raise "File with launch id wasn't created during #{wait_time_for_launch_start} seconds" + raise "File with launch ID wasn't created after waiting #{wait_time_for_launch_start} seconds" end sleep 0.5 end From 580850b20f39413a93e0a0f1e849fcab4bcd9c00 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Tue, 19 Mar 2019 16:23:22 -0400 Subject: [PATCH 18/98] Use temporary directory for tracking folder creation --- lib/report_portal/cucumber/parallel_report.rb | 7 +++---- lib/report_portal/cucumber/report.rb | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 0f0a8fe..b0de7ee 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -25,7 +25,7 @@ module ReportPortal module Cucumber class ParallelReport < Report - $folder_creation_tracking_file = ".reportportal/folder_creation_tracking.lck" + $folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" def parallel? true @@ -43,9 +43,8 @@ def initialize(desired_time) f.flush f.flock(File::LOCK_UN) end - $folder_creation_tracking_file = ".reportportal/folder_creation_tracking_#{ReportPortal.launch_id}.lck" - FileUtils.mkdir_p('.reportportal') - File.open($folder_creation_tracking_file, 'w') do |f| + $folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" + File.open($folder_creation_tracking_file, 'w+') do |f| f.flock(File::LOCK_EX) f.flush f.flock(File::LOCK_UN) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index e798753..6d5958d 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -193,7 +193,7 @@ def start_feature_with_parentage(feature, desired_time) is_created = false if parallel? && name.include?("Folder:") folder_name = name.gsub("Folder: ", "") - $folder_creation_tracking_file = ".reportportal/folder_creation_tracking_#{ReportPortal.launch_id}.lck" + $folder_creation_tracking_file = (Pathname(Dir.tmpdir)) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" File.open($folder_creation_tracking_file, 'r+') do |f| f.flock(File::LOCK_SH) report_portal_folders = f.read From b188a3421f6f6a6c3335167262edd3bad503ea47 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Tue, 19 Mar 2019 16:23:35 -0400 Subject: [PATCH 19/98] Add commenting around why we are delaying threads --- lib/report_portal/cucumber/parallel_report.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index b0de7ee..053b770 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -64,7 +64,7 @@ def initialize(desired_time) f.flock(File::LOCK_UN) end sleep_time = ENV['TEST_ENV_NUMBER'].to_i - sleep(ENV['TEST_ENV_NUMBER'].to_i) + sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision end end From 10fb831255f5ec2629ccd9370ebe7ae9d6b7ed62 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Tue, 19 Mar 2019 16:27:34 -0400 Subject: [PATCH 20/98] Make sure to use a "full path" for folder creation tracking --- lib/report_portal/cucumber/report.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 6d5958d..bef54ea 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -175,6 +175,7 @@ def start_feature_with_parentage(feature, desired_time) parent_node = @root_node child_node = nil path_components = feature.location.file.split(File::SEPARATOR) + path_components_no_feature = feature.location.file.split(File::SEPARATOR)[0...path_components.size - 1] path_components.each_with_index do |path_component, index| child_node = parent_node[path_component] unless child_node # if child node was not created yet @@ -193,13 +194,20 @@ def start_feature_with_parentage(feature, desired_time) is_created = false if parallel? && name.include?("Folder:") folder_name = name.gsub("Folder: ", "") + folder_name_for_tracker = "./#{folder_name}" # create a full path file name to use for tracking + if index > 0 + folder_name_for_tracker = "./" + for path_index in (0...path_components_no_feature.length) + folder_name_for_tracker += "#{path_components_no_feature[path_index]}/" + end + end $folder_creation_tracking_file = (Pathname(Dir.tmpdir)) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" File.open($folder_creation_tracking_file, 'r+') do |f| f.flock(File::LOCK_SH) report_portal_folders = f.read if report_portal_folders report_portal_folders_array = report_portal_folders.split(/\n/) - if report_portal_folders_array.include?(folder_name) + if report_portal_folders_array.include?(folder_name_for_tracker) is_created = true end end @@ -208,7 +216,7 @@ def start_feature_with_parentage(feature, desired_time) unless is_created File.open($folder_creation_tracking_file, 'a') do |f| f.flock(File::LOCK_EX) - f.write("\n#{folder_name}") + f.write("\n#{folder_name_for_tracker}") f.flush f.flock(File::LOCK_UN) end From 357e707d4a351ac42df74083661d62a9f288c34e Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 12 Apr 2019 15:38:53 -0400 Subject: [PATCH 21/98] Switch to use class instance variables instead of global variable for folder creation tracking lock file --- lib/report_portal/cucumber/parallel_report.rb | 5 ++--- lib/report_portal/cucumber/report.rb | 8 +++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 053b770..6dba4c4 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -25,7 +25,6 @@ module ReportPortal module Cucumber class ParallelReport < Report - $folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" def parallel? true @@ -43,8 +42,8 @@ def initialize(desired_time) f.flush f.flock(File::LOCK_UN) end - $folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" - File.open($folder_creation_tracking_file, 'w+') do |f| + @folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" + File.open(@folder_creation_tracking_file, 'w+') do |f| f.flock(File::LOCK_EX) f.flush f.flock(File::LOCK_UN) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index bef54ea..7c93287 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -29,6 +29,8 @@ module Cucumber # @api private class Report + @folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" + def parallel? false end @@ -201,8 +203,8 @@ def start_feature_with_parentage(feature, desired_time) folder_name_for_tracker += "#{path_components_no_feature[path_index]}/" end end - $folder_creation_tracking_file = (Pathname(Dir.tmpdir)) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" - File.open($folder_creation_tracking_file, 'r+') do |f| + @folder_creation_tracking_file = (Pathname(Dir.tmpdir)) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" + File.open(@folder_creation_tracking_file, 'r+') do |f| f.flock(File::LOCK_SH) report_portal_folders = f.read if report_portal_folders @@ -214,7 +216,7 @@ def start_feature_with_parentage(feature, desired_time) f.flock(File::LOCK_UN) end unless is_created - File.open($folder_creation_tracking_file, 'a') do |f| + File.open(@folder_creation_tracking_file, 'a') do |f| f.flock(File::LOCK_EX) f.write("\n#{folder_name_for_tracker}") f.flush From c540c01a108abe2f7629c620de0c7bf025897854 Mon Sep 17 00:00:00 2001 From: vveliev Date: Fri, 24 May 2019 14:17:29 -0400 Subject: [PATCH 22/98] work in progress, refactoring lock generation for parallel tests --- Gemfile | 4 +- lib/report_portal/cucumber/formatter.rb | 3 +- .../cucumber/parallel_formatter.rb | 4 +- lib/report_portal/cucumber/parallel_report.rb | 35 +++++--------- lib/report_portal/cucumber/report.rb | 48 +++++++++++++++++-- lib/report_portal/rspec/formatter.rb | 1 + lib/report_portal/settings.rb | 10 ++++ 7 files changed, 70 insertions(+), 35 deletions(-) diff --git a/Gemfile b/Gemfile index 0ee81e1..6185358 100644 --- a/Gemfile +++ b/Gemfile @@ -23,8 +23,8 @@ gem 'rest-client' gem 'rubytree', git: 'https://github.com/razboev/RubyTree' gem 'rspec' gem 'rake' -gem 'parallel_tests', '2.15.0' -gem 'sys-proctable', '1.1.5' +gem 'parallel_tests', '~> 2.15.0' +gem 'sys-proctable', '~> 1.1.5' gem 'logging' gem 'log4r' diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index dec478e..c27a0a6 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -25,7 +25,6 @@ module Cucumber class Formatter # @api private def initialize(config) - ENV['REPORT_PORTAL_USED'] = 'true' @thread = Thread.new do initialize_report @@ -47,7 +46,7 @@ def initialize(config) def puts(message) queue.push([:puts, message, ReportPortal.now]) - @io.puts(message) + # @io.puts(message) @io.flush end diff --git a/lib/report_portal/cucumber/parallel_formatter.rb b/lib/report_portal/cucumber/parallel_formatter.rb index 84a1f3c..a7da40d 100644 --- a/lib/report_portal/cucumber/parallel_formatter.rb +++ b/lib/report_portal/cucumber/parallel_formatter.rb @@ -16,8 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Report Portal. If not, see . -require_relative 'formatter' -require_relative 'parallel_report' +require File.dirname(__FILE__) + '/formatter' +require File.dirname(__FILE__) + '/parallel_report' module ReportPortal module Cucumber diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 053b770..b1a54d0 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -25,7 +25,6 @@ module ReportPortal module Cucumber class ParallelReport < Report - $folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" def parallel? true @@ -36,32 +35,18 @@ def initialize(desired_time) ReportPortal.last_used_time = 0 set_parallel_tests_vars if ParallelTests.first_process? - File.open(file_with_launch_id, 'w') do |f| - f.flock(File::LOCK_EX) - start_launch(desired_time, @cmd_args_of_parallel_tests) - f.write(ReportPortal.launch_id) - f.flush - f.flock(File::LOCK_UN) - end - $folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" - File.open($folder_creation_tracking_file, 'w+') do |f| - f.flock(File::LOCK_EX) - f.flush - f.flock(File::LOCK_UN) - end + start_launch(desired_time) else start_time = monotonic_time loop do - break if File.exist?(file_with_launch_id) + break if File.exist?(lock_file) if monotonic_time - start_time > wait_time_for_launch_start raise "File with launch ID wasn't created after waiting #{wait_time_for_launch_start} seconds" end sleep 0.5 end - File.open(file_with_launch_id, 'r') do |f| - f.flock(File::LOCK_SH) + File.open(lock_file, 'r') do |f| ReportPortal.launch_id = f.read - f.flock(File::LOCK_UN) end sleep_time = ENV['TEST_ENV_NUMBER'].to_i sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision @@ -74,7 +59,7 @@ def done(desired_time = ReportPortal.now) if ParallelTests.first_process? ParallelTests.wait_for_other_processes_to_finish - File.delete(file_with_launch_id) + File.delete(lock_file) unless attach_to_launch? $stdout.puts "Finishing launch #{ReportPortal.launch_id}" @@ -85,11 +70,12 @@ def done(desired_time = ReportPortal.now) end end - private - - def file_with_launch_id - Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lck" + def lock_file + file_path ||= tmp_dir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" + super end + + private def set_parallel_tests_vars pid = Process.pid @@ -106,8 +92,9 @@ def set_parallel_tests_vars end end + #time required for first tread to created remote project in RP and save id to file def wait_time_for_launch_start - 60 + ENV['rp_parallel_launch_wait_time'] ? ENV['rp_parallel_launch_wait_time'] : 60 end def monotonic_time diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index bef54ea..91b9c14 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -44,19 +44,53 @@ def initialize end def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) + # Not sure what is the use case if launch id is missing. But it does not make much of practical usage + # + # Expected behavior that make sense: + # 1. If launch_id present attach to existing (simple use case) + # 2. If launch_id not present check if exist rp_launch_id.tmp + # 3. [ADDED] If launch_id is not present check if lock exist with launch_uuid if attach_to_launch? ReportPortal.launch_id = if ReportPortal::Settings.instance.launch_id ReportPortal::Settings.instance.launch_id else - file_path = ReportPortal::Settings.instance.file_with_launch_id || (Pathname(Dir.tmpdir) + 'rp_launch_id.tmp') - File.read(file_path) + file_path = lock_file + if File.file?(file_path) + File.read(file_path) + else + new_launch(desired_time, cmd_args, file_path) + end end $stdout.puts "Attaching to launch #{ReportPortal.launch_id}" + else - description = ReportPortal::Settings.instance.description - description ||= cmd_args.map {|arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]")}.join(' ') - ReportPortal.start_launch(description, time_to_send(desired_time)) + new_launch(desired_time, cmd_args) + end + end + + def lock_file + file_path ||= ReportPortal::Settings.instance.file_with_launch_id + file_path ||= tmp_dir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid + file_path ||= tmp_dir + 'rp_launch_id.tmp' + file_path + end + + def new_launch(desired_time = ReportPortal.now, cmd_args = ARGV, lock_file = nil) + description = ReportPortal::Settings.instance.description + description ||= cmd_args.map {|arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]")}.join(' ') + ReportPortal.start_launch(description, time_to_send(desired_time)) + set_file_lock_with_launch_id(lock_file, ReportPortal.launch_id) if lock_file + ReportPortal.launch_id + end + + def set_file_lock_with_launch_id(lock_file, launch_id) + FileUtils.mkdir_p lock_file.dirname + File.open(lock_file, 'w') do |f| + f.flock(File::LOCK_EX) + f.write(launch_id) + f.flush + f.flock(File::LOCK_UN) end end @@ -167,6 +201,10 @@ def time_to_send(desired_time) ReportPortal.last_used_time = time_to_send end + def tmp_dir + Pathname(ENV['TMPDIR'] ? ENV['TMPDIR'] : Dir.tmpdir) + end + def same_feature_as_previous_test_case?(feature) @feature_node && @feature_node.name == feature.location.file.split(File::SEPARATOR).last end diff --git a/lib/report_portal/rspec/formatter.rb b/lib/report_portal/rspec/formatter.rb index 79733aa..6a7fcc4 100644 --- a/lib/report_portal/rspec/formatter.rb +++ b/lib/report_portal/rspec/formatter.rb @@ -40,6 +40,7 @@ def initialize(_output) def start(_start_notification) cmd_args = ARGV.map { |arg| (arg.include? 'rp_uuid=')? 'rp_uuid=[FILTERED]' : arg }.join(' ') + debugger ReportPortal.start_launch(cmd_args) @root_node = Tree::TreeNode.new(SecureRandom.hex) @current_group_node = @root_node diff --git a/lib/report_portal/settings.rb b/lib/report_portal/settings.rb index b2aa82d..a79c590 100644 --- a/lib/report_portal/settings.rb +++ b/lib/report_portal/settings.rb @@ -46,18 +46,28 @@ def initialize 'use_standard_logger' => false, 'launch_id' => false, 'file_with_launch_id' => false, + 'launch_uuid'=>true #used when multiple cucumber processes executed } keys.each do |key, is_required| define_singleton_method(key.to_sym) { setting(key) } fail "ReportPortal: Define environment variable '#{PREFIX}#{key}' or key #{key} in the configuration YAML file" if is_required && public_send(key).nil? end + launch_uuid = SecureRandom.uuid unless launch_uuid end def launch_mode is_debug ? 'DEBUG' : 'DEFAULT' end + def file_with_launch_id=(val) + @file_with_launch_id = val + end + + def file_with_launch_id + @file_with_launch_id + end + def formatter_modes setting('formatter_modes') || [] end From 7ed6e2c02e4a5d4aef97855355d5424d896645cb Mon Sep 17 00:00:00 2001 From: vveliev Date: Sat, 25 May 2019 05:07:53 -0400 Subject: [PATCH 23/98] fixing parallel execution --- lib/report_portal/cucumber/formatter.rb | 12 +++-- lib/report_portal/cucumber/parallel_report.rb | 19 +++++++- lib/report_portal/cucumber/report.rb | 47 +++++-------------- lib/reportportal.rb | 9 ++++ 4 files changed, 44 insertions(+), 43 deletions(-) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index c27a0a6..dcef785 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -17,12 +17,14 @@ # along with Report Portal. If not, see . require 'thread' +require 'cucumber/formatter/io' require_relative 'report' module ReportPortal module Cucumber class Formatter + include ::Cucumber::Formatter::Io # @api private def initialize(config) @@ -38,16 +40,16 @@ def initialize(config) else @thread.abort_on_exception = true end - - @io = config.out_stream - + @io = ensure_io(config.out_stream) handle_cucumber_events(config) end def puts(message) queue.push([:puts, message, ReportPortal.now]) - # @io.puts(message) - @io.flush + unless @io.class.eql?(File) + @io.puts(message) + @io.flush + end end def embed(*args) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index b1a54d0..8fe2d52 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -48,10 +48,17 @@ def initialize(desired_time) File.open(lock_file, 'r') do |f| ReportPortal.launch_id = f.read end - sleep_time = ENV['TEST_ENV_NUMBER'].to_i + add_process_description + sleep_time = 5 sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision end end + + def add_process_description + description = ReportPortal.get_launch['description'].split(' ') + description.push(self.description().split(' ')) + ReportPortal.update_launch({description: description.join(' ')}) + end def done(desired_time = ReportPortal.now) end_feature(desired_time) if @feature_node @@ -72,7 +79,10 @@ def done(desired_time = ReportPortal.now) def lock_file file_path ||= tmp_dir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" - super + file_path ||= ReportPortal::Settings.instance.file_with_launch_id + file_path ||= tmp_dir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid + file_path ||= tmp_dir + 'rp_launch_id.tmp' + file_path end private @@ -81,6 +91,11 @@ def set_parallel_tests_vars pid = Process.pid loop do current_process = Sys::ProcTable.ps(pid) + #TODO: add exception to fall back to cucumber process + # 1. if rm_launch_uuid was created by some other parallel script that executes cucumber batch of feature files + # 2. if fallback to cucumber process, this allows to use same formatter sequential and parallel executions + # useful when formatters are default configured in AfterConfiguration hook + # config.formats.push(["ReportPortal::Cucumber::ParallelFormatter", {}, set_up_output_format(report_name, :report_portal)]) raise 'Could not find parallel_cucumber/parallel_test in ancestors of current process' if current_process.nil? match = current_process.cmdline.match(/bin(?:\/|\\)parallel_(?:cucumber|test)(.+)/) if match diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 91b9c14..692a87b 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -77,12 +77,16 @@ def lock_file end def new_launch(desired_time = ReportPortal.now, cmd_args = ARGV, lock_file = nil) - description = ReportPortal::Settings.instance.description - description ||= cmd_args.map {|arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]")}.join(' ') - ReportPortal.start_launch(description, time_to_send(desired_time)) + ReportPortal.start_launch(description(cmd_args), time_to_send(desired_time)) set_file_lock_with_launch_id(lock_file, ReportPortal.launch_id) if lock_file ReportPortal.launch_id end + + def description(cmd_args=ARGV) + description ||= ReportPortal::Settings.instance.description + description ||= cmd_args.map {|arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]")}.join(' ') + description + end def set_file_lock_with_launch_id(lock_file, launch_id) FileUtils.mkdir_p lock_file.dirname @@ -229,39 +233,10 @@ def start_feature_with_parentage(feature, desired_time) tags = feature.tags.map(&:name) type = :TEST end - is_created = false - if parallel? && name.include?("Folder:") - folder_name = name.gsub("Folder: ", "") - folder_name_for_tracker = "./#{folder_name}" # create a full path file name to use for tracking - if index > 0 - folder_name_for_tracker = "./" - for path_index in (0...path_components_no_feature.length) - folder_name_for_tracker += "#{path_components_no_feature[path_index]}/" - end - end - $folder_creation_tracking_file = (Pathname(Dir.tmpdir)) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" - File.open($folder_creation_tracking_file, 'r+') do |f| - f.flock(File::LOCK_SH) - report_portal_folders = f.read - if report_portal_folders - report_portal_folders_array = report_portal_folders.split(/\n/) - if report_portal_folders_array.include?(folder_name_for_tracker) - is_created = true - end - end - f.flock(File::LOCK_UN) - end - unless is_created - File.open($folder_creation_tracking_file, 'a') do |f| - f.flock(File::LOCK_EX) - f.write("\n#{folder_name_for_tracker}") - f.flush - f.flock(File::LOCK_UN) - end - end - end - if parallel? && index < path_components.size - 1 && is_created - id_of_created_item = ReportPortal.item_id_of(name, parent_node) + if parallel? && + index < path_components.size - 1 && # is folder? + (id_of_created_item = ReportPortal.item_id_of(name, parent_node)) # get id for folder from report portal + # get child id from other process item = ReportPortal::TestItem.new(name, type, id_of_created_item, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node diff --git a/lib/reportportal.rb b/lib/reportportal.rb index ac4332c..7841e35 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -55,6 +55,15 @@ def start_launch(description, start_time = now) @launch_id = JSON.parse(response)['id'] end + def get_launch() + response = project_resource["launch/#{@launch_id}"].get + JSON.parse(response) + end + + def update_launch(data) + project_resource["launch/#{@launch_id}/update"].put(data.to_json) + end + def finish_launch(end_time = now) data = { end_time: end_time } project_resource["launch/#{@launch_id}/finish"].put(data.to_json) From c01819c33a667e405a484a46d17ed7a865f1dca6 Mon Sep 17 00:00:00 2001 From: vveliev Date: Mon, 27 May 2019 11:21:13 -0400 Subject: [PATCH 24/98] small refactoring --- lib/report_portal/cucumber/parallel_report.rb | 20 +++++++++---------- lib/report_portal/cucumber/report.rb | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 8fe2d52..035f882 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -53,11 +53,11 @@ def initialize(desired_time) sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision end end - + def add_process_description description = ReportPortal.get_launch['description'].split(' ') - description.push(self.description().split(' ')) - ReportPortal.update_launch({description: description.join(' ')}) + description.push(self.description().split(' ').strip) + ReportPortal.update_launch({description: description.uniq.join(' ')}) end def done(desired_time = ReportPortal.now) @@ -68,8 +68,9 @@ def done(desired_time = ReportPortal.now) File.delete(lock_file) - unless attach_to_launch? - $stdout.puts "Finishing launch #{ReportPortal.launch_id}" + # TODO: if parallel test does not need to finish launch, there should be env variable to support this. + # as report launch is is created by first process it should also be finished by same process + unless ReportPortal::Settings.instance.file_with_launch_id ReportPortal.close_child_items(nil) time_to_send = time_to_send(desired_time) ReportPortal.finish_launch(time_to_send) @@ -77,14 +78,11 @@ def done(desired_time = ReportPortal.now) end end - def lock_file + def lock_file(file_path = nil) file_path ||= tmp_dir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" - file_path ||= ReportPortal::Settings.instance.file_with_launch_id - file_path ||= tmp_dir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid - file_path ||= tmp_dir + 'rp_launch_id.tmp' - file_path + super(file_path) end - + private def set_parallel_tests_vars diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 36e400b..61bee5f 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -71,7 +71,7 @@ def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) end end - def lock_file + def lock_file(file_path = nil) file_path ||= ReportPortal::Settings.instance.file_with_launch_id file_path ||= tmp_dir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid file_path ||= tmp_dir + 'rp_launch_id.tmp' From ecc4801c2baabc8df459547e336e8653f5af10d4 Mon Sep 17 00:00:00 2001 From: vveliev Date: Mon, 27 May 2019 17:42:57 -0400 Subject: [PATCH 25/98] fixing report description when run in parallel --- lib/report_portal/cucumber/parallel_report.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 035f882..e5fb726 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -56,7 +56,7 @@ def initialize(desired_time) def add_process_description description = ReportPortal.get_launch['description'].split(' ') - description.push(self.description().split(' ').strip) + description.push(self.description().split(' ')).flatten! ReportPortal.update_launch({description: description.uniq.join(' ')}) end From e62541eb7242e3e454c029161ad7d28d2a89bebd Mon Sep 17 00:00:00 2001 From: vveliev Date: Mon, 27 May 2019 18:02:26 -0400 Subject: [PATCH 26/98] fixing report description when run in parallel --- lib/report_portal/cucumber/parallel_report.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 035f882..e5fb726 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -56,7 +56,7 @@ def initialize(desired_time) def add_process_description description = ReportPortal.get_launch['description'].split(' ') - description.push(self.description().split(' ').strip) + description.push(self.description().split(' ')).flatten! ReportPortal.update_launch({description: description.uniq.join(' ')}) end From 01fbe3a8261194f55cc96029595266583875fc54 Mon Sep 17 00:00:00 2001 From: vveliev Date: Fri, 31 May 2019 11:09:12 -0400 Subject: [PATCH 27/98] - removing debugger - if launch_id or file_with_launch_id is provided launch will not be marked as finished - moved sleep time, since we are waiting for report to be created anyways --- lib/report_portal/cucumber/parallel_report.rb | 10 ++++++---- lib/report_portal/cucumber/report.rb | 9 +++------ lib/report_portal/rspec/formatter.rb | 1 - 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index e5fb726..ece59ae 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -45,12 +45,13 @@ def initialize(desired_time) end sleep 0.5 end + sleep_time = 5 + sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision + File.open(lock_file, 'r') do |f| ReportPortal.launch_id = f.read end add_process_description - sleep_time = 5 - sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision end end @@ -70,7 +71,7 @@ def done(desired_time = ReportPortal.now) # TODO: if parallel test does not need to finish launch, there should be env variable to support this. # as report launch is is created by first process it should also be finished by same process - unless ReportPortal::Settings.instance.file_with_launch_id + unless ReportPortal::Settings.instance.launch_id || ReportPortal::Settings.instance.file_with_launch_id ReportPortal.close_child_items(nil) time_to_send = time_to_send(desired_time) ReportPortal.finish_launch(time_to_send) @@ -79,7 +80,7 @@ def done(desired_time = ReportPortal.now) end def lock_file(file_path = nil) - file_path ||= tmp_dir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" + file_path ||= Dir.tmpdir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" super(file_path) end @@ -88,6 +89,7 @@ def lock_file(file_path = nil) def set_parallel_tests_vars pid = Process.pid loop do + #FIXME failing in jenkins current_process = Sys::ProcTable.ps(pid) #TODO: add exception to fall back to cucumber process # 1. if rm_launch_uuid was created by some other parallel script that executes cucumber batch of feature files diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 61bee5f..6adc756 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -20,6 +20,7 @@ require 'cucumber/formatter/hook_query_visitor' require 'tree' require 'securerandom' +require 'tempfile' require_relative '../../reportportal' require_relative '../logging/logger' @@ -73,8 +74,8 @@ def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) def lock_file(file_path = nil) file_path ||= ReportPortal::Settings.instance.file_with_launch_id - file_path ||= tmp_dir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid - file_path ||= tmp_dir + 'rp_launch_id.tmp' + file_path ||= Dir.tmpdir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid + file_path ||= Dir.tmpdir + 'rp_launch_id.tmp' file_path end @@ -207,10 +208,6 @@ def time_to_send(desired_time) ReportPortal.last_used_time = time_to_send end - def tmp_dir - Pathname(ENV['TMPDIR'] ? ENV['TMPDIR'] : Dir.tmpdir) - end - def same_feature_as_previous_test_case?(feature) @feature_node && @feature_node.name == feature.location.file.split(File::SEPARATOR).last end diff --git a/lib/report_portal/rspec/formatter.rb b/lib/report_portal/rspec/formatter.rb index 6a7fcc4..79733aa 100644 --- a/lib/report_portal/rspec/formatter.rb +++ b/lib/report_portal/rspec/formatter.rb @@ -40,7 +40,6 @@ def initialize(_output) def start(_start_notification) cmd_args = ARGV.map { |arg| (arg.include? 'rp_uuid=')? 'rp_uuid=[FILTERED]' : arg }.join(' ') - debugger ReportPortal.start_launch(cmd_args) @root_node = Tree::TreeNode.new(SecureRandom.hex) @current_group_node = @root_node From c15caef5a33f1f0762c01803e8a32858e167b676 Mon Sep 17 00:00:00 2001 From: vveliev Date: Fri, 31 May 2019 13:27:00 -0400 Subject: [PATCH 28/98] - reverting file lock changes for reading the file - moving lock file related functions to private --- lib/report_portal/cucumber/parallel_report.rb | 9 ++---- lib/report_portal/cucumber/report.rb | 31 +++++++++++-------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index ece59ae..6c03850 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -47,10 +47,7 @@ def initialize(desired_time) end sleep_time = 5 sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision - - File.open(lock_file, 'r') do |f| - ReportPortal.launch_id = f.read - end + ReportPortal.launch_id = read_lock_file(lock_file) add_process_description end end @@ -79,13 +76,13 @@ def done(desired_time = ReportPortal.now) end end + private + def lock_file(file_path = nil) file_path ||= Dir.tmpdir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" super(file_path) end - private - def set_parallel_tests_vars pid = Process.pid loop do diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 6adc756..034cc94 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -59,26 +59,14 @@ def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) ReportPortal::Settings.instance.launch_id else file_path = lock_file - if File.file?(file_path) - File.read(file_path) - else - new_launch(desired_time, cmd_args, file_path) - end + File.file?(file_path) ? read_lock_file(file_path) : new_launch(desired_time, cmd_args, file_path) end $stdout.puts "Attaching to launch #{ReportPortal.launch_id}" - else new_launch(desired_time, cmd_args) end end - def lock_file(file_path = nil) - file_path ||= ReportPortal::Settings.instance.file_with_launch_id - file_path ||= Dir.tmpdir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid - file_path ||= Dir.tmpdir + 'rp_launch_id.tmp' - file_path - end - def new_launch(desired_time = ReportPortal.now, cmd_args = ARGV, lock_file = nil) ReportPortal.start_launch(description(cmd_args), time_to_send(desired_time)) set_file_lock_with_launch_id(lock_file, ReportPortal.launch_id) if lock_file @@ -194,6 +182,23 @@ def embed(src, mime_type, label, desired_time = ReportPortal.now) private + def lock_file(file_path = nil) + file_path ||= ReportPortal::Settings.instance.file_with_launch_id + file_path ||= Dir.tmpdir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid + file_path ||= Dir.tmpdir + 'rp_launch_id.tmp' + file_path + end + + def read_lock_file(file_path) + content = nil + File.open(file_with_launch_id, 'r') do |f| + f.flock(File::LOCK_SH) + content = File.read(file_path) + f.flock(File::LOCK_UN) + end + content + end + # Report Portal sorts logs by time. However, several logs might have the same time. # So to get Report Portal sort them properly the time should be different in all logs related to the same item. # And thus it should be stored. From dad6361037a0ba394d8eaf342646c2ca3c597d09 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 20 Jun 2019 12:32:55 -0400 Subject: [PATCH 29/98] - fixing temp file path generation - refactoring how runner process is detected - added fallback to cucumber if parallel test was not for parallel formatter --- lib/report_portal/cucumber/parallel_report.rb | 38 ++++++++++--------- lib/report_portal/cucumber/report.rb | 6 +-- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 6c03850..ce92d35 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -79,29 +79,31 @@ def done(desired_time = ReportPortal.now) private def lock_file(file_path = nil) - file_path ||= Dir.tmpdir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" + file_path ||= Dir.tmpdir + "/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" super(file_path) end def set_parallel_tests_vars - pid = Process.pid - loop do - #FIXME failing in jenkins - current_process = Sys::ProcTable.ps(pid) - #TODO: add exception to fall back to cucumber process - # 1. if rm_launch_uuid was created by some other parallel script that executes cucumber batch of feature files - # 2. if fallback to cucumber process, this allows to use same formatter sequential and parallel executions - # useful when formatters are default configured in AfterConfiguration hook - # config.formats.push(["ReportPortal::Cucumber::ParallelFormatter", {}, set_up_output_format(report_name, :report_portal)]) - raise 'Could not find parallel_cucumber/parallel_test in ancestors of current process' if current_process.nil? - match = current_process.cmdline.match(/bin(?:\/|\\)parallel_(?:cucumber|test)(.+)/) - if match - @pid_of_parallel_tests = current_process.pid - @cmd_args_of_parallel_tests = match[1].strip.split - break - end - pid = Sys::ProcTable.ps(pid).ppid + process_list = Sys::ProcTable.ps + runner_process ||= get_parallel_test_process(process_list) + runner_process ||= get_cucumber_test_process(process_list) + raise 'Could not find parallel_cucumber/parallel_test in ancestors of current process' if runner_process.nil? + @pid_of_parallel_tests = runner_process.pid + @cmd_args_of_parallel_tests = runner_process.cmdline.split(' ',2).pop + end + + def get_parallel_test_process(process_list) + process_list.each do |process| + return process if process.cmdline.match(/bin(?:\/|\\)parallel_(?:cucumber|test)(.+)/) + end + nil + end + + def get_cucumber_test_process(process_list) + process_list.each do |process| + return process if process.cmdline.match(/bin(?:\/|\\)(?:cucumber)(.+)/) end + nil end #time required for first tread to created remote project in RP and save id to file diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 034cc94..073c3e2 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -80,7 +80,7 @@ def description(cmd_args=ARGV) end def set_file_lock_with_launch_id(lock_file, launch_id) - FileUtils.mkdir_p lock_file.dirname + FileUtils.mkdir_p File.dirname(lock_file) File.open(lock_file, 'w') do |f| f.flock(File::LOCK_EX) f.write(launch_id) @@ -184,8 +184,8 @@ def embed(src, mime_type, label, desired_time = ReportPortal.now) def lock_file(file_path = nil) file_path ||= ReportPortal::Settings.instance.file_with_launch_id - file_path ||= Dir.tmpdir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid - file_path ||= Dir.tmpdir + 'rp_launch_id.tmp' + file_path ||= Dir.tmpdir + "/report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid + file_path ||= Dir.tmpdir + '/rp_launch_id.tmp' file_path end From e40548b652546e01bf58ae671b7605f59d295972 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 20 Jun 2019 13:17:46 -0400 Subject: [PATCH 30/98] - reverting some changes - launch_uuid related fixes --- lib/report_portal/cucumber/report.rb | 2 +- lib/report_portal/settings.rb | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 073c3e2..de0040b 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -191,7 +191,7 @@ def lock_file(file_path = nil) def read_lock_file(file_path) content = nil - File.open(file_with_launch_id, 'r') do |f| + File.open(file_path, 'r') do |f| f.flock(File::LOCK_SH) content = File.read(file_path) f.flock(File::LOCK_UN) diff --git a/lib/report_portal/settings.rb b/lib/report_portal/settings.rb index a79c590..60c0386 100644 --- a/lib/report_portal/settings.rb +++ b/lib/report_portal/settings.rb @@ -46,28 +46,20 @@ def initialize 'use_standard_logger' => false, 'launch_id' => false, 'file_with_launch_id' => false, - 'launch_uuid'=>true #used when multiple cucumber processes executed + 'launch_uuid'=>false } keys.each do |key, is_required| define_singleton_method(key.to_sym) { setting(key) } fail "ReportPortal: Define environment variable '#{PREFIX}#{key}' or key #{key} in the configuration YAML file" if is_required && public_send(key).nil? end - launch_uuid = SecureRandom.uuid unless launch_uuid + launch_uuid ||= SecureRandom.uuid end def launch_mode is_debug ? 'DEBUG' : 'DEFAULT' end - def file_with_launch_id=(val) - @file_with_launch_id = val - end - - def file_with_launch_id - @file_with_launch_id - end - def formatter_modes setting('formatter_modes') || [] end From ad0525299468f9389e4bcc6c51e069ec7e738579 Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Thu, 25 Jan 2018 19:28:58 +0300 Subject: [PATCH 31/98] Fix Cucumber parallel formatter --- Gemfile | 4 +++ lib/report_portal/cucumber/parallel_report.rb | 32 ++++++++++++++----- lib/report_portal/cucumber/report.rb | 8 ++--- lib/reportportal.rb | 22 +++++++++++-- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/Gemfile b/Gemfile index e45c32c..b74033c 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,10 @@ gem 'parallel_tests' gem 'rake' gem 'rspec' gem 'rubytree', git: 'https://github.com/razboev/RubyTree' +gem 'parallel_tests', '~> 2.15' +gem 'sys-proctable' +gem 'rest-client' + gem 'log4r' gem 'logging' diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index d05b8f8..82d4ed0 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -3,26 +3,28 @@ module ReportPortal module Cucumber class ParallelReport < Report - FILE_WITH_LAUNCH_ID = Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{Process.ppid}.lck" - def parallel? true end - def initialize + def initialize(desired_time) @root_node = Tree::TreeNode.new('') - @last_used_time ||= 0 + ReportPortal.last_used_time = 0 if ParallelTests.first_process? - File.open(FILE_WITH_LAUNCH_ID, 'w') do |f| + File.open(file_with_launch_id, 'w') do |f| f.flock(File::LOCK_EX) - start_launch + start_launch(desired_time) f.write(ReportPortal.launch_id) f.flush f.flock(File::LOCK_UN) end else - File.open(FILE_WITH_LAUNCH_ID, 'r') do |f| + loop do + break if File.exist?(file_with_launch_id) + sleep 0.5 + end + File.open(file_with_launch_id, 'r') do |f| f.flock(File::LOCK_SH) ReportPortal.launch_id = f.read f.flock(File::LOCK_UN) @@ -36,7 +38,7 @@ def done(desired_time = ReportPortal.now) if ParallelTests.first_process? ParallelTests.wait_for_other_processes_to_finish - File.delete(FILE_WITH_LAUNCH_ID) + File.delete(file_with_launch_id) unless attach_to_launch? $stdout.puts "Finishing launch #{ReportPortal.launch_id}" @@ -46,6 +48,20 @@ def done(desired_time = ReportPortal.now) end end end + + def file_with_launch_id + Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{pid_of_parallel_tests}.lck" + end + + def pid_of_parallel_tests + pid = Process.pid + loop do + current_process = Sys::ProcTable.ps(pid) + raise 'Could not get parallel_cucumber in process tree' if current_process.nil? + return current_process.pid if current_process.cmdline[/bin(?:\/|\\)parallel_cucumber/] + pid = Sys::ProcTable.ps(pid).ppid + end + end end end end diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 172207e..4a2464e 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -19,7 +19,7 @@ def attach_to_launch? end def initialize - @last_used_time = 0 + ReportPortal.last_used_time = 0 @root_node = Tree::TreeNode.new('') start_launch end @@ -142,10 +142,10 @@ def embed(src, mime_type, label, desired_time = ReportPortal.now) # * that process/thread can't start the next test until it's done with the previous one def time_to_send(desired_time) time_to_send = desired_time - if time_to_send <= @last_used_time - time_to_send = @last_used_time + 1 + if time_to_send <= ReportPortal.last_used_time + time_to_send = ReportPortal.last_used_time + 1 end - @last_used_time = time_to_send + ReportPortal.last_used_time = time_to_send end def same_feature_as_previous_test_case?(feature) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index b8e2527..db97c6b 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -23,7 +23,7 @@ module ReportPortal end class << self - attr_accessor :launch_id, :current_scenario + attr_accessor :launch_id, :current_scenario, :last_used_time def now (Time.now.to_f * 1000).to_i @@ -64,9 +64,18 @@ def start_item(item_node) item = item_node.content data = { start_time: item.start_time, name: item.name[0, 255], type: item.type.to_s, launch_id: @launch_id, description: item.description } data[:tags] = item.tags unless item.tags.empty? - do_request(url) do |resource| - JSON.parse(resource.post(data.to_json, content_type: :json, &@response_handler))['id'] + begin + url = "item" + url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? + response = resource[url].post(data.to_json, content_type: :json) + rescue RestClient::Exception => e + if JSON.parse(e.response)['message'][/Start time of child .+ item should be same or later than start time .+ of the parent/] + data[:start_time] += 1 + ReportPortal.last_used_time = data[:start_time] + retry + end end + JSON.parse(response)['id'] end def finish_item(item, status = nil, end_time = nil, force_issue = nil) @@ -166,6 +175,13 @@ def close_child_items(parent_id) private + def resource + props = { :headers => {:Authorization => "Bearer #{Settings.instance.uuid}"}} + verify_ssl = Settings.instance.disable_ssl_verification + props[:verify_ssl] = !verify_ssl unless verify_ssl.nil? + RestClient::Resource.new(Settings.instance.project_url, props) + end + def create_resource(url) props = { :headers => {:Authorization => "Bearer #{Settings.instance.uuid}"}} verify_ssl = Settings.instance.disable_ssl_verification From 5067695e145f3d424a3aeed8e549c9c365422fd7 Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Mon, 29 Jan 2018 17:52:14 +0300 Subject: [PATCH 32/98] Fix parallel_tests version and possible endless loop --- Gemfile | 1 - lib/report_portal/cucumber/parallel_report.rb | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index b74033c..e59523b 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,6 @@ source 'https://rubygems.org' gemspec gem 'cucumber', '~> 3' -gem 'parallel_tests' gem 'rake' gem 'rspec' gem 'rubytree', git: 'https://github.com/razboev/RubyTree' diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 82d4ed0..e043674 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -20,8 +20,12 @@ def initialize(desired_time) f.flock(File::LOCK_UN) end else + start_time = monotonic_time loop do break if File.exist?(file_with_launch_id) + if monotonic_time - start_time > wait_time_for_launch_start + raise "File with launch id wasn't created during #{wait_time_for_launch_start} seconds" + end sleep 0.5 end File.open(file_with_launch_id, 'r') do |f| @@ -49,6 +53,8 @@ def done(desired_time = ReportPortal.now) end end + private + def file_with_launch_id Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{pid_of_parallel_tests}.lck" end @@ -62,6 +68,14 @@ def pid_of_parallel_tests pid = Sys::ProcTable.ps(pid).ppid end end + + def wait_time_for_launch_start + 60 + end + + def monotonic_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end end end end From 3242ac12a9f36232e071949360b5a076ead07136 Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Tue, 30 Jan 2018 15:55:05 +0300 Subject: [PATCH 33/98] Add debug logging to debug some issue --- lib/report_portal/cucumber/parallel_report.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index e043674..9f39b97 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -7,11 +7,12 @@ def parallel? true end - def initialize(desired_time) + def initialize(desired_time, logger) @root_node = Tree::TreeNode.new('') ReportPortal.last_used_time = 0 if ParallelTests.first_process? + logger.debug('Create launch file') File.open(file_with_launch_id, 'w') do |f| f.flock(File::LOCK_EX) start_launch(desired_time) @@ -19,20 +20,25 @@ def initialize(desired_time) f.flush f.flock(File::LOCK_UN) end + logger.debug('Created launch file') else + logger.debug('Wait for launch file started') start_time = monotonic_time loop do break if File.exist?(file_with_launch_id) + logger.debug('File does not yet exist') if monotonic_time - start_time > wait_time_for_launch_start raise "File with launch id wasn't created during #{wait_time_for_launch_start} seconds" end - sleep 0.5 + sleep 1.5 end + logger.debug('Wait for file to exist finished') File.open(file_with_launch_id, 'r') do |f| f.flock(File::LOCK_SH) ReportPortal.launch_id = f.read f.flock(File::LOCK_UN) end + logger.debug('Launch id was read from the file') end end From ab9483ca45e8c0bb88b5191c6c2ade336ca882ec Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Tue, 30 Jan 2018 18:15:31 +0300 Subject: [PATCH 34/98] Add more debug logging --- lib/report_portal/cucumber/parallel_report.rb | 1 + lib/report_portal/cucumber/report.rb | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 9f39b97..d874a20 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -8,6 +8,7 @@ def parallel? end def initialize(desired_time, logger) + @logger = logger @root_node = Tree::TreeNode.new('') ReportPortal.last_used_time = 0 diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 4a2464e..2ca0d54 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -153,6 +153,7 @@ def same_feature_as_previous_test_case?(feature) end def start_feature_with_parentage(feature, desired_time) + @logger.debug("Start feature #{feature}") parent_node = @root_node child_node = nil path_components = feature.location.file.split(File::SEPARATOR) @@ -170,19 +171,23 @@ def start_feature_with_parentage(feature, desired_time) tags = feature.tags.map(&:name) type = :TEST end + @logger.debug("Start item #{name}") # TODO: multithreading # Parallel formatter always executes scenarios inside the same feature in the same process if parallel? && index < path_components.size - 1 && # is folder? (id_of_created_item = ReportPortal.item_id_of(name, parent_node)) # get id for folder from report portal # get child id from other process + @logger.debug("Id of parent item from another process: #{id_of_created_item}") item = ReportPortal::TestItem.new(name, type, id_of_created_item, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node + @logger.debug("Item already created") else item = ReportPortal::TestItem.new(name, type, nil, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node item.id = ReportPortal.start_item(child_node) # TODO: multithreading + @logger.debug("Item #{item.id} started") end end parent_node = child_node From c25fb0e05b73bd5225da82a23094f728cbe7d265 Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Tue, 30 Jan 2018 18:40:19 +0300 Subject: [PATCH 35/98] Remove retrying of requests to Report Portal --- lib/report_portal/cucumber/report.rb | 1 + lib/reportportal.rb | 115 +++++++++------------------ 2 files changed, 39 insertions(+), 77 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 2ca0d54..0d0a958 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -186,6 +186,7 @@ def start_feature_with_parentage(feature, desired_time) item = ReportPortal::TestItem.new(name, type, nil, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node + @logger.debug("Try to start item in RP") item.id = ReportPortal.start_item(child_node) # TODO: multithreading @logger.debug("Item #{item.id} started") end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index db97c6b..15fd154 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -11,17 +11,6 @@ module ReportPortal TestItem = Struct.new(:name, :type, :id, :start_time, :description, :closed, :tags) LOG_LEVELS = { error: 'ERROR', warn: 'WARN', info: 'INFO', debug: 'DEBUG', trace: 'TRACE', fatal: 'FATAL', unknown: 'UNKNOWN' } - @response_handler = proc do |response, request, result, &block| - if (200..207).include? response.code - response - else - p "ReportPortal API returned #{response}" - p "Offending request method/URL: #{request.args[:method].upcase} #{request.args[:url]}" - p "Offending request payload: #{request.args[:payload]}}" - response.return!(request, result, &block) - end - end - class << self attr_accessor :launch_id, :current_scenario, :last_used_time @@ -43,44 +32,36 @@ def status_to_level(status) end def start_launch(description, start_time = now) - url = "#{Settings.instance.project_url}/launch" data = { name: Settings.instance.launch, start_time: start_time, tags: Settings.instance.tags, description: description, mode: Settings.instance.launch_mode } - @launch_id = do_request(url) do |resource| - JSON.parse(resource.post(data.to_json, content_type: :json, &@response_handler))['id'] - end + response = project_resource['launch'].post(data.to_json) + @launch_id = JSON.parse(response)['id'] end def finish_launch(end_time = now) - url = "#{Settings.instance.project_url}/launch/#{@launch_id}/finish" data = { end_time: end_time } - do_request(url) do |resource| - resource.put data.to_json, content_type: :json, &@response_handler - end + project_resource["launch/#{@launch_id}/finish"].put(data.to_json) end def start_item(item_node) - url = "#{Settings.instance.project_url}/item" - url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? item = item_node.content data = { start_time: item.start_time, name: item.name[0, 255], type: item.type.to_s, launch_id: @launch_id, description: item.description } data[:tags] = item.tags unless item.tags.empty? begin - url = "item" + url = 'item' url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? - response = resource[url].post(data.to_json, content_type: :json) + response = project_resource[url].post(data.to_json) rescue RestClient::Exception => e - if JSON.parse(e.response)['message'][/Start time of child .+ item should be same or later than start time .+ of the parent/] - data[:start_time] += 1 - ReportPortal.last_used_time = data[:start_time] - retry - end + response_message = JSON.parse(e.response)['message'] + raise unless response_message[/Start time of child .+ item should be same or later than start time .+ of the parent/] + data[:start_time] += 1 + ReportPortal.last_used_time = data[:start_time] + retry end JSON.parse(response)['id'] end def finish_item(item, status = nil, end_time = nil, force_issue = nil) unless item.nil? || item.id.nil? || item.closed - url = "#{Settings.instance.project_url}/item/#{item.id}" data = { end_time: end_time.nil? ? now : end_time } data[:status] = status unless status.nil? if force_issue && status != :passed # TODO: check for :passed status is probably not needed @@ -88,9 +69,7 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) elsif status == :skipped data[:issue] = { issue_type: 'NOT_ISSUE' } end - do_request(url) do |resource| - resource.put data.to_json, content_type: :json, &@response_handler - end + project_resource["item/#{item.id}"].put(data.to_json) item.closed = true end end @@ -99,16 +78,12 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) def send_log(status, message, time) unless @current_scenario.nil? || @current_scenario.closed # it can be nil if scenario outline in expand mode is executed - url = "#{Settings.instance.project_url}/log" data = { item_id: @current_scenario.id, time: time, level: status_to_level(status), message: message.to_s } - do_request(url) do |resource| - resource.post(data.to_json, content_type: :json, &@response_handler) - end + project_resource['log'].post(data.to_json) end end - def send_file(status, path, label = nil, time = now, mime_type='image/png') - url = "#{Settings.instance.project_url}/log" + def send_file(status, path, label = nil, time = now, mime_type = 'image/png') unless File.file?(path) extension = ".#{MIME::Types[mime_type].first.extensions.first}" temp = Tempfile.open(['file',extension]) @@ -121,39 +96,35 @@ def send_file(status, path, label = nil, time = now, mime_type='image/png') label ||= File.basename(file) json = { level: status_to_level(status), message: label, item_id: @current_scenario.id, time: time, file: { name: File.basename(file) } } data = { :json_request_part => [json].to_json, label => file, :multipart => true, :content_type => 'application/json' } - do_request(url) do |resource| - resource.post(data, { content_type: 'multipart/form-data' }, &@response_handler) - end + project_resource['log'].post(data, content_type: 'multipart/form-data') end end # needed for parallel formatter def item_id_of(name, parent_node) if parent_node.is_root? # folder without parent folder - url = "#{Settings.instance.project_url}/item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{URI.escape(name)}&filter.size.path=0" + url = "item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{URI.escape(name)}&filter.size.path=0" else - url = "#{Settings.instance.project_url}/item?filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" + url = "item?filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" end - do_request(url) do |resource| - data = JSON.parse(resource.get) - if data.key? 'content' - data['content'].empty? ? nil : data['content'][0]['id'] - else - nil # item isn't started yet - end + data = JSON.parse(project_resource[url].get) + if data.key? 'content' + data['content'].empty? ? nil : data['content'][0]['id'] + else + nil # item isn't started yet end end # needed for parallel formatter def close_child_items(parent_id) if parent_id.nil? - url = "#{Settings.instance.project_url}/item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" + url = "item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" else - url = "#{Settings.instance.project_url}/item?filter.eq.parent=#{parent_id}&page.page=1&page.size=100" + url = "item?filter.eq.parent=#{parent_id}&page.page=1&page.size=100" end ids = [] loop do - response = do_request(url) { |r| JSON.parse(r.get) } + response = JSON.parse(project_resource[url].get) if response.key?('links') link = response['links'].find { |i| i['rel'] == 'next' } url = link.nil? ? nil : link['href'] @@ -175,31 +146,21 @@ def close_child_items(parent_id) private - def resource - props = { :headers => {:Authorization => "Bearer #{Settings.instance.uuid}"}} - verify_ssl = Settings.instance.disable_ssl_verification - props[:verify_ssl] = !verify_ssl unless verify_ssl.nil? - RestClient::Resource.new(Settings.instance.project_url, props) - end - - def create_resource(url) - props = { :headers => {:Authorization => "Bearer #{Settings.instance.uuid}"}} + def project_resource + options = {} + options[:headers] = { + :Authorization => "Bearer #{Settings.instance.uuid}", + content_type: :json + } verify_ssl = Settings.instance.disable_ssl_verification - props[:verify_ssl] = !verify_ssl unless verify_ssl.nil? - RestClient::Resource.new url, props - end - - def do_request(url) - resource = create_resource(url) - tries = 3 - begin - yield resource - rescue - p "Request to #{url} produced an exception: #{$!.class}: #{$!}" - $!.backtrace.each { |l| p l } - retry unless (tries -= 1).zero? - p "Failed to execute request to #{url} after 3 attempts." - nil + options[:verify_ssl] = !verify_ssl unless verify_ssl.nil? + RestClient::Resource.new(Settings.instance.project_url, options) do |response, request, _, &block| + unless (200..207).include?(response.code) + p "ReportPortal API returned #{response}" + p "Offending request method/URL: #{request.args[:method].upcase} #{request.args[:url]}" + p "Offending request payload: #{request.args[:payload]}}" + end + response.return!(&block) end end end From 4fbf8631b8b09322fd99fd19ca6fd8500a1740f4 Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Tue, 30 Jan 2018 19:16:58 +0300 Subject: [PATCH 36/98] Set last_used_time to a time of the parent item --- lib/reportportal.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 15fd154..796a9e0 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -52,8 +52,10 @@ def start_item(item_node) response = project_resource[url].post(data.to_json) rescue RestClient::Exception => e response_message = JSON.parse(e.response)['message'] - raise unless response_message[/Start time of child .+ item should be same or later than start time .+ of the parent/] - data[:start_time] += 1 + m = response_message.match(/Start time of child (.+) item should be same or later than start time \[\'(.+)\'\] of the parent/) + raise unless m + time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') + data[:start_time] = (time.to_f * 1000).to_i + 1000 ReportPortal.last_used_time = data[:start_time] retry end From 41e06045c39962dc21371bb9b0b9b2985d935a9f Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Wed, 31 Jan 2018 15:18:51 +0300 Subject: [PATCH 37/98] Remove log debug statements --- lib/report_portal/cucumber/parallel_report.rb | 9 +-------- lib/report_portal/cucumber/report.rb | 6 ------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index d874a20..3b15eab 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -7,13 +7,11 @@ def parallel? true end - def initialize(desired_time, logger) - @logger = logger + def initialize(desired_time) @root_node = Tree::TreeNode.new('') ReportPortal.last_used_time = 0 if ParallelTests.first_process? - logger.debug('Create launch file') File.open(file_with_launch_id, 'w') do |f| f.flock(File::LOCK_EX) start_launch(desired_time) @@ -21,25 +19,20 @@ def initialize(desired_time, logger) f.flush f.flock(File::LOCK_UN) end - logger.debug('Created launch file') else - logger.debug('Wait for launch file started') start_time = monotonic_time loop do break if File.exist?(file_with_launch_id) - logger.debug('File does not yet exist') if monotonic_time - start_time > wait_time_for_launch_start raise "File with launch id wasn't created during #{wait_time_for_launch_start} seconds" end sleep 1.5 end - logger.debug('Wait for file to exist finished') File.open(file_with_launch_id, 'r') do |f| f.flock(File::LOCK_SH) ReportPortal.launch_id = f.read f.flock(File::LOCK_UN) end - logger.debug('Launch id was read from the file') end end diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 0d0a958..4a2464e 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -153,7 +153,6 @@ def same_feature_as_previous_test_case?(feature) end def start_feature_with_parentage(feature, desired_time) - @logger.debug("Start feature #{feature}") parent_node = @root_node child_node = nil path_components = feature.location.file.split(File::SEPARATOR) @@ -171,24 +170,19 @@ def start_feature_with_parentage(feature, desired_time) tags = feature.tags.map(&:name) type = :TEST end - @logger.debug("Start item #{name}") # TODO: multithreading # Parallel formatter always executes scenarios inside the same feature in the same process if parallel? && index < path_components.size - 1 && # is folder? (id_of_created_item = ReportPortal.item_id_of(name, parent_node)) # get id for folder from report portal # get child id from other process - @logger.debug("Id of parent item from another process: #{id_of_created_item}") item = ReportPortal::TestItem.new(name, type, id_of_created_item, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node - @logger.debug("Item already created") else item = ReportPortal::TestItem.new(name, type, nil, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node - @logger.debug("Try to start item in RP") item.id = ReportPortal.start_item(child_node) # TODO: multithreading - @logger.debug("Item #{item.id} started") end end parent_node = child_node From 24e16f6d925238aa40f3fd041f1556f4d8431e38 Mon Sep 17 00:00:00 2001 From: Andrei Botalov Date: Thu, 1 Feb 2018 12:52:41 +0300 Subject: [PATCH 38/98] Set description to cmd args of parallel_tests process --- lib/report_portal/cucumber/parallel_report.rb | 18 ++++++++++++------ lib/report_portal/cucumber/report.rb | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 3b15eab..72ab38c 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -10,11 +10,12 @@ def parallel? def initialize(desired_time) @root_node = Tree::TreeNode.new('') ReportPortal.last_used_time = 0 + set_parallel_tests_vars if ParallelTests.first_process? File.open(file_with_launch_id, 'w') do |f| f.flock(File::LOCK_EX) - start_launch(desired_time) + start_launch(desired_time, @cmd_args_of_parallel_tests) f.write(ReportPortal.launch_id) f.flush f.flock(File::LOCK_UN) @@ -26,7 +27,7 @@ def initialize(desired_time) if monotonic_time - start_time > wait_time_for_launch_start raise "File with launch id wasn't created during #{wait_time_for_launch_start} seconds" end - sleep 1.5 + sleep 0.5 end File.open(file_with_launch_id, 'r') do |f| f.flock(File::LOCK_SH) @@ -56,15 +57,20 @@ def done(desired_time = ReportPortal.now) private def file_with_launch_id - Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{pid_of_parallel_tests}.lck" + Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lck" end - def pid_of_parallel_tests + def set_parallel_tests_vars pid = Process.pid loop do current_process = Sys::ProcTable.ps(pid) - raise 'Could not get parallel_cucumber in process tree' if current_process.nil? - return current_process.pid if current_process.cmdline[/bin(?:\/|\\)parallel_cucumber/] + raise 'Could not find parallel_cucumber/parallel_test in ancestors of current process' if current_process.nil? + match = current_process.cmdline.match(/bin(?:\/|\\)parallel_(?:cucumber|test)(.+)/) + if match + @pid_of_parallel_tests = current_process.pid + @cmd_args_of_parallel_tests = match[1].strip.split + break + end pid = Sys::ProcTable.ps(pid).ppid end end diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 4a2464e..6552435 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -24,7 +24,7 @@ def initialize start_launch end - def start_launch(desired_time = ReportPortal.now) + def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) if attach_to_launch? ReportPortal.launch_id = if ReportPortal::Settings.instance.launch_id @@ -36,7 +36,7 @@ def start_launch(desired_time = ReportPortal.now) $stdout.puts "Attaching to launch #{ReportPortal.launch_id}" else description = ReportPortal::Settings.instance.description - description ||= ARGV.map { |arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]") }.join(' ') + description ||= cmd_args.map { |arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]") }.join(' ') ReportPortal.start_launch(description, time_to_send(desired_time)) end end From 1ec43e8efceea6f7ca586823a2c319057e689d39 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:28:07 -0500 Subject: [PATCH 39/98] Fix URLs to include required "filter.eq.launch" parameter --- lib/reportportal.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 796a9e0..869f353 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -107,7 +107,7 @@ def item_id_of(name, parent_node) if parent_node.is_root? # folder without parent folder url = "item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{URI.escape(name)}&filter.size.path=0" else - url = "item?filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" + url = "item?filter.eq.launch=#{@launch_id}&filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" end data = JSON.parse(project_resource[url].get) if data.key? 'content' @@ -122,7 +122,7 @@ def close_child_items(parent_id) if parent_id.nil? url = "item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" else - url = "item?filter.eq.parent=#{parent_id}&page.page=1&page.size=100" + url = "item?filter.eq.launch=#{@launch_id}&filter.eq.parent=#{parent_id}&page.page=1&page.size=100" end ids = [] loop do From d1c128a65a878986fc66a58cd6e758f9f377a623 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:29:15 -0500 Subject: [PATCH 40/98] Fix matcher to detect when start time needs to be updated --- lib/reportportal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 869f353..5f05ef5 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -52,7 +52,7 @@ def start_item(item_node) response = project_resource[url].post(data.to_json) rescue RestClient::Exception => e response_message = JSON.parse(e.response)['message'] - m = response_message.match(/Start time of child (.+) item should be same or later than start time \[\'(.+)\'\] of the parent/) + m = response_message.match(/Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'/) raise unless m time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') data[:start_time] = (time.to_f * 1000).to_i + 1000 From b62c7ebc74376e553d3d5f5220aee0d1b5ce373c Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:30:20 -0500 Subject: [PATCH 41/98] Clean up formatting in report.rb --- lib/report_portal/cucumber/report.rb | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 6552435..948b3b6 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -10,6 +10,7 @@ module ReportPortal module Cucumber # @api private class Report + def parallel? false end @@ -27,16 +28,16 @@ def initialize def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) if attach_to_launch? ReportPortal.launch_id = - if ReportPortal::Settings.instance.launch_id - ReportPortal::Settings.instance.launch_id - else - file_path = ReportPortal::Settings.instance.file_with_launch_id || (Pathname(Dir.tmpdir) + 'rp_launch_id.tmp') - File.read(file_path) - end + if ReportPortal::Settings.instance.launch_id + ReportPortal::Settings.instance.launch_id + else + file_path = ReportPortal::Settings.instance.file_with_launch_id || (Pathname(Dir.tmpdir) + 'rp_launch_id.tmp') + File.read(file_path) + end $stdout.puts "Attaching to launch #{ReportPortal.launch_id}" else description = ReportPortal::Settings.instance.description - description ||= cmd_args.map { |arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]") }.join(' ') + description ||= cmd_args.map {|arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]")}.join(' ') ReportPortal.start_launch(description, time_to_send(desired_time)) end end @@ -80,7 +81,7 @@ def test_step_started(event, desired_time = ReportPortal.now) if step_source.multiline_arg.doc_string? message << %(\n"""\n#{step_source.multiline_arg.content}\n""") elsif step_source.multiline_arg.data_table? - message << step_source.multiline_arg.raw.reduce("\n") { |acc, row| acc << "| #{row.join(' | ')} |\n" } + message << step_source.multiline_arg.raw.reduce("\n") {|acc, row| acc << "| #{row.join(' | ')} |\n"} end ReportPortal.send_log(:trace, message, time_to_send(desired_time)) end @@ -102,7 +103,7 @@ def test_step_finished(event, desired_time = ReportPortal.now) end if status != :passed - log_level = (status == :skipped)? :warn : :error + log_level = (status == :skipped) ? :warn : :error step_type = if step?(test_step) 'Step' else @@ -129,7 +130,7 @@ def puts(message, desired_time = ReportPortal.now) end def embed(src, mime_type, label, desired_time = ReportPortal.now) - ReportPortal.send_file(:info, src, label, time_to_send(desired_time),mime_type) + ReportPortal.send_file(:info, src, label, time_to_send(desired_time), mime_type) end private @@ -165,8 +166,9 @@ def start_feature_with_parentage(feature, desired_time) tags = [] type = :SUITE else + # TODO: Consider adding feature description and comments. name = "#{feature.keyword}: #{feature.name}" - description = feature.file # TODO: consider adding feature description and comments + description = feature.file tags = feature.tags.map(&:name) type = :TEST end @@ -182,7 +184,7 @@ def start_feature_with_parentage(feature, desired_time) item = ReportPortal::TestItem.new(name, type, nil, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node - item.id = ReportPortal.start_item(child_node) # TODO: multithreading + item.id = ReportPortal.start_item(child_node) end end parent_node = child_node From ad7c8d6022e631afff2a6ec7355b9df208cadc7d Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:31:15 -0500 Subject: [PATCH 42/98] Implement a folder creation lock file to address duplicate folders being created --- lib/report_portal/cucumber/report.rb | 31 +++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 948b3b6..8ede8aa 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -172,11 +172,32 @@ def start_feature_with_parentage(feature, desired_time) tags = feature.tags.map(&:name) type = :TEST end - # TODO: multithreading # Parallel formatter always executes scenarios inside the same feature in the same process - if parallel? && - index < path_components.size - 1 && # is folder? - (id_of_created_item = ReportPortal.item_id_of(name, parent_node)) # get id for folder from report portal - # get child id from other process + is_created = false + if parallel? && name.include?("Folder:") + folder_name = name.gsub("Folder: ", "") + $folder_creation_tracking_file = ".reportportal/folder_creation_tracking_#{ReportPortal.launch_id}.lck" + File.open($folder_creation_tracking_file, 'r+') do |f| + f.flock(File::LOCK_SH) + report_portal_folders = f.read + if report_portal_folders + report_portal_folders_array = report_portal_folders.split(/\n/) + if report_portal_folders_array.include?(folder_name) + is_created = true + end + end + f.flock(File::LOCK_UN) + end + unless is_created + File.open($folder_creation_tracking_file, 'a') do |f| + f.flock(File::LOCK_EX) + f.write("\n#{folder_name}") + f.flush + f.flock(File::LOCK_UN) + end + end + end + if parallel? && index < path_components.size - 1 && is_created + id_of_created_item = ReportPortal.item_id_of(name, parent_node) item = ReportPortal::TestItem.new(name, type, id_of_created_item, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node From b9b6b14bfd8765cfcbfa4236727a8cb0eedbc6b9 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:32:58 -0500 Subject: [PATCH 43/98] Delay each process by its ID number to create a staggered start to reporting in Report Portal (addresses load issues) --- lib/report_portal/cucumber/parallel_report.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 72ab38c..4cf322f 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -34,6 +34,8 @@ def initialize(desired_time) ReportPortal.launch_id = f.read f.flock(File::LOCK_UN) end + sleep_time = ENV['TEST_ENV_NUMBER'].to_i + sleep(ENV['TEST_ENV_NUMBER'].to_i) end end From 1f40f5835d61c263d384293389cb20945c292f69 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 22 Feb 2019 14:33:12 -0500 Subject: [PATCH 44/98] Formatting and cleanup in parallel_report.rb --- lib/report_portal/cucumber/parallel_report.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 4cf322f..365a980 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -11,7 +11,6 @@ def initialize(desired_time) @root_node = Tree::TreeNode.new('') ReportPortal.last_used_time = 0 set_parallel_tests_vars - if ParallelTests.first_process? File.open(file_with_launch_id, 'w') do |f| f.flock(File::LOCK_EX) @@ -25,7 +24,7 @@ def initialize(desired_time) loop do break if File.exist?(file_with_launch_id) if monotonic_time - start_time > wait_time_for_launch_start - raise "File with launch id wasn't created during #{wait_time_for_launch_start} seconds" + raise "File with launch ID wasn't created after waiting #{wait_time_for_launch_start} seconds" end sleep 0.5 end From e61d8f7a6292ce98818717b67bb5a652e6638b08 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Tue, 19 Mar 2019 16:23:22 -0400 Subject: [PATCH 45/98] Use temporary directory for tracking folder creation --- lib/report_portal/cucumber/report.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 8ede8aa..abc0d9f 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -175,7 +175,7 @@ def start_feature_with_parentage(feature, desired_time) is_created = false if parallel? && name.include?("Folder:") folder_name = name.gsub("Folder: ", "") - $folder_creation_tracking_file = ".reportportal/folder_creation_tracking_#{ReportPortal.launch_id}.lck" + $folder_creation_tracking_file = (Pathname(Dir.tmpdir)) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" File.open($folder_creation_tracking_file, 'r+') do |f| f.flock(File::LOCK_SH) report_portal_folders = f.read From 3bc8d9a1f636b2909d1303f47fe9a077b8742f0b Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Tue, 19 Mar 2019 16:23:35 -0400 Subject: [PATCH 46/98] Add commenting around why we are delaying threads --- lib/report_portal/cucumber/parallel_report.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 365a980..ece5598 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -34,7 +34,7 @@ def initialize(desired_time) f.flock(File::LOCK_UN) end sleep_time = ENV['TEST_ENV_NUMBER'].to_i - sleep(ENV['TEST_ENV_NUMBER'].to_i) + sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision end end From 893af0fd181377f2be9e230fdd0fa173fa355780 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Tue, 19 Mar 2019 16:27:34 -0400 Subject: [PATCH 47/98] Make sure to use a "full path" for folder creation tracking --- lib/report_portal/cucumber/report.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index abc0d9f..c68afb2 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -157,6 +157,7 @@ def start_feature_with_parentage(feature, desired_time) parent_node = @root_node child_node = nil path_components = feature.location.file.split(File::SEPARATOR) + path_components_no_feature = feature.location.file.split(File::SEPARATOR)[0...path_components.size - 1] path_components.each_with_index do |path_component, index| child_node = parent_node[path_component] unless child_node # if child node was not created yet @@ -175,13 +176,20 @@ def start_feature_with_parentage(feature, desired_time) is_created = false if parallel? && name.include?("Folder:") folder_name = name.gsub("Folder: ", "") + folder_name_for_tracker = "./#{folder_name}" # create a full path file name to use for tracking + if index > 0 + folder_name_for_tracker = "./" + for path_index in (0...path_components_no_feature.length) + folder_name_for_tracker += "#{path_components_no_feature[path_index]}/" + end + end $folder_creation_tracking_file = (Pathname(Dir.tmpdir)) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" File.open($folder_creation_tracking_file, 'r+') do |f| f.flock(File::LOCK_SH) report_portal_folders = f.read if report_portal_folders report_portal_folders_array = report_portal_folders.split(/\n/) - if report_portal_folders_array.include?(folder_name) + if report_portal_folders_array.include?(folder_name_for_tracker) is_created = true end end @@ -190,7 +198,7 @@ def start_feature_with_parentage(feature, desired_time) unless is_created File.open($folder_creation_tracking_file, 'a') do |f| f.flock(File::LOCK_EX) - f.write("\n#{folder_name}") + f.write("\n#{folder_name_for_tracker}") f.flush f.flock(File::LOCK_UN) end From a92fc75e66667a1d6f338df5ed56e81a752da7f5 Mon Sep 17 00:00:00 2001 From: Mike Salvia Date: Fri, 12 Apr 2019 15:38:53 -0400 Subject: [PATCH 48/98] Switch to use class instance variables instead of global variable for folder creation tracking lock file --- lib/report_portal/cucumber/report.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index c68afb2..cfb369d 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -11,6 +11,8 @@ module Cucumber # @api private class Report + @folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" + def parallel? false end @@ -183,8 +185,8 @@ def start_feature_with_parentage(feature, desired_time) folder_name_for_tracker += "#{path_components_no_feature[path_index]}/" end end - $folder_creation_tracking_file = (Pathname(Dir.tmpdir)) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" - File.open($folder_creation_tracking_file, 'r+') do |f| + @folder_creation_tracking_file = (Pathname(Dir.tmpdir)) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" + File.open(@folder_creation_tracking_file, 'r+') do |f| f.flock(File::LOCK_SH) report_portal_folders = f.read if report_portal_folders @@ -196,7 +198,7 @@ def start_feature_with_parentage(feature, desired_time) f.flock(File::LOCK_UN) end unless is_created - File.open($folder_creation_tracking_file, 'a') do |f| + File.open(@folder_creation_tracking_file, 'a') do |f| f.flock(File::LOCK_EX) f.write("\n#{folder_name_for_tracker}") f.flush From c6bb6ec40503f371e03ce3e72b212acebbe858e4 Mon Sep 17 00:00:00 2001 From: vveliev Date: Fri, 24 May 2019 14:17:29 -0400 Subject: [PATCH 49/98] work in progress, refactoring lock generation for parallel tests --- lib/report_portal/cucumber/parallel_report.rb | 29 +++++------ lib/report_portal/cucumber/report.rb | 48 +++++++++++++++++-- lib/report_portal/rspec/formatter.rb | 1 + lib/report_portal/settings.rb | 10 ++++ 4 files changed, 66 insertions(+), 22 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index ece5598..80c9090 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -3,6 +3,7 @@ module ReportPortal module Cucumber class ParallelReport < Report + def parallel? true end @@ -12,26 +13,18 @@ def initialize(desired_time) ReportPortal.last_used_time = 0 set_parallel_tests_vars if ParallelTests.first_process? - File.open(file_with_launch_id, 'w') do |f| - f.flock(File::LOCK_EX) - start_launch(desired_time, @cmd_args_of_parallel_tests) - f.write(ReportPortal.launch_id) - f.flush - f.flock(File::LOCK_UN) - end + start_launch(desired_time) else start_time = monotonic_time loop do - break if File.exist?(file_with_launch_id) + break if File.exist?(lock_file) if monotonic_time - start_time > wait_time_for_launch_start raise "File with launch ID wasn't created after waiting #{wait_time_for_launch_start} seconds" end sleep 0.5 end - File.open(file_with_launch_id, 'r') do |f| - f.flock(File::LOCK_SH) + File.open(lock_file, 'r') do |f| ReportPortal.launch_id = f.read - f.flock(File::LOCK_UN) end sleep_time = ENV['TEST_ENV_NUMBER'].to_i sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision @@ -44,7 +37,7 @@ def done(desired_time = ReportPortal.now) if ParallelTests.first_process? ParallelTests.wait_for_other_processes_to_finish - File.delete(file_with_launch_id) + File.delete(lock_file) unless attach_to_launch? $stdout.puts "Finishing launch #{ReportPortal.launch_id}" @@ -55,12 +48,13 @@ def done(desired_time = ReportPortal.now) end end - private - - def file_with_launch_id - Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lck" + def lock_file + file_path ||= tmp_dir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" + super end + private + def set_parallel_tests_vars pid = Process.pid loop do @@ -76,8 +70,9 @@ def set_parallel_tests_vars end end + #time required for first tread to created remote project in RP and save id to file def wait_time_for_launch_start - 60 + ENV['rp_parallel_launch_wait_time'] ? ENV['rp_parallel_launch_wait_time'] : 60 end def monotonic_time diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index cfb369d..aa8c9d1 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -28,19 +28,53 @@ def initialize end def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) + # Not sure what is the use case if launch id is missing. But it does not make much of practical usage + # + # Expected behavior that make sense: + # 1. If launch_id present attach to existing (simple use case) + # 2. If launch_id not present check if exist rp_launch_id.tmp + # 3. [ADDED] If launch_id is not present check if lock exist with launch_uuid if attach_to_launch? ReportPortal.launch_id = if ReportPortal::Settings.instance.launch_id ReportPortal::Settings.instance.launch_id else - file_path = ReportPortal::Settings.instance.file_with_launch_id || (Pathname(Dir.tmpdir) + 'rp_launch_id.tmp') - File.read(file_path) + file_path = lock_file + if File.file?(file_path) + File.read(file_path) + else + new_launch(desired_time, cmd_args, file_path) + end end $stdout.puts "Attaching to launch #{ReportPortal.launch_id}" + else - description = ReportPortal::Settings.instance.description - description ||= cmd_args.map {|arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]")}.join(' ') - ReportPortal.start_launch(description, time_to_send(desired_time)) + new_launch(desired_time, cmd_args) + end + end + + def lock_file + file_path ||= ReportPortal::Settings.instance.file_with_launch_id + file_path ||= tmp_dir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid + file_path ||= tmp_dir + 'rp_launch_id.tmp' + file_path + end + + def new_launch(desired_time = ReportPortal.now, cmd_args = ARGV, lock_file = nil) + description = ReportPortal::Settings.instance.description + description ||= cmd_args.map {|arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]")}.join(' ') + ReportPortal.start_launch(description, time_to_send(desired_time)) + set_file_lock_with_launch_id(lock_file, ReportPortal.launch_id) if lock_file + ReportPortal.launch_id + end + + def set_file_lock_with_launch_id(lock_file, launch_id) + FileUtils.mkdir_p lock_file.dirname + File.open(lock_file, 'w') do |f| + f.flock(File::LOCK_EX) + f.write(launch_id) + f.flush + f.flock(File::LOCK_UN) end end @@ -151,6 +185,10 @@ def time_to_send(desired_time) ReportPortal.last_used_time = time_to_send end + def tmp_dir + Pathname(ENV['TMPDIR'] ? ENV['TMPDIR'] : Dir.tmpdir) + end + def same_feature_as_previous_test_case?(feature) @feature_node && @feature_node.name == feature.location.file.split(File::SEPARATOR).last end diff --git a/lib/report_portal/rspec/formatter.rb b/lib/report_portal/rspec/formatter.rb index 0e6c220..3098c6c 100644 --- a/lib/report_portal/rspec/formatter.rb +++ b/lib/report_portal/rspec/formatter.rb @@ -22,6 +22,7 @@ def initialize(_output) def start(_start_notification) cmd_args = ARGV.map { |arg| (arg.include? 'rp_uuid=')? 'rp_uuid=[FILTERED]' : arg }.join(' ') + debugger ReportPortal.start_launch(cmd_args) @root_node = Tree::TreeNode.new(SecureRandom.hex) @current_group_node = @root_node diff --git a/lib/report_portal/settings.rb b/lib/report_portal/settings.rb index a8f48fa..96a2776 100644 --- a/lib/report_portal/settings.rb +++ b/lib/report_portal/settings.rb @@ -28,18 +28,28 @@ def initialize 'use_standard_logger' => false, 'launch_id' => false, 'file_with_launch_id' => false, + 'launch_uuid'=>true #used when multiple cucumber processes executed } keys.each do |key, is_required| define_singleton_method(key.to_sym) { setting(key) } fail "ReportPortal: Define environment variable '#{PREFIX}#{key}' or key #{key} in the configuration YAML file" if is_required && public_send(key).nil? end + launch_uuid = SecureRandom.uuid unless launch_uuid end def launch_mode is_debug ? 'DEBUG' : 'DEFAULT' end + def file_with_launch_id=(val) + @file_with_launch_id = val + end + + def file_with_launch_id + @file_with_launch_id + end + def formatter_modes setting('formatter_modes') || [] end From f97e072004e2a3b7080138adf81c2830aae79bc7 Mon Sep 17 00:00:00 2001 From: vveliev Date: Sat, 25 May 2019 05:07:53 -0400 Subject: [PATCH 50/98] fixing parallel execution --- lib/report_portal/cucumber/formatter.rb | 5 +- lib/report_portal/cucumber/parallel_report.rb | 19 ++++++- lib/report_portal/cucumber/report.rb | 49 +++++-------------- lib/reportportal.rb | 9 ++++ 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index ac45900..8221669 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -1,17 +1,18 @@ require 'thread' +require 'cucumber/formatter/io' require_relative 'report' module ReportPortal module Cucumber class Formatter + include ::Cucumber::Formatter::Io # @api private def initialize(config) - ENV['REPORT_PORTAL_USED'] = 'true' setup_message_processing - @io = config.out_stream + @io = ensure_io(config.out_stream) [:test_case_started, :test_case_finished, :test_step_started, :test_step_finished, :test_run_finished].each do |event_name| config.on_event event_name do |event| diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 80c9090..e6011ff 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -26,10 +26,17 @@ def initialize(desired_time) File.open(lock_file, 'r') do |f| ReportPortal.launch_id = f.read end - sleep_time = ENV['TEST_ENV_NUMBER'].to_i + add_process_description + sleep_time = 5 sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision end end + + def add_process_description + description = ReportPortal.get_launch['description'].split(' ') + description.push(self.description().split(' ')) + ReportPortal.update_launch({description: description.join(' ')}) + end def done(desired_time = ReportPortal.now) end_feature(desired_time) if @feature_node @@ -50,7 +57,10 @@ def done(desired_time = ReportPortal.now) def lock_file file_path ||= tmp_dir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" - super + file_path ||= ReportPortal::Settings.instance.file_with_launch_id + file_path ||= tmp_dir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid + file_path ||= tmp_dir + 'rp_launch_id.tmp' + file_path end private @@ -59,6 +69,11 @@ def set_parallel_tests_vars pid = Process.pid loop do current_process = Sys::ProcTable.ps(pid) + #TODO: add exception to fall back to cucumber process + # 1. if rm_launch_uuid was created by some other parallel script that executes cucumber batch of feature files + # 2. if fallback to cucumber process, this allows to use same formatter sequential and parallel executions + # useful when formatters are default configured in AfterConfiguration hook + # config.formats.push(["ReportPortal::Cucumber::ParallelFormatter", {}, set_up_output_format(report_name, :report_portal)]) raise 'Could not find parallel_cucumber/parallel_test in ancestors of current process' if current_process.nil? match = current_process.cmdline.match(/bin(?:\/|\\)parallel_(?:cucumber|test)(.+)/) if match diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index aa8c9d1..b0d3773 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -11,8 +11,6 @@ module Cucumber # @api private class Report - @folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" - def parallel? false end @@ -61,13 +59,17 @@ def lock_file end def new_launch(desired_time = ReportPortal.now, cmd_args = ARGV, lock_file = nil) - description = ReportPortal::Settings.instance.description - description ||= cmd_args.map {|arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]")}.join(' ') - ReportPortal.start_launch(description, time_to_send(desired_time)) + ReportPortal.start_launch(description(cmd_args), time_to_send(desired_time)) set_file_lock_with_launch_id(lock_file, ReportPortal.launch_id) if lock_file ReportPortal.launch_id end + def description(cmd_args=ARGV) + description ||= ReportPortal::Settings.instance.description + description ||= cmd_args.map {|arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]")}.join(' ') + description + end + def set_file_lock_with_launch_id(lock_file, launch_id) FileUtils.mkdir_p lock_file.dirname File.open(lock_file, 'w') do |f| @@ -213,39 +215,10 @@ def start_feature_with_parentage(feature, desired_time) tags = feature.tags.map(&:name) type = :TEST end - is_created = false - if parallel? && name.include?("Folder:") - folder_name = name.gsub("Folder: ", "") - folder_name_for_tracker = "./#{folder_name}" # create a full path file name to use for tracking - if index > 0 - folder_name_for_tracker = "./" - for path_index in (0...path_components_no_feature.length) - folder_name_for_tracker += "#{path_components_no_feature[path_index]}/" - end - end - @folder_creation_tracking_file = (Pathname(Dir.tmpdir)) + "folder_creation_tracking_#{ReportPortal.launch_id}.lck" - File.open(@folder_creation_tracking_file, 'r+') do |f| - f.flock(File::LOCK_SH) - report_portal_folders = f.read - if report_portal_folders - report_portal_folders_array = report_portal_folders.split(/\n/) - if report_portal_folders_array.include?(folder_name_for_tracker) - is_created = true - end - end - f.flock(File::LOCK_UN) - end - unless is_created - File.open(@folder_creation_tracking_file, 'a') do |f| - f.flock(File::LOCK_EX) - f.write("\n#{folder_name_for_tracker}") - f.flush - f.flock(File::LOCK_UN) - end - end - end - if parallel? && index < path_components.size - 1 && is_created - id_of_created_item = ReportPortal.item_id_of(name, parent_node) + if parallel? && + index < path_components.size - 1 && # is folder? + (id_of_created_item = ReportPortal.item_id_of(name, parent_node)) # get id for folder from report portal + # get child id from other process item = ReportPortal::TestItem.new(name, type, id_of_created_item, time_to_send(desired_time), description, false, tags) child_node = Tree::TreeNode.new(path_component, item) parent_node << child_node diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 5f05ef5..ddcf694 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -37,6 +37,15 @@ def start_launch(description, start_time = now) @launch_id = JSON.parse(response)['id'] end + def get_launch() + response = project_resource["launch/#{@launch_id}"].get + JSON.parse(response) + end + + def update_launch(data) + project_resource["launch/#{@launch_id}/update"].put(data.to_json) + end + def finish_launch(end_time = now) data = { end_time: end_time } project_resource["launch/#{@launch_id}/finish"].put(data.to_json) From d2d2cbe5935e4410abbca6b17c7af77dc2d213fb Mon Sep 17 00:00:00 2001 From: vveliev Date: Mon, 27 May 2019 11:21:13 -0400 Subject: [PATCH 51/98] small refactoring --- lib/report_portal/cucumber/parallel_report.rb | 18 ++++++++---------- lib/report_portal/cucumber/report.rb | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index e6011ff..abc6897 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -31,11 +31,11 @@ def initialize(desired_time) sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision end end - + def add_process_description description = ReportPortal.get_launch['description'].split(' ') - description.push(self.description().split(' ')) - ReportPortal.update_launch({description: description.join(' ')}) + description.push(self.description().split(' ').strip) + ReportPortal.update_launch({description: description.uniq.join(' ')}) end def done(desired_time = ReportPortal.now) @@ -46,8 +46,9 @@ def done(desired_time = ReportPortal.now) File.delete(lock_file) - unless attach_to_launch? - $stdout.puts "Finishing launch #{ReportPortal.launch_id}" + # TODO: if parallel test does not need to finish launch, there should be env variable to support this. + # as report launch is is created by first process it should also be finished by same process + unless ReportPortal::Settings.instance.file_with_launch_id ReportPortal.close_child_items(nil) time_to_send = time_to_send(desired_time) ReportPortal.finish_launch(time_to_send) @@ -55,12 +56,9 @@ def done(desired_time = ReportPortal.now) end end - def lock_file + def lock_file(file_path = nil) file_path ||= tmp_dir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" - file_path ||= ReportPortal::Settings.instance.file_with_launch_id - file_path ||= tmp_dir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid - file_path ||= tmp_dir + 'rp_launch_id.tmp' - file_path + super(file_path) end private diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index b0d3773..8fb21cd 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -51,7 +51,7 @@ def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) end end - def lock_file + def lock_file(file_path = nil) file_path ||= ReportPortal::Settings.instance.file_with_launch_id file_path ||= tmp_dir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid file_path ||= tmp_dir + 'rp_launch_id.tmp' From f30977b6d42e867596875da12cc0c9f1c6a981fa Mon Sep 17 00:00:00 2001 From: vveliev Date: Mon, 27 May 2019 17:42:57 -0400 Subject: [PATCH 52/98] fixing report description when run in parallel --- lib/report_portal/cucumber/parallel_report.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index abc6897..552334b 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -34,7 +34,7 @@ def initialize(desired_time) def add_process_description description = ReportPortal.get_launch['description'].split(' ') - description.push(self.description().split(' ').strip) + description.push(self.description().split(' ')).flatten! ReportPortal.update_launch({description: description.uniq.join(' ')}) end From 265edef51176a59cce655dc7b7c9b2c67ebb9070 Mon Sep 17 00:00:00 2001 From: vveliev Date: Fri, 31 May 2019 11:09:12 -0400 Subject: [PATCH 53/98] - removing debugger - if launch_id or file_with_launch_id is provided launch will not be marked as finished - moved sleep time, since we are waiting for report to be created anyways --- lib/report_portal/cucumber/parallel_report.rb | 10 ++++++---- lib/report_portal/cucumber/report.rb | 9 +++------ lib/report_portal/rspec/formatter.rb | 1 - 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 552334b..60811db 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -23,12 +23,13 @@ def initialize(desired_time) end sleep 0.5 end + sleep_time = 5 + sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision + File.open(lock_file, 'r') do |f| ReportPortal.launch_id = f.read end add_process_description - sleep_time = 5 - sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision end end @@ -48,7 +49,7 @@ def done(desired_time = ReportPortal.now) # TODO: if parallel test does not need to finish launch, there should be env variable to support this. # as report launch is is created by first process it should also be finished by same process - unless ReportPortal::Settings.instance.file_with_launch_id + unless ReportPortal::Settings.instance.launch_id || ReportPortal::Settings.instance.file_with_launch_id ReportPortal.close_child_items(nil) time_to_send = time_to_send(desired_time) ReportPortal.finish_launch(time_to_send) @@ -57,7 +58,7 @@ def done(desired_time = ReportPortal.now) end def lock_file(file_path = nil) - file_path ||= tmp_dir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" + file_path ||= Dir.tmpdir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" super(file_path) end @@ -66,6 +67,7 @@ def lock_file(file_path = nil) def set_parallel_tests_vars pid = Process.pid loop do + #FIXME failing in jenkins current_process = Sys::ProcTable.ps(pid) #TODO: add exception to fall back to cucumber process # 1. if rm_launch_uuid was created by some other parallel script that executes cucumber batch of feature files diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 8fb21cd..3640c68 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -2,6 +2,7 @@ require 'cucumber/formatter/hook_query_visitor' require 'tree' require 'securerandom' +require 'tempfile' require_relative '../../reportportal' require_relative '../logging/logger' @@ -53,8 +54,8 @@ def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) def lock_file(file_path = nil) file_path ||= ReportPortal::Settings.instance.file_with_launch_id - file_path ||= tmp_dir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid - file_path ||= tmp_dir + 'rp_launch_id.tmp' + file_path ||= Dir.tmpdir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid + file_path ||= Dir.tmpdir + 'rp_launch_id.tmp' file_path end @@ -187,10 +188,6 @@ def time_to_send(desired_time) ReportPortal.last_used_time = time_to_send end - def tmp_dir - Pathname(ENV['TMPDIR'] ? ENV['TMPDIR'] : Dir.tmpdir) - end - def same_feature_as_previous_test_case?(feature) @feature_node && @feature_node.name == feature.location.file.split(File::SEPARATOR).last end diff --git a/lib/report_portal/rspec/formatter.rb b/lib/report_portal/rspec/formatter.rb index 3098c6c..0e6c220 100644 --- a/lib/report_portal/rspec/formatter.rb +++ b/lib/report_portal/rspec/formatter.rb @@ -22,7 +22,6 @@ def initialize(_output) def start(_start_notification) cmd_args = ARGV.map { |arg| (arg.include? 'rp_uuid=')? 'rp_uuid=[FILTERED]' : arg }.join(' ') - debugger ReportPortal.start_launch(cmd_args) @root_node = Tree::TreeNode.new(SecureRandom.hex) @current_group_node = @root_node From 0d3fba85ccd8f340b86d37eb1f1de510f5172950 Mon Sep 17 00:00:00 2001 From: vveliev Date: Fri, 31 May 2019 13:27:00 -0400 Subject: [PATCH 54/98] - reverting file lock changes for reading the file - moving lock file related functions to private --- lib/report_portal/cucumber/parallel_report.rb | 9 ++---- lib/report_portal/cucumber/report.rb | 31 +++++++++++-------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 60811db..18058ec 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -25,10 +25,7 @@ def initialize(desired_time) end sleep_time = 5 sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision - - File.open(lock_file, 'r') do |f| - ReportPortal.launch_id = f.read - end + ReportPortal.launch_id = read_lock_file(lock_file) add_process_description end end @@ -57,13 +54,13 @@ def done(desired_time = ReportPortal.now) end end + private + def lock_file(file_path = nil) file_path ||= Dir.tmpdir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" super(file_path) end - private - def set_parallel_tests_vars pid = Process.pid loop do diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 3640c68..36b6ea9 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -39,26 +39,14 @@ def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) ReportPortal::Settings.instance.launch_id else file_path = lock_file - if File.file?(file_path) - File.read(file_path) - else - new_launch(desired_time, cmd_args, file_path) - end + File.file?(file_path) ? read_lock_file(file_path) : new_launch(desired_time, cmd_args, file_path) end $stdout.puts "Attaching to launch #{ReportPortal.launch_id}" - else new_launch(desired_time, cmd_args) end end - def lock_file(file_path = nil) - file_path ||= ReportPortal::Settings.instance.file_with_launch_id - file_path ||= Dir.tmpdir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid - file_path ||= Dir.tmpdir + 'rp_launch_id.tmp' - file_path - end - def new_launch(desired_time = ReportPortal.now, cmd_args = ARGV, lock_file = nil) ReportPortal.start_launch(description(cmd_args), time_to_send(desired_time)) set_file_lock_with_launch_id(lock_file, ReportPortal.launch_id) if lock_file @@ -174,6 +162,23 @@ def embed(src, mime_type, label, desired_time = ReportPortal.now) private + def lock_file(file_path = nil) + file_path ||= ReportPortal::Settings.instance.file_with_launch_id + file_path ||= Dir.tmpdir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid + file_path ||= Dir.tmpdir + 'rp_launch_id.tmp' + file_path + end + + def read_lock_file(file_path) + content = nil + File.open(file_with_launch_id, 'r') do |f| + f.flock(File::LOCK_SH) + content = File.read(file_path) + f.flock(File::LOCK_UN) + end + content + end + # Report Portal sorts logs by time. However, several logs might have the same time. # So to get Report Portal sort them properly the time should be different in all logs related to the same item. # And thus it should be stored. From 6215a535136620a2d258ae47c8151eb75498bc11 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 20 Jun 2019 12:32:55 -0400 Subject: [PATCH 55/98] - fixing temp file path generation - refactoring how runner process is detected - added fallback to cucumber if parallel test was not for parallel formatter --- lib/report_portal/cucumber/parallel_report.rb | 38 ++++++++++--------- lib/report_portal/cucumber/report.rb | 6 +-- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 18058ec..c84696f 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -57,29 +57,31 @@ def done(desired_time = ReportPortal.now) private def lock_file(file_path = nil) - file_path ||= Dir.tmpdir + "parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" + file_path ||= Dir.tmpdir + "/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" super(file_path) end def set_parallel_tests_vars - pid = Process.pid - loop do - #FIXME failing in jenkins - current_process = Sys::ProcTable.ps(pid) - #TODO: add exception to fall back to cucumber process - # 1. if rm_launch_uuid was created by some other parallel script that executes cucumber batch of feature files - # 2. if fallback to cucumber process, this allows to use same formatter sequential and parallel executions - # useful when formatters are default configured in AfterConfiguration hook - # config.formats.push(["ReportPortal::Cucumber::ParallelFormatter", {}, set_up_output_format(report_name, :report_portal)]) - raise 'Could not find parallel_cucumber/parallel_test in ancestors of current process' if current_process.nil? - match = current_process.cmdline.match(/bin(?:\/|\\)parallel_(?:cucumber|test)(.+)/) - if match - @pid_of_parallel_tests = current_process.pid - @cmd_args_of_parallel_tests = match[1].strip.split - break - end - pid = Sys::ProcTable.ps(pid).ppid + process_list = Sys::ProcTable.ps + runner_process ||= get_parallel_test_process(process_list) + runner_process ||= get_cucumber_test_process(process_list) + raise 'Could not find parallel_cucumber/parallel_test in ancestors of current process' if runner_process.nil? + @pid_of_parallel_tests = runner_process.pid + @cmd_args_of_parallel_tests = runner_process.cmdline.split(' ',2).pop + end + + def get_parallel_test_process(process_list) + process_list.each do |process| + return process if process.cmdline.match(/bin(?:\/|\\)parallel_(?:cucumber|test)(.+)/) + end + nil + end + + def get_cucumber_test_process(process_list) + process_list.each do |process| + return process if process.cmdline.match(/bin(?:\/|\\)(?:cucumber)(.+)/) end + nil end #time required for first tread to created remote project in RP and save id to file diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 36b6ea9..6035c99 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -60,7 +60,7 @@ def description(cmd_args=ARGV) end def set_file_lock_with_launch_id(lock_file, launch_id) - FileUtils.mkdir_p lock_file.dirname + FileUtils.mkdir_p File.dirname(lock_file) File.open(lock_file, 'w') do |f| f.flock(File::LOCK_EX) f.write(launch_id) @@ -164,8 +164,8 @@ def embed(src, mime_type, label, desired_time = ReportPortal.now) def lock_file(file_path = nil) file_path ||= ReportPortal::Settings.instance.file_with_launch_id - file_path ||= Dir.tmpdir + "report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid - file_path ||= Dir.tmpdir + 'rp_launch_id.tmp' + file_path ||= Dir.tmpdir + "/report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid + file_path ||= Dir.tmpdir + '/rp_launch_id.tmp' file_path end From 3e655b624cb551df7bdd060860bd4aca8b4dd609 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 20 Jun 2019 13:17:46 -0400 Subject: [PATCH 56/98] - reverting some changes - launch_uuid related fixes --- lib/report_portal/cucumber/report.rb | 2 +- lib/report_portal/settings.rb | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 6035c99..ca15207 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -171,7 +171,7 @@ def lock_file(file_path = nil) def read_lock_file(file_path) content = nil - File.open(file_with_launch_id, 'r') do |f| + File.open(file_path, 'r') do |f| f.flock(File::LOCK_SH) content = File.read(file_path) f.flock(File::LOCK_UN) diff --git a/lib/report_portal/settings.rb b/lib/report_portal/settings.rb index 96a2776..29f44be 100644 --- a/lib/report_portal/settings.rb +++ b/lib/report_portal/settings.rb @@ -28,28 +28,20 @@ def initialize 'use_standard_logger' => false, 'launch_id' => false, 'file_with_launch_id' => false, - 'launch_uuid'=>true #used when multiple cucumber processes executed + 'launch_uuid'=>false } keys.each do |key, is_required| define_singleton_method(key.to_sym) { setting(key) } fail "ReportPortal: Define environment variable '#{PREFIX}#{key}' or key #{key} in the configuration YAML file" if is_required && public_send(key).nil? end - launch_uuid = SecureRandom.uuid unless launch_uuid + launch_uuid ||= SecureRandom.uuid end def launch_mode is_debug ? 'DEBUG' : 'DEFAULT' end - def file_with_launch_id=(val) - @file_with_launch_id = val - end - - def file_with_launch_id - @file_with_launch_id - end - def formatter_modes setting('formatter_modes') || [] end From 8985eca06d4fc363c36268d651e8005281fb1652 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 20 Jun 2019 20:13:47 -0400 Subject: [PATCH 57/98] - fixes after merge - update readme --- README.md | 29 +++++++++++++++---- lib/report_portal/cucumber/parallel_report.rb | 6 ++-- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 80de6cb..39894b0 100644 --- a/README.md +++ b/README.md @@ -75,11 +75,30 @@ WebMock.disable_net_connect!(:net_http_connect_on_start => true, :allow_localhos ## Formatter modes The following modes are supported: - -| Name | Purpose | -| --- | --- | -| attach_to_launch | Do not create a new launch but add executing features/scenarios to an existing launch. Use launch_id or file_with_launch_id settings to configure that. If they are not present client will check rp_launch_id.tmp in `Dir.tmpdir`) -| use_same_thread_for_reporting | Send reporting commands in the same main thread used for running tests. This mode is useful for debugging this Report Portal client. It changes default behavior to send commands in the separate thread. Default behavior is there not to slow test execution. | + + + + + + + + + + + +
NamePurpose
attach_to_launch +Add executing features/scenarios to an existing launch. +Use following options to configure that. + + 1. launch_id + 2. file_with_launch_id + 3. rp_launch_id.tmp in `Dir.tmpdir` + + If above options not present client will create new launch +
use_same_thread_for_reporting +Send reporting commands in the same main thread used for running tests. This mode is useful for debugging +Report Portal client. It changes default behavior to send commands in the separate thread. +Default behavior is there not to slow test execution.
## Logging Experimental support for three common logging frameworks was added: diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 075b035..90a1dfc 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -11,12 +11,12 @@ def parallel? true end - def initialize(desired_time) + def initialize() @root_node = Tree::TreeNode.new('') ReportPortal.last_used_time = 0 set_parallel_tests_vars if ParallelTests.first_process? - start_launch(desired_time) + start_launch() else start_time = monotonic_time loop do @@ -26,8 +26,6 @@ def initialize(desired_time) end sleep 0.5 end - sleep_time = 5 - sleep(sleep_time) # stagger start times for reporting to Report Portal to avoid collision ReportPortal.launch_id = read_lock_file(lock_file) add_process_description end From f5cc434c5887b7d84e9b10c8a5efca70b4e2b5c8 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 20 Jun 2019 20:55:58 -0400 Subject: [PATCH 58/98] layout fixes --- Gemfile | 6 +++--- lib/report_portal/cucumber/formatter.rb | 4 ++-- lib/report_portal/cucumber/json_slurper.rb | 12 ++++++------ lib/report_portal/cucumber/parallel_report.rb | 18 +++++++++--------- lib/report_portal/cucumber/report.rb | 9 +++------ lib/report_portal/settings.rb | 3 +-- lib/reportportal.rb | 2 +- 7 files changed, 25 insertions(+), 29 deletions(-) diff --git a/Gemfile b/Gemfile index e34e42a..586f0ad 100644 --- a/Gemfile +++ b/Gemfile @@ -4,10 +4,10 @@ gemspec gem 'cucumber', '~> 3' gem 'rest-client' -gem 'rubytree', git: 'https://github.com/razboev/RubyTree' -gem 'rspec' -gem 'rake' gem 'parallel_tests', '~> 2.15.0' +gem 'rake' +gem 'rspec' +gem 'rubytree', git: 'https://github.com/razboev/RubyTree' gem 'sys-proctable', '~> 1.1.5' gem 'log4r' diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index 70d098d..b041717 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -13,14 +13,14 @@ def initialize(config) setup_message_processing - @io = ensure_io(config.out_stream) + @io = ensure_io(config.out_stream) [:test_case_started, :test_case_finished, :test_step_started, :test_step_finished, :test_run_finished].each do |event_name| config.on_event event_name do |event| process_message(event_name, event) end end - config.on_event(:test_run_finished) { finish_message_processing } + config.on_event(:test_run_finished) {finish_message_processing} end def puts(message) diff --git a/lib/report_portal/cucumber/json_slurper.rb b/lib/report_portal/cucumber/json_slurper.rb index b2bcf85..8182935 100644 --- a/lib/report_portal/cucumber/json_slurper.rb +++ b/lib/report_portal/cucumber/json_slurper.rb @@ -61,14 +61,14 @@ def run element['steps'].each do |step| name = decorate("#{step['keyword']}#{step['name']}") if step['rows'] - name << step['rows'].reduce("\n") { |acc, row| acc << decorate("| #{row['cells'].join(' | ')} |") << "\n" } + name << step['rows'].reduce("\n") {|acc, row| acc << decorate("| #{row['cells'].join(' | ')} |") << "\n"} end if step['doc_string'] name << %(\n"""\n#{step['doc_string']['value']}\n""") end ReportPortal.send_log(:passed, name, get_time) - step['output'].each { |o| ReportPortal.send_log(:passed, o, get_time) } unless step['output'].nil? + step['output'].each {|o| ReportPortal.send_log(:passed, o, get_time)} unless step['output'].nil? error = step['result']['error_message'] ReportPortal.send_log(:failed, error, get_time) if error (step['embeddings'] || []).each do |embedding| @@ -90,9 +90,9 @@ def run end statuses += report_hooks(element, 'after') - status = if statuses.any? { |s| %w(failed undefined pending).include? s } + status = if statuses.any? {|s| %w(failed undefined pending).include? s} :failed - elsif statuses.all? { |s| s == 'passed' } + elsif statuses.all? {|s| s == 'passed'} :passed else :skipped @@ -111,7 +111,7 @@ def run private def tags(item) - item['tags'].nil? ? [] : item['tags'].map { |h| h['name'] } + item['tags'].nil? ? [] : item['tags'].map {|h| h['name']} end def decorate(str) @@ -121,7 +121,7 @@ def decorate(str) def expanded? bad_item = @json.find do |f| - so = f['elements'].find { |e| e['keyword'] == 'Scenario Outline' } + so = f['elements'].find {|e| e['keyword'] == 'Scenario Outline'} so ? so.key?('examples') : false end bad_item.nil? diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 90a1dfc..c37aefb 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -6,17 +6,16 @@ module ReportPortal module Cucumber class ParallelReport < Report - def parallel? true end - def initialize() + def initialize @root_node = Tree::TreeNode.new('') ReportPortal.last_used_time = 0 set_parallel_tests_vars if ParallelTests.first_process? - start_launch() + start_launch else start_time = monotonic_time loop do @@ -33,8 +32,8 @@ def initialize() def add_process_description description = ReportPortal.get_launch['description'].split(' ') - description.push(self.description().split(' ')).flatten! - ReportPortal.update_launch({description: description.uniq.join(' ')}) + description.push(self.description.split(' ')).flatten! + ReportPortal.update_launch(description: description.uniq.join(' ')) end def done(desired_time = ReportPortal.now) @@ -67,27 +66,28 @@ def set_parallel_tests_vars runner_process ||= get_parallel_test_process(process_list) runner_process ||= get_cucumber_test_process(process_list) raise 'Could not find parallel_cucumber/parallel_test in ancestors of current process' if runner_process.nil? + @pid_of_parallel_tests = runner_process.pid - @cmd_args_of_parallel_tests = runner_process.cmdline.split(' ',2).pop + @cmd_args_of_parallel_tests = runner_process.cmdline.split(' ', 2).pop end def get_parallel_test_process(process_list) process_list.each do |process| - return process if process.cmdline.match(/bin(?:\/|\\)parallel_(?:cucumber|test)(.+)/) + return process if process.cmdline.match(%r(bin(?:\/|\\)parallel_(?:cucumber|test)(.+))) end nil end def get_cucumber_test_process(process_list) process_list.each do |process| - return process if process.cmdline.match(/bin(?:\/|\\)(?:cucumber)(.+)/) + return process if process.cmdline.match(%r(bin(?:\/|\\)(?:cucumber)(.+))) end nil end #time required for first tread to created remote project in RP and save id to file def wait_time_for_launch_start - ENV['rp_parallel_launch_wait_time'] ? ENV['rp_parallel_launch_wait_time'] : 60 + ENV['rp_parallel_launch_wait_time'] || 60 end def monotonic_time diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index eceb393..9841db1 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -9,10 +9,8 @@ module ReportPortal module Cucumber - # @api private class Report - - @folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" + @folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" def parallel? false @@ -54,8 +52,8 @@ def new_launch(desired_time = ReportPortal.now, cmd_args = ARGV, lock_file = nil set_file_lock_with_launch_id(lock_file, ReportPortal.launch_id) if lock_file ReportPortal.launch_id end - - def description(cmd_args=ARGV) + + def description(cmd_args = ARGV) description ||= ReportPortal::Settings.instance.description description ||= cmd_args.map {|arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]")}.join(' ') description @@ -203,7 +201,6 @@ def start_feature_with_parentage(feature, desired_time) parent_node = @root_node child_node = nil path_components = feature.location.file.split(File::SEPARATOR) - path_components_no_feature = feature.location.file.split(File::SEPARATOR)[0...path_components.size - 1] path_components.each_with_index do |path_component, index| child_node = parent_node[path_component] unless child_node # if child node was not created yet diff --git a/lib/report_portal/settings.rb b/lib/report_portal/settings.rb index 29f44be..6bc3ce9 100644 --- a/lib/report_portal/settings.rb +++ b/lib/report_portal/settings.rb @@ -28,14 +28,13 @@ def initialize 'use_standard_logger' => false, 'launch_id' => false, 'file_with_launch_id' => false, - 'launch_uuid'=>false + 'launch_uuid' => false } keys.each do |key, is_required| define_singleton_method(key.to_sym) { setting(key) } fail "ReportPortal: Define environment variable '#{PREFIX}#{key}' or key #{key} in the configuration YAML file" if is_required && public_send(key).nil? end - launch_uuid ||= SecureRandom.uuid end def launch_mode diff --git a/lib/reportportal.rb b/lib/reportportal.rb index ddcf694..c2cd3b5 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -61,7 +61,7 @@ def start_item(item_node) response = project_resource[url].post(data.to_json) rescue RestClient::Exception => e response_message = JSON.parse(e.response)['message'] - m = response_message.match(/Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'/) + m = response_message.match(%r(Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+')) raise unless m time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') data[:start_time] = (time.to_f * 1000).to_i + 1000 From 0cb6a6cc59391427de1a803c149acae1b4cdc809 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 20 Jun 2019 21:18:46 -0400 Subject: [PATCH 59/98] layout fixes --- Gemfile | 2 +- lib/report_portal/cucumber/formatter.rb | 1 - lib/report_portal/cucumber/json_slurper.rb | 12 ++++++------ lib/report_portal/cucumber/parallel_report.rb | 14 +++++++------- lib/report_portal/cucumber/report.rb | 17 +++++++++-------- lib/reportportal.rb | 5 +++-- 6 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Gemfile b/Gemfile index 586f0ad..53178fc 100644 --- a/Gemfile +++ b/Gemfile @@ -3,8 +3,8 @@ source 'https://rubygems.org' gemspec gem 'cucumber', '~> 3' -gem 'rest-client' gem 'parallel_tests', '~> 2.15.0' +gem 'rest-client' gem 'rake' gem 'rspec' gem 'rubytree', git: 'https://github.com/razboev/RubyTree' diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index b041717..b364779 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -10,7 +10,6 @@ class Formatter # @api private def initialize(config) - setup_message_processing @io = ensure_io(config.out_stream) diff --git a/lib/report_portal/cucumber/json_slurper.rb b/lib/report_portal/cucumber/json_slurper.rb index 8182935..b2bcf85 100644 --- a/lib/report_portal/cucumber/json_slurper.rb +++ b/lib/report_portal/cucumber/json_slurper.rb @@ -61,14 +61,14 @@ def run element['steps'].each do |step| name = decorate("#{step['keyword']}#{step['name']}") if step['rows'] - name << step['rows'].reduce("\n") {|acc, row| acc << decorate("| #{row['cells'].join(' | ')} |") << "\n"} + name << step['rows'].reduce("\n") { |acc, row| acc << decorate("| #{row['cells'].join(' | ')} |") << "\n" } end if step['doc_string'] name << %(\n"""\n#{step['doc_string']['value']}\n""") end ReportPortal.send_log(:passed, name, get_time) - step['output'].each {|o| ReportPortal.send_log(:passed, o, get_time)} unless step['output'].nil? + step['output'].each { |o| ReportPortal.send_log(:passed, o, get_time) } unless step['output'].nil? error = step['result']['error_message'] ReportPortal.send_log(:failed, error, get_time) if error (step['embeddings'] || []).each do |embedding| @@ -90,9 +90,9 @@ def run end statuses += report_hooks(element, 'after') - status = if statuses.any? {|s| %w(failed undefined pending).include? s} + status = if statuses.any? { |s| %w(failed undefined pending).include? s } :failed - elsif statuses.all? {|s| s == 'passed'} + elsif statuses.all? { |s| s == 'passed' } :passed else :skipped @@ -111,7 +111,7 @@ def run private def tags(item) - item['tags'].nil? ? [] : item['tags'].map {|h| h['name']} + item['tags'].nil? ? [] : item['tags'].map { |h| h['name'] } end def decorate(str) @@ -121,7 +121,7 @@ def decorate(str) def expanded? bad_item = @json.find do |f| - so = f['elements'].find {|e| e['keyword'] == 'Scenario Outline'} + so = f['elements'].find { |e| e['keyword'] == 'Scenario Outline' } so ? so.key?('examples') : false end bad_item.nil? diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index c37aefb..80a6794 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -20,9 +20,10 @@ def initialize start_time = monotonic_time loop do break if File.exist?(lock_file) - if monotonic_time - start_time > wait_time_for_launch_start - raise "File with launch ID wasn't created after waiting #{wait_time_for_launch_start} seconds" + if monotonic_time - start_time > wait_time_for_launch_create + raise "File with launch ID wasn't created after waiting #{wait_time_for_launch_create} seconds" end + sleep 0.5 end ReportPortal.launch_id = read_lock_file(lock_file) @@ -31,7 +32,7 @@ def initialize end def add_process_description - description = ReportPortal.get_launch['description'].split(' ') + description = ReportPortal.remote_launch['description'].split(' ') description.push(self.description.split(' ')).flatten! ReportPortal.update_launch(description: description.uniq.join(' ')) end @@ -73,20 +74,19 @@ def set_parallel_tests_vars def get_parallel_test_process(process_list) process_list.each do |process| - return process if process.cmdline.match(%r(bin(?:\/|\\)parallel_(?:cucumber|test)(.+))) + return process if process.cmdline.match(%r{bin(?:\/|\\)parallel_(?:cucumber|test)(.+)}) end nil end def get_cucumber_test_process(process_list) process_list.each do |process| - return process if process.cmdline.match(%r(bin(?:\/|\\)(?:cucumber)(.+))) + return process if process.cmdline.match(%r{bin(?:\/|\\)(?:cucumber)(.+)}) end nil end - #time required for first tread to created remote project in RP and save id to file - def wait_time_for_launch_start + def wait_time_for_launch_create ENV['rp_parallel_launch_wait_time'] || 60 end diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 9841db1..7dae306 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -9,6 +9,7 @@ module ReportPortal module Cucumber + # @api private class Report @folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" @@ -35,12 +36,12 @@ def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) # 3. [ADDED] If launch_id is not present check if lock exist with launch_uuid if attach_to_launch? ReportPortal.launch_id = - if ReportPortal::Settings.instance.launch_id - ReportPortal::Settings.instance.launch_id - else - file_path = lock_file - File.file?(file_path) ? read_lock_file(file_path) : new_launch(desired_time, cmd_args, file_path) - end + if ReportPortal::Settings.instance.launch_id + ReportPortal::Settings.instance.launch_id + else + file_path = lock_file + File.file?(file_path) ? read_lock_file(file_path) : new_launch(desired_time, cmd_args, file_path) + end $stdout.puts "Attaching to launch #{ReportPortal.launch_id}" else new_launch(desired_time, cmd_args) @@ -55,7 +56,7 @@ def new_launch(desired_time = ReportPortal.now, cmd_args = ARGV, lock_file = nil def description(cmd_args = ARGV) description ||= ReportPortal::Settings.instance.description - description ||= cmd_args.map {|arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]")}.join(' ') + description ||= cmd_args.map { |arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]") }.join(' ') description end @@ -108,7 +109,7 @@ def test_step_started(event, desired_time = ReportPortal.now) if step_source.multiline_arg.doc_string? message << %(\n"""\n#{step_source.multiline_arg.content}\n""") elsif step_source.multiline_arg.data_table? - message << step_source.multiline_arg.raw.reduce("\n") {|acc, row| acc << "| #{row.join(' | ')} |\n"} + message << step_source.multiline_arg.raw.reduce("\n") { |acc, row| acc << "| #{row.join(' | ')} |\n" } end ReportPortal.send_log(:trace, message, time_to_send(desired_time)) end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index c2cd3b5..1ecc099 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -37,7 +37,7 @@ def start_launch(description, start_time = now) @launch_id = JSON.parse(response)['id'] end - def get_launch() + def remote_launch response = project_resource["launch/#{@launch_id}"].get JSON.parse(response) end @@ -61,8 +61,9 @@ def start_item(item_node) response = project_resource[url].post(data.to_json) rescue RestClient::Exception => e response_message = JSON.parse(e.response)['message'] - m = response_message.match(%r(Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+')) + m = response_message.match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) raise unless m + time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') data[:start_time] = (time.to_f * 1000).to_i + 1000 ReportPortal.last_used_time = data[:start_time] From 9056c74db5b46224003f67e64bad3d681506822e Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 20 Jun 2019 21:27:41 -0400 Subject: [PATCH 60/98] layout fixes --- Gemfile | 3 +-- lib/report_portal/cucumber/formatter.rb | 3 +-- lib/report_portal/cucumber/report.rb | 12 ++++++------ 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index 53178fc..fa756ab 100644 --- a/Gemfile +++ b/Gemfile @@ -4,11 +4,10 @@ gemspec gem 'cucumber', '~> 3' gem 'parallel_tests', '~> 2.15.0' -gem 'rest-client' gem 'rake' +gem 'rest-client' gem 'rspec' gem 'rubytree', git: 'https://github.com/razboev/RubyTree' gem 'sys-proctable', '~> 1.1.5' - gem 'log4r' gem 'logging' diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index b364779..01d2c02 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -9,7 +9,6 @@ class Formatter include ::Cucumber::Formatter::Io # @api private def initialize(config) - setup_message_processing @io = ensure_io(config.out_stream) @@ -19,7 +18,7 @@ def initialize(config) process_message(event_name, event) end end - config.on_event(:test_run_finished) {finish_message_processing} + config.on_event(:test_run_finished) { finish_message_processing } end def puts(message) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 7dae306..da6064a 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -36,12 +36,12 @@ def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) # 3. [ADDED] If launch_id is not present check if lock exist with launch_uuid if attach_to_launch? ReportPortal.launch_id = - if ReportPortal::Settings.instance.launch_id - ReportPortal::Settings.instance.launch_id - else - file_path = lock_file - File.file?(file_path) ? read_lock_file(file_path) : new_launch(desired_time, cmd_args, file_path) - end + if ReportPortal::Settings.instance.launch_id + ReportPortal::Settings.instance.launch_id + else + file_path = lock_file + File.file?(file_path) ? read_lock_file(file_path) : new_launch(desired_time, cmd_args, file_path) + end $stdout.puts "Attaching to launch #{ReportPortal.launch_id}" else new_launch(desired_time, cmd_args) From 52707cad2a90fb41b2307341295a8f2899e43905 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 20 Jun 2019 21:36:31 -0400 Subject: [PATCH 61/98] layout fixes --- .rubocop_todo.yml | 2 +- Gemfile | 2 +- lib/report_portal/cucumber/formatter.rb | 1 - lib/report_portal/cucumber/parallel_formatter.rb | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 9f5cdcb..878c2a6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -121,7 +121,7 @@ Metrics/BlockLength: # Offense count: 2 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 165 + Max: 250 # Offense count: 3 Metrics/CyclomaticComplexity: diff --git a/Gemfile b/Gemfile index fa756ab..4b5c99c 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,6 @@ gem 'rake' gem 'rest-client' gem 'rspec' gem 'rubytree', git: 'https://github.com/razboev/RubyTree' -gem 'sys-proctable', '~> 1.1.5' gem 'log4r' gem 'logging' +gem 'sys-proctable', '~> 1.1.5' diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index 01d2c02..e70e28b 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -33,7 +33,6 @@ def embed(*args) private - def report @report ||= ReportPortal::Cucumber::Report.new end diff --git a/lib/report_portal/cucumber/parallel_formatter.rb b/lib/report_portal/cucumber/parallel_formatter.rb index 5863ba9..eb05868 100644 --- a/lib/report_portal/cucumber/parallel_formatter.rb +++ b/lib/report_portal/cucumber/parallel_formatter.rb @@ -4,7 +4,6 @@ module ReportPortal module Cucumber class ParallelFormatter < Formatter - private def report From b3359a7818eef5a3bbb5b5cc37ab65a016883132 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 20 Jun 2019 21:41:57 -0400 Subject: [PATCH 62/98] layout fixes --- Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 4b5c99c..e61f802 100644 --- a/Gemfile +++ b/Gemfile @@ -3,11 +3,11 @@ source 'https://rubygems.org' gemspec gem 'cucumber', '~> 3' +gem 'log4r' +gem 'logging' gem 'parallel_tests', '~> 2.15.0' gem 'rake' gem 'rest-client' gem 'rspec' gem 'rubytree', git: 'https://github.com/razboev/RubyTree' -gem 'log4r' -gem 'logging' gem 'sys-proctable', '~> 1.1.5' From fe8d51d19d20983ca2b018119ddc499211ca3f0c Mon Sep 17 00:00:00 2001 From: vveliev Date: Mon, 24 Jun 2019 12:28:28 -0400 Subject: [PATCH 63/98] fixing report completion step --- lib/report_portal/cucumber/parallel_report.rb | 2 +- lib/report_portal/cucumber/report.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 80a6794..034b856 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -37,7 +37,7 @@ def add_process_description ReportPortal.update_launch(description: description.uniq.join(' ')) end - def done(desired_time = ReportPortal.now) + def test_run_finished(event, desired_time = ReportPortal.now) end_feature(desired_time) if @feature_node if ParallelTests.first_process? diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 66d1512..b8ddcbd 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -143,7 +143,7 @@ def test_step_finished(event, desired_time = ReportPortal.now) end end - def done(desired_time = ReportPortal.now) + def test_run_finished(event, desired_time = ReportPortal.now) end_feature(desired_time) if @feature_node unless attach_to_launch? From 7694f48e7d5bf9ce3a59426b1d4ada98737fe358 Mon Sep 17 00:00:00 2001 From: vveliev Date: Mon, 24 Jun 2019 15:00:11 -0400 Subject: [PATCH 64/98] fixes after merge --- lib/report_portal/cucumber/parallel_report.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 3f9748e..56217e2 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -12,6 +12,7 @@ def parallel? def initialize @root_node = Tree::TreeNode.new('') + @parent_item_node = @root_node ReportPortal.last_used_time = 0 set_parallel_tests_vars if ParallelTests.first_process? From 37fbb353d4499c03dc2b3f9b0ee86020116f9d0d Mon Sep 17 00:00:00 2001 From: vveliev Date: Tue, 25 Jun 2019 04:38:33 -0400 Subject: [PATCH 65/98] - fixing retry error - making time required field - making parallel_report formatter to support both cucumber and parallel_cucumber --- lib/report_portal/cucumber/parallel_report.rb | 38 +++++++++++-------- lib/report_portal/cucumber/report.rb | 33 ++++++++-------- lib/reportportal.rb | 1 + 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 56217e2..2e369e4 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -6,17 +6,17 @@ module ReportPortal module Cucumber class ParallelReport < Report - def parallel? - true - end + def initialize + ReportPortal.last_used_time = 0 @root_node = Tree::TreeNode.new('') @parent_item_node = @root_node - ReportPortal.last_used_time = 0 + set_parallel_tests_vars + if ParallelTests.first_process? - start_launch + start_launch(ReportPortal.now) else start_time = monotonic_time loop do @@ -38,20 +38,23 @@ def add_process_description ReportPortal.update_launch(description: description.uniq.join(' ')) end - def test_run_finished(_event, desired_time = ReportPortal.now) - end_feature(desired_time) unless @root_node.is_root? + def test_run_finished(_event, desired_time ) + end_feature(desired_time) unless @parent_item_node.is_root? - if ParallelTests.first_process? - ParallelTests.wait_for_other_processes_to_finish + if parallel + if ParallelTests.first_process? + ParallelTests.wait_for_other_processes_to_finish + close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature - File.delete(lock_file) + File.delete(lock_file) - unless attach_to_launch? - $stdout.puts "Finishing launch #{ReportPortal.launch_id}" - ReportPortal.close_child_items(nil) - time_to_send = time_to_send(desired_time) - ReportPortal.finish_launch(time_to_send) + if started_launch || !attach_to_launch? + time_to_send = time_to_send(desired_time) + ReportPortal.finish_launch(time_to_send) + end end + else + super(_event,desired_time) end end @@ -74,7 +77,10 @@ def set_parallel_tests_vars def get_parallel_test_process(process_list) process_list.each do |process| - return process if process.cmdline.match(%r{bin(?:\/|\\)parallel_(?:cucumber|test)(.+)}) + if process.cmdline.match(%r{bin(?:\/|\\)parallel_(?:cucumber|test)(.+)}) + @parallel = true + return process + end end nil end diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 7c894fc..3a73784 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -11,12 +11,9 @@ module ReportPortal module Cucumber # @api private class Report + attr_accessor :parallel, :started_launch @folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" - def parallel? - false - end - def attach_to_launch? ReportPortal::Settings.instance.formatter_modes.include?('attach_to_launch') end @@ -25,10 +22,10 @@ def initialize ReportPortal.last_used_time = 0 @root_node = Tree::TreeNode.new('') @parent_item_node = @root_node - start_launch + start_launch(ReportPortal.now) end - def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) + def start_launch(desired_time , cmd_args = ARGV) # Not sure what is the use case if launch id is missing. But it does not make much of practical usage # # Expected behavior that make sense: @@ -40,6 +37,7 @@ def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) if ReportPortal::Settings.instance.launch_id ReportPortal::Settings.instance.launch_id else + self.started_launch = true file_path = lock_file File.file?(file_path) ? read_lock_file(file_path) : new_launch(desired_time, cmd_args, file_path) end @@ -49,7 +47,7 @@ def start_launch(desired_time = ReportPortal.now, cmd_args = ARGV) end end - def new_launch(desired_time = ReportPortal.now, cmd_args = ARGV, lock_file = nil) + def new_launch(desired_time, cmd_args = ARGV, lock_file = nil) ReportPortal.start_launch(description(cmd_args), time_to_send(desired_time)) set_file_lock_with_launch_id(lock_file, ReportPortal.launch_id) if lock_file ReportPortal.launch_id @@ -71,7 +69,7 @@ def set_file_lock_with_launch_id(lock_file, launch_id) end end - def test_case_started(event, desired_time = ReportPortal.now) # TODO: time should be a required argument + def test_case_started(event, desired_time) # TODO: time should be a required argument test_case = event.test_case feature = test_case.feature if report_hierarchy? && !same_feature_as_previous_test_case?(feature) @@ -90,7 +88,7 @@ def test_case_started(event, desired_time = ReportPortal.now) # TODO: time shoul ReportPortal.current_scenario.id = ReportPortal.start_item(scenario_node) end - def test_case_finished(event, desired_time = ReportPortal.now) + def test_case_finished(event, desired_time) result = event.result status = result.to_sym issue = nil @@ -102,7 +100,7 @@ def test_case_finished(event, desired_time = ReportPortal.now) ReportPortal.current_scenario = nil end - def test_step_started(event, desired_time = ReportPortal.now) + def test_step_started(event, desired_time) test_step = event.test_step if step?(test_step) # `after_test_step` is also invoked for hooks step_source = test_step.source.last @@ -116,7 +114,7 @@ def test_step_started(event, desired_time = ReportPortal.now) end end - def test_step_finished(event, desired_time = ReportPortal.now) + def test_step_finished(event, desired_time ) test_step = event.test_step result = event.result status = result.to_sym @@ -144,21 +142,20 @@ def test_step_finished(event, desired_time = ReportPortal.now) end end - def test_run_finished(_event, desired_time = ReportPortal.now) + def test_run_finished(_event, desired_time ) end_feature(desired_time) unless @parent_item_node.is_root? - - unless attach_to_launch? - close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature + close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature + if started_launch || !attach_to_launch? time_to_send = time_to_send(desired_time) ReportPortal.finish_launch(time_to_send) end end - def puts(message, desired_time = ReportPortal.now) + def puts(message, desired_time) ReportPortal.send_log(:info, message, time_to_send(desired_time)) end - def embed(src, mime_type, label, desired_time = ReportPortal.now) + def embed(src, mime_type, label, desired_time) ReportPortal.send_file(:info, src, label, time_to_send(desired_time), mime_type) end @@ -218,7 +215,7 @@ def start_feature_with_parentage(feature, desired_time) type = :TEST end # TODO: multithreading # Parallel formatter always executes scenarios inside the same feature in the same process - if parallel? && + if parallel && index < path_components.size - 1 && # is folder? (id_of_created_item = ReportPortal.item_id_of(name, parent_node)) # get id for folder from report portal # get child id from other process diff --git a/lib/reportportal.rb b/lib/reportportal.rb index c49d69b..8ade0fe 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -167,6 +167,7 @@ def project_resource verify_ssl = Settings.instance.disable_ssl_verification options[:verify_ssl] = !verify_ssl unless verify_ssl.nil? RestClient::Resource.new(Settings.instance.project_url, options) do |response, request, _, &block| + raise ::RestClient::Exception.new response.body if response.code == 406 unless (200..207).include?(response.code) p "ReportPortal API returned #{response}" p "Offending request method/URL: #{request.args[:method].upcase} #{request.args[:url]}" From f33890955bcdebe7458889208b64d4d109c9831f Mon Sep 17 00:00:00 2001 From: vveliev Date: Tue, 25 Jun 2019 11:34:40 -0400 Subject: [PATCH 66/98] minor layout fixes --- lib/report_portal/cucumber/parallel_report.rb | 21 ++++++++++--------- lib/report_portal/cucumber/report.rb | 4 ++-- lib/reportportal.rb | 3 ++- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 2e369e4..0bab53b 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -7,7 +7,6 @@ module ReportPortal module Cucumber class ParallelReport < Report - def initialize ReportPortal.last_used_time = 0 @root_node = Tree::TreeNode.new('') @@ -38,28 +37,30 @@ def add_process_description ReportPortal.update_launch(description: description.uniq.join(' ')) end - def test_run_finished(_event, desired_time ) + def test_run_finished(_event, desired_time) end_feature(desired_time) unless @parent_item_node.is_root? - if parallel if ParallelTests.first_process? ParallelTests.wait_for_other_processes_to_finish close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature - File.delete(lock_file) - - if started_launch || !attach_to_launch? - time_to_send = time_to_send(desired_time) - ReportPortal.finish_launch(time_to_send) - end + complete_launch(desired_time) end else - super(_event,desired_time) + close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature + complete_launch(desired_time) end end private + def complete_launch(desired_time) + if started_launch || !attach_to_launch? + time_to_send = time_to_send(desired_time) + ReportPortal.finish_launch(time_to_send) + end + end + def lock_file(file_path = nil) file_path ||= Dir.tmpdir + "/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" super(file_path) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 3a73784..9823f23 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -114,7 +114,7 @@ def test_step_started(event, desired_time) end end - def test_step_finished(event, desired_time ) + def test_step_finished(event, desired_time) test_step = event.test_step result = event.result status = result.to_sym @@ -142,7 +142,7 @@ def test_step_finished(event, desired_time ) end end - def test_run_finished(_event, desired_time ) + def test_run_finished(_event, desired_time) end_feature(desired_time) unless @parent_item_node.is_root? close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature if started_launch || !attach_to_launch? diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 8ade0fe..a9b7cc5 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -167,7 +167,8 @@ def project_resource verify_ssl = Settings.instance.disable_ssl_verification options[:verify_ssl] = !verify_ssl unless verify_ssl.nil? RestClient::Resource.new(Settings.instance.project_url, options) do |response, request, _, &block| - raise ::RestClient::Exception.new response.body if response.code == 406 + raise(::RestClient::Exception.new, response.body) if response.code == 406 + unless (200..207).include?(response.code) p "ReportPortal API returned #{response}" p "Offending request method/URL: #{request.args[:method].upcase} #{request.args[:url]}" From b929ce826ca173d803204f56bbe6be17c600c906 Mon Sep 17 00:00:00 2001 From: vveliev Date: Tue, 25 Jun 2019 11:49:07 -0400 Subject: [PATCH 67/98] minor layout fixes --- lib/report_portal/cucumber/parallel_report.rb | 1 - lib/report_portal/cucumber/report.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb index 0bab53b..e82b524 100644 --- a/lib/report_portal/cucumber/parallel_report.rb +++ b/lib/report_portal/cucumber/parallel_report.rb @@ -6,7 +6,6 @@ module ReportPortal module Cucumber class ParallelReport < Report - def initialize ReportPortal.last_used_time = 0 @root_node = Tree::TreeNode.new('') diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 9823f23..620c347 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -25,7 +25,7 @@ def initialize start_launch(ReportPortal.now) end - def start_launch(desired_time , cmd_args = ARGV) + def start_launch(desired_time, cmd_args = ARGV) # Not sure what is the use case if launch id is missing. But it does not make much of practical usage # # Expected behavior that make sense: From a6edfa7f17824bf54171bc7cc37325556ff590ff Mon Sep 17 00:00:00 2001 From: vveliev Date: Tue, 25 Jun 2019 14:33:01 -0400 Subject: [PATCH 68/98] using single formatter for both parallel_cucumber and cucumber --- .rubocop_todo.yml | 5 - README.md | 2 +- .../cucumber/parallel_formatter.rb | 14 --- lib/report_portal/cucumber/parallel_report.rb | 104 ------------------ lib/report_portal/cucumber/report.rb | 86 ++++++++++++++- lib/reportportal.rb | 7 +- 6 files changed, 87 insertions(+), 131 deletions(-) delete mode 100644 lib/report_portal/cucumber/parallel_formatter.rb delete mode 100644 lib/report_portal/cucumber/parallel_report.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 878c2a6..1688d1e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -31,8 +31,6 @@ Layout/ExtraSpacing: # Offense count: 1 # Cop supports --auto-correct. Layout/LeadingCommentSpace: - Exclude: - - 'lib/report_portal/cucumber/parallel_formatter.rb' # Offense count: 2 # Cop supports --auto-correct. @@ -181,8 +179,6 @@ Style/Documentation: - 'spec/**/*' - 'test/**/*' - 'lib/report_portal/cucumber/formatter.rb' - - 'lib/report_portal/cucumber/parallel_formatter.rb' - - 'lib/report_portal/cucumber/parallel_report.rb' - 'lib/report_portal/logging/logger.rb' - 'lib/report_portal/patches/rest_client.rb' - 'lib/report_portal/rspec/formatter.rb' @@ -237,7 +233,6 @@ Style/GlobalVars: # Configuration parameters: MinBodyLength. Style/GuardClause: Exclude: - - 'lib/report_portal/cucumber/parallel_report.rb' - 'lib/report_portal/cucumber/report.rb' - 'lib/report_portal/rspec/formatter.rb' - 'lib/reportportal.rb' diff --git a/README.md b/README.md index 418295f..b16853e 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Add `gem 'reportportal', git: 'https://github.com/reportportal/agent-ruby.git'` * With Cucumber and parallel_tests gem: -```parallel_cucumber -o ' -f ReportPortal::Cucumber::ParallelFormatter'``` +```parallel_cucumber -o ' -f ReportPortal::Cucumber::Formatter'``` * With RSpec: diff --git a/lib/report_portal/cucumber/parallel_formatter.rb b/lib/report_portal/cucumber/parallel_formatter.rb deleted file mode 100644 index eb05868..0000000 --- a/lib/report_portal/cucumber/parallel_formatter.rb +++ /dev/null @@ -1,14 +0,0 @@ -require File.dirname(__FILE__) + '/formatter' -require File.dirname(__FILE__) + '/parallel_report' - -module ReportPortal - module Cucumber - class ParallelFormatter < Formatter - private - - def report - @report ||= ReportPortal::Cucumber::ParallelReport.new - end - end - end -end diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb deleted file mode 100644 index e82b524..0000000 --- a/lib/report_portal/cucumber/parallel_report.rb +++ /dev/null @@ -1,104 +0,0 @@ -require 'parallel_tests' -require 'sys/proctable' -require 'fileutils' -require_relative 'report' - -module ReportPortal - module Cucumber - class ParallelReport < Report - def initialize - ReportPortal.last_used_time = 0 - @root_node = Tree::TreeNode.new('') - @parent_item_node = @root_node - - set_parallel_tests_vars - - if ParallelTests.first_process? - start_launch(ReportPortal.now) - else - start_time = monotonic_time - loop do - break if File.exist?(lock_file) - if monotonic_time - start_time > wait_time_for_launch_create - raise "File with launch ID wasn't created after waiting #{wait_time_for_launch_create} seconds" - end - - sleep 0.5 - end - ReportPortal.launch_id = read_lock_file(lock_file) - add_process_description - end - end - - def add_process_description - description = ReportPortal.remote_launch['description'].split(' ') - description.push(self.description.split(' ')).flatten! - ReportPortal.update_launch(description: description.uniq.join(' ')) - end - - def test_run_finished(_event, desired_time) - end_feature(desired_time) unless @parent_item_node.is_root? - if parallel - if ParallelTests.first_process? - ParallelTests.wait_for_other_processes_to_finish - close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature - File.delete(lock_file) - complete_launch(desired_time) - end - else - close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature - complete_launch(desired_time) - end - end - - private - - def complete_launch(desired_time) - if started_launch || !attach_to_launch? - time_to_send = time_to_send(desired_time) - ReportPortal.finish_launch(time_to_send) - end - end - - def lock_file(file_path = nil) - file_path ||= Dir.tmpdir + "/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" - super(file_path) - end - - def set_parallel_tests_vars - process_list = Sys::ProcTable.ps - runner_process ||= get_parallel_test_process(process_list) - runner_process ||= get_cucumber_test_process(process_list) - raise 'Could not find parallel_cucumber/parallel_test in ancestors of current process' if runner_process.nil? - - @pid_of_parallel_tests = runner_process.pid - @cmd_args_of_parallel_tests = runner_process.cmdline.split(' ', 2).pop - end - - def get_parallel_test_process(process_list) - process_list.each do |process| - if process.cmdline.match(%r{bin(?:\/|\\)parallel_(?:cucumber|test)(.+)}) - @parallel = true - return process - end - end - nil - end - - def get_cucumber_test_process(process_list) - process_list.each do |process| - return process if process.cmdline.match(%r{bin(?:\/|\\)(?:cucumber)(.+)}) - end - nil - end - - def wait_time_for_launch_create - ENV['rp_parallel_launch_wait_time'] || 60 - end - - def monotonic_time - Process.clock_gettime(Process::CLOCK_MONOTONIC) - end - end - end -end diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 620c347..27c5aa1 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -3,6 +3,9 @@ require 'tree' require 'securerandom' require 'tempfile' +require 'parallel_tests' +require 'sys/proctable' +require 'fileutils' require_relative '../../reportportal' require_relative '../logging/logger' @@ -22,9 +25,27 @@ def initialize ReportPortal.last_used_time = 0 @root_node = Tree::TreeNode.new('') @parent_item_node = @root_node - start_launch(ReportPortal.now) + + set_parallel_tests_vars + + if ParallelTests.first_process? + start_launch(ReportPortal.now) + else + start_time = monotonic_time + loop do + break if File.exist?(lock_file) + if monotonic_time - start_time > wait_time_for_launch_create + raise "File with launch ID wasn't created after waiting #{wait_time_for_launch_create} seconds" + end + + sleep 0.5 + end + ReportPortal.launch_id = read_lock_file(lock_file) + add_process_description + end end + def start_launch(desired_time, cmd_args = ARGV) # Not sure what is the use case if launch id is missing. But it does not make much of practical usage # @@ -144,13 +165,25 @@ def test_step_finished(event, desired_time) def test_run_finished(_event, desired_time) end_feature(desired_time) unless @parent_item_node.is_root? - close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature - if started_launch || !attach_to_launch? - time_to_send = time_to_send(desired_time) - ReportPortal.finish_launch(time_to_send) + if parallel + if ParallelTests.first_process? + ParallelTests.wait_for_other_processes_to_finish + close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature + File.delete(lock_file) + complete_launch(desired_time) + end + else + close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature + complete_launch(desired_time) end end + def add_process_description + description = ReportPortal.remote_launch['description'].split(' ') + description.push(self.description.split(' ')).flatten! + ReportPortal.update_launch(description: description.uniq.join(' ')) + end + def puts(message, desired_time) ReportPortal.send_log(:info, message, time_to_send(desired_time)) end @@ -161,13 +194,56 @@ def embed(src, mime_type, label, desired_time) private + def complete_launch(desired_time) + if started_launch || !attach_to_launch? + time_to_send = time_to_send(desired_time) + ReportPortal.finish_launch(time_to_send) + end + end + def lock_file(file_path = nil) file_path ||= ReportPortal::Settings.instance.file_with_launch_id + file_path ||= Dir.tmpdir + "/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" file_path ||= Dir.tmpdir + "/report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid file_path ||= Dir.tmpdir + '/rp_launch_id.tmp' file_path end + def set_parallel_tests_vars + process_list = Sys::ProcTable.ps + runner_process ||= get_parallel_test_process(process_list) + runner_process ||= get_cucumber_test_process(process_list) + raise 'Could not find parallel_cucumber/parallel_test in ancestors of current process' if runner_process.nil? + + @pid_of_parallel_tests = runner_process.pid + @cmd_args_of_parallel_tests = runner_process.cmdline.split(' ', 2).pop + end + + def get_parallel_test_process(process_list) + process_list.each do |process| + if process.cmdline.match(%r{bin(?:\/|\\)parallel_(?:cucumber|test)(.+)}) + @parallel = true + return process + end + end + nil + end + + def get_cucumber_test_process(process_list) + process_list.each do |process| + return process if process.cmdline.match(%r{bin(?:\/|\\)(?:cucumber)(.+)}) + end + nil + end + + def wait_time_for_launch_create + ENV['rp_parallel_launch_wait_time'] || 60 + end + + def monotonic_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + def read_lock_file(file_path) content = nil File.open(file_path, 'r') do |f| diff --git a/lib/reportportal.rb b/lib/reportportal.rb index a9b7cc5..a1ddb79 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -167,8 +167,11 @@ def project_resource verify_ssl = Settings.instance.disable_ssl_verification options[:verify_ssl] = !verify_ssl unless verify_ssl.nil? RestClient::Resource.new(Settings.instance.project_url, options) do |response, request, _, &block| - raise(::RestClient::Exception.new, response.body) if response.code == 406 - + if response.code == 406 + p response.body + raise RestClient::Exception, response.body + end + unless (200..207).include?(response.code) p "ReportPortal API returned #{response}" p "Offending request method/URL: #{request.args[:method].upcase} #{request.args[:url]}" From aa0342c4f38baea9c62fcf86f2b8c47b8e1c808d Mon Sep 17 00:00:00 2001 From: vveliev Date: Tue, 25 Jun 2019 14:40:02 -0400 Subject: [PATCH 69/98] using single formatter for both parallel_cucumber and cucumber --- README.md | 6 ------ lib/report_portal/cucumber/report.rb | 2 +- lib/report_portal/settings.rb | 1 - lib/reportportal.rb | 2 -- 4 files changed, 1 insertion(+), 10 deletions(-) diff --git a/README.md b/README.md index b16853e..96f7f74 100644 --- a/README.md +++ b/README.md @@ -114,12 +114,6 @@ Experimental support for three common logging frameworks was added: To use Logger, set use_standard_logger parameter to true (see Configuration chapter). For the other two corresponding appenders/outputters are available under reportportal/logging. -## Parallel formatter - -ReportPortal::Cucumber::ParallelFormatter can be used for tests started via parallel_tests gem. - -Note: Launch id is shared between independent processes (as is the case with parallel_tests gem) via a file in `Dir.tmpdir`. - ## Links - [ReportPortal](https://github.com/reportportal/) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 27c5aa1..a10589d 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -213,7 +213,7 @@ def set_parallel_tests_vars process_list = Sys::ProcTable.ps runner_process ||= get_parallel_test_process(process_list) runner_process ||= get_cucumber_test_process(process_list) - raise 'Could not find parallel_cucumber/parallel_test in ancestors of current process' if runner_process.nil? + raise 'Failed to find any cucumber related test process' if runner_process.nil? @pid_of_parallel_tests = runner_process.pid @cmd_args_of_parallel_tests = runner_process.cmdline.split(' ', 2).pop diff --git a/lib/report_portal/settings.rb b/lib/report_portal/settings.rb index 6bc3ce9..d69b20d 100644 --- a/lib/report_portal/settings.rb +++ b/lib/report_portal/settings.rb @@ -24,7 +24,6 @@ def initialize 'tags' => false, 'is_debug' => false, 'disable_ssl_verification' => false, - # for parallel execution only 'use_standard_logger' => false, 'launch_id' => false, 'file_with_launch_id' => false, diff --git a/lib/reportportal.rb b/lib/reportportal.rb index a1ddb79..d3cb2ff 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -112,7 +112,6 @@ def send_file(status, path, label = nil, time = now, mime_type = 'image/png') end end - # needed for parallel formatter def item_id_of(name, parent_node) if parent_node.is_root? # folder without parent folder url = "item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{URI.escape(name)}&filter.size.path=0" @@ -127,7 +126,6 @@ def item_id_of(name, parent_node) end end - # needed for parallel formatter def close_child_items(parent_id) if parent_id.nil? url = "item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" From 8549179482166bbd8f6772d294fc0d58e3bf70bf Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 27 Jun 2019 12:59:43 -0400 Subject: [PATCH 70/98] adding logger for report portal --- lib/report_portal/cucumber/formatter.rb | 8 ++--- lib/report_portal/cucumber/report.rb | 25 +++++++++----- lib/reportportal.rb | 43 ++++++++++++++----------- 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index e70e28b..7ce6160 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -9,10 +9,10 @@ class Formatter include ::Cucumber::Formatter::Io # @api private def initialize(config) + @logger ||= Logger.new(config.out_stream) + @logger.level = :info setup_message_processing - @io = ensure_io(config.out_stream) - [:test_case_started, :test_case_finished, :test_step_started, :test_step_finished, :test_run_finished].each do |event_name| config.on_event event_name do |event| process_message(event_name, event) @@ -23,8 +23,6 @@ def initialize(config) def puts(message) process_message(:puts, message) - @io.puts(message) - @io.flush end def embed(*args) @@ -34,7 +32,7 @@ def embed(*args) private def report - @report ||= ReportPortal::Cucumber::Report.new + @report ||= ReportPortal::Cucumber::Report.new(@logger) end def setup_message_processing diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index a10589d..54788ed 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -21,8 +21,10 @@ def attach_to_launch? ReportPortal::Settings.instance.formatter_modes.include?('attach_to_launch') end - def initialize + def initialize(logger) + @logger = logger ReportPortal.last_used_time = 0 + ReportPortal.logger = logger @root_node = Tree::TreeNode.new('') @parent_item_node = @root_node @@ -37,18 +39,18 @@ def initialize if monotonic_time - start_time > wait_time_for_launch_create raise "File with launch ID wasn't created after waiting #{wait_time_for_launch_create} seconds" end + @logger.debug "File with launch ID wasn't created after waiting #{monotonic_time - start_time} seconds" sleep 0.5 end ReportPortal.launch_id = read_lock_file(lock_file) + @logger.debug "Attaching to launch using lock_file [#{lock_file}], launch_id: [#{ReportPortal.launch_id}] " add_process_description end end def start_launch(desired_time, cmd_args = ARGV) - # Not sure what is the use case if launch id is missing. But it does not make much of practical usage - # # Expected behavior that make sense: # 1. If launch_id present attach to existing (simple use case) # 2. If launch_id not present check if exist rp_launch_id.tmp @@ -62,13 +64,14 @@ def start_launch(desired_time, cmd_args = ARGV) file_path = lock_file File.file?(file_path) ? read_lock_file(file_path) : new_launch(desired_time, cmd_args, file_path) end - $stdout.puts "Attaching to launch #{ReportPortal.launch_id}" + @logger.info "Attaching to launch #{ReportPortal.launch_id}" else new_launch(desired_time, cmd_args) end end def new_launch(desired_time, cmd_args = ARGV, lock_file = nil) + @logger.info("Creating new launch at: [#{desired_time}], with cmd: [#{cmd_args}] and file lock: [#{lock_file}]") ReportPortal.start_launch(description(cmd_args), time_to_send(desired_time)) set_file_lock_with_launch_id(lock_file, ReportPortal.launch_id) if lock_file ReportPortal.launch_id @@ -90,7 +93,9 @@ def set_file_lock_with_launch_id(lock_file, launch_id) end end - def test_case_started(event, desired_time) # TODO: time should be a required argument + # scenario starts in separate treads + def test_case_started(event, desired_time) + @logger.debug "test_case_started: [#{event}], " test_case = event.test_case feature = test_case.feature if report_hierarchy? && !same_feature_as_previous_test_case?(feature) @@ -110,6 +115,7 @@ def test_case_started(event, desired_time) # TODO: time should be a required arg end def test_case_finished(event, desired_time) + @logger.debug "test_case_finished: [#{event}], " result = event.result status = result.to_sym issue = nil @@ -122,6 +128,7 @@ def test_case_finished(event, desired_time) end def test_step_started(event, desired_time) + @logger.debug "test_step_started: [#{event}], " test_step = event.test_step if step?(test_step) # `after_test_step` is also invoked for hooks step_source = test_step.source.last @@ -165,16 +172,17 @@ def test_step_finished(event, desired_time) def test_run_finished(_event, desired_time) end_feature(desired_time) unless @parent_item_node.is_root? + @logger.info("Test run finish,") if parallel if ParallelTests.first_process? ParallelTests.wait_for_other_processes_to_finish close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature File.delete(lock_file) + @logger.info("close launch , delete lock") complete_launch(desired_time) end else close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature - complete_launch(desired_time) end end @@ -269,10 +277,11 @@ def time_to_send(desired_time) end def same_feature_as_previous_test_case?(feature) - @feature_node && @feature_node.name == feature.location.file.split(File::SEPARATOR).last + @parent_item_node.name == feature.location.file.split(File::SEPARATOR).last end def start_feature_with_parentage(feature, desired_time) + @logger.debug("start_feature_with_parentage: [#{feature}], [#{desired_time}]") parent_node = @root_node child_node = nil path_components = feature.location.file.split(File::SEPARATOR) @@ -286,7 +295,7 @@ def start_feature_with_parentage(feature, desired_time) type = :SUITE else name = "#{feature.keyword}: #{feature.name}" - description = feature.file + description = feature.file # TODO: consider adding feature description and comments tags = feature.tags.map(&:name) type = :TEST end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index d3cb2ff..b890bca 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -12,7 +12,7 @@ module ReportPortal LOG_LEVELS = { error: 'ERROR', warn: 'WARN', info: 'INFO', debug: 'DEBUG', trace: 'TRACE', fatal: 'FATAL', unknown: 'UNKNOWN' } class << self - attr_accessor :launch_id, :current_scenario, :last_used_time + attr_accessor :launch_id, :current_scenario, :last_used_time, :logger def now (Time.now.to_f * 1000).to_i @@ -47,6 +47,7 @@ def update_launch(data) end def finish_launch(end_time = now) + self.logger.debug "finish_launch: [#{end_time}]" data = { end_time: end_time } project_resource["launch/#{@launch_id}/finish"].put(data.to_json) end @@ -55,19 +56,29 @@ def start_item(item_node) item = item_node.content data = { start_time: item.start_time, name: item.name[0, 255], type: item.type.to_s, launch_id: @launch_id, description: item.description } data[:tags] = item.tags unless item.tags.empty? + retry_count = 0 begin + retry_count += 1 url = 'item' url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? response = project_resource[url].post(data.to_json) rescue RestClient::Exception => e - response_message = JSON.parse(e.response)['message'] - m = response_message.match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) - raise unless m - - time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') - data[:start_time] = (time.to_f * 1000).to_i + 1000 - ReportPortal.last_used_time = data[:start_time] - retry + self.logger.warn("Exception[#{e}],response:[#{e.response}], retry_count: [#{retry_count}]") + + response = JSON.parse(e.response) + m = response['message'].match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) + if m + time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') + data[:start_time] = (time.to_f * 1000).to_i + 1000 + ReportPortal.last_used_time = data[:start_time] + else + self.logger.error("RestClient::Exception -> response: [#{response}]") + self.logger.error("TRACE[#{e.backtrace}]") + raise + end + + + retry if retry_count < 10 end JSON.parse(response)['id'] end @@ -81,6 +92,7 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) elsif status == :skipped data[:issue] = { issue_type: 'NOT_ISSUE' } end + self.logger.debug "finish_item:id[#{item}], data: #{data} " project_resource["item/#{item.id}"].put(data.to_json) item.closed = true end @@ -89,6 +101,7 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) # TODO: implement force finish def send_log(status, message, time) + @logger.debug "send_log: [#{status}],[#{message}], #{@current_scenario} " unless @current_scenario.nil? || @current_scenario.closed # it can be nil if scenario outline in expand mode is executed data = { item_id: @current_scenario.id, time: time, level: status_to_level(status), message: message.to_s } project_resource['log'].post(data.to_json) @@ -127,6 +140,7 @@ def item_id_of(name, parent_node) end def close_child_items(parent_id) + self.logger.debug "closing child items: #{parent_id} " if parent_id.nil? url = "item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" else @@ -165,16 +179,7 @@ def project_resource verify_ssl = Settings.instance.disable_ssl_verification options[:verify_ssl] = !verify_ssl unless verify_ssl.nil? RestClient::Resource.new(Settings.instance.project_url, options) do |response, request, _, &block| - if response.code == 406 - p response.body - raise RestClient::Exception, response.body - end - - unless (200..207).include?(response.code) - p "ReportPortal API returned #{response}" - p "Offending request method/URL: #{request.args[:method].upcase} #{request.args[:url]}" - p "Offending request payload: #{request.args[:payload]}}" - end + self.logger.debug "Request: #{request.args[:method].upcase} #{request.args[:url]}, data: #{request.args[:payload]}" response.return!(&block) end end From 0da36ccc9cc751ecadecbb11bdabd97bc30f01d9 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 27 Jun 2019 13:12:08 -0400 Subject: [PATCH 71/98] updating readme and adding settings for logging level --- README.md | 3 ++- lib/report_portal/cucumber/formatter.rb | 2 +- lib/report_portal/settings.rb | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 96f7f74..e4eca83 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Add `gem 'reportportal', git: 'https://github.com/reportportal/agent-ruby.git'` * With Cucumber: -```cucumber -f ReportPortal::Cucumber::Formatter``` +```cucumber -f ReportPortal::Cucumber::Formatter -o ''``` * With Cucumber and parallel_tests gem: @@ -54,6 +54,7 @@ Supported settings: - launch_id - id of previously created launch (to be used if formatter_modes contains attach_to_launch) - file_with_launch_id - path to file with id of launch (to be used if formatter_modes contains attach_to_launch) - disable_ssl_verification - set to true to disable SSL verification on connect to ReportPortal (potential security hole!). Set `disable_ssl_verification` to `true` if you see the following error: + - log_level - this is log level for report_portal agent (useful for troubleshooting issued when run in parallel mode) ``` Request to https://rp.epam.com/reportportal-ws/api/v1/pass-team/launch//finish produced an exception: RestClient::SSLCertificateNotVerified: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed ``` diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index 7ce6160..1fcf12f 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -10,7 +10,7 @@ class Formatter # @api private def initialize(config) @logger ||= Logger.new(config.out_stream) - @logger.level = :info + @logger.level = ReportPortal::Settings.instance.log_level || :warn setup_message_processing [:test_case_started, :test_case_finished, :test_step_started, :test_step_finished, :test_run_finished].each do |event_name| diff --git a/lib/report_portal/settings.rb b/lib/report_portal/settings.rb index d69b20d..56edb44 100644 --- a/lib/report_portal/settings.rb +++ b/lib/report_portal/settings.rb @@ -27,7 +27,8 @@ def initialize 'use_standard_logger' => false, 'launch_id' => false, 'file_with_launch_id' => false, - 'launch_uuid' => false + 'launch_uuid' => false, + 'log_level' => false } keys.each do |key, is_required| From 321f256a76323f7f3353454c6a59a2fafd253a63 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 27 Jun 2019 15:22:24 -0400 Subject: [PATCH 72/98] updating how response processing is handling --- .rubocop_todo.yml | 1 + lib/reportportal.rb | 74 +++++++++++++++++++++++---------------------- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 1688d1e..61057f4 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -31,6 +31,7 @@ Layout/ExtraSpacing: # Offense count: 1 # Cop supports --auto-correct. Layout/LeadingCommentSpace: + Enabled: true # Offense count: 2 # Cop supports --auto-correct. diff --git a/lib/reportportal.rb b/lib/reportportal.rb index b890bca..e18e02c 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -33,54 +33,30 @@ def status_to_level(status) def start_launch(description, start_time = now) data = { name: Settings.instance.launch, start_time: start_time, tags: Settings.instance.tags, description: description, mode: Settings.instance.launch_mode } - response = project_resource['launch'].post(data.to_json) - @launch_id = JSON.parse(response)['id'] + @launch_id = process_request('launch',:post,data.to_json)['id'] end def remote_launch - response = project_resource["launch/#{@launch_id}"].get - JSON.parse(response) + process_request("launch/#{@launch_id}",:get) end def update_launch(data) - project_resource["launch/#{@launch_id}/update"].put(data.to_json) + process_request("launch/#{@launch_id}/update",:put, data.to_json) end def finish_launch(end_time = now) self.logger.debug "finish_launch: [#{end_time}]" data = { end_time: end_time } - project_resource["launch/#{@launch_id}/finish"].put(data.to_json) + process_request("launch/#{@launch_id}/finish",:put,data.to_json) end def start_item(item_node) item = item_node.content data = { start_time: item.start_time, name: item.name[0, 255], type: item.type.to_s, launch_id: @launch_id, description: item.description } data[:tags] = item.tags unless item.tags.empty? - retry_count = 0 - begin - retry_count += 1 - url = 'item' - url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? - response = project_resource[url].post(data.to_json) - rescue RestClient::Exception => e - self.logger.warn("Exception[#{e}],response:[#{e.response}], retry_count: [#{retry_count}]") - - response = JSON.parse(e.response) - m = response['message'].match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) - if m - time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') - data[:start_time] = (time.to_f * 1000).to_i + 1000 - ReportPortal.last_used_time = data[:start_time] - else - self.logger.error("RestClient::Exception -> response: [#{response}]") - self.logger.error("TRACE[#{e.backtrace}]") - raise - end - - - retry if retry_count < 10 - end - JSON.parse(response)['id'] + url = 'item' + url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? + process_request(url,:post,data.to_json)['id'] end def finish_item(item, status = nil, end_time = nil, force_issue = nil) @@ -93,7 +69,7 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) data[:issue] = { issue_type: 'NOT_ISSUE' } end self.logger.debug "finish_item:id[#{item}], data: #{data} " - project_resource["item/#{item.id}"].put(data.to_json) + process_request("item/#{item.id}",:put,data.to_json) item.closed = true end end @@ -104,7 +80,7 @@ def send_log(status, message, time) @logger.debug "send_log: [#{status}],[#{message}], #{@current_scenario} " unless @current_scenario.nil? || @current_scenario.closed # it can be nil if scenario outline in expand mode is executed data = { item_id: @current_scenario.id, time: time, level: status_to_level(status), message: message.to_s } - project_resource['log'].post(data.to_json) + process_request("log",:post,data.to_json) end end @@ -121,7 +97,7 @@ def send_file(status, path, label = nil, time = now, mime_type = 'image/png') label ||= File.basename(file) json = { level: status_to_level(status), message: label, item_id: @current_scenario.id, time: time, file: { name: File.basename(file) } } data = { :json_request_part => [json].to_json, label => file, :multipart => true, :content_type => 'application/json' } - project_resource['log'].post(data, content_type: 'multipart/form-data') + process_request("log",:post,data, content_type: 'multipart/form-data') end end @@ -131,7 +107,7 @@ def item_id_of(name, parent_node) else url = "item?filter.eq.launch=#{@launch_id}&filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" end - data = JSON.parse(project_resource[url].get) + data = process_request(url,:get) if data.key? 'content' data['content'].empty? ? nil : data['content'][0]['id'] else @@ -148,7 +124,7 @@ def close_child_items(parent_id) end ids = [] loop do - response = JSON.parse(project_resource[url].get) + response = process_request(url,:get) if response.key?('links') link = response['links'].find { |i| i['rel'] == 'next' } url = link.nil? ? nil : link['href'] @@ -170,6 +146,32 @@ def close_child_items(parent_id) private + def process_request(path, method, *options) + tries = 5 + begin + response = project_resource[path].send(method, *options) + rescue RestClient::Exception => e + self.logger.warn("Exception[#{e}],response:[#{e.response}], retry_count: [#{tries}]") + response = JSON.parse(e.response) + m = response['message'].match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) + if m + time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') + data[:start_time] = (time.to_f * 1000).to_i + 1000 + ReportPortal.last_used_time = data[:start_time] + else + self.logger.error("RestClient::Exception -> response: [#{response}]") + self.logger.error("TRACE[#{e.backtrace}]") + raise + end + + retry unless (tries -= 1).zero? + rescue => e + debugger + puts 'say hello' + end + JSON.parse(response) + end + def project_resource options = {} options[:headers] = { From 9ba5fceb516c1057d35f7ca1042c7724ad46dbbd Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 27 Jun 2019 15:24:05 -0400 Subject: [PATCH 73/98] removing debugger --- lib/reportportal.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index e18e02c..d026c2d 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -163,11 +163,8 @@ def process_request(path, method, *options) self.logger.error("TRACE[#{e.backtrace}]") raise end - + retry unless (tries -= 1).zero? - rescue => e - debugger - puts 'say hello' end JSON.parse(response) end From 92671e7447e54d00423eb1ebb7080652b712e203 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 27 Jun 2019 21:47:27 -0400 Subject: [PATCH 74/98] fixing issue with child process not closing the folders items --- lib/report_portal/cucumber/report.rb | 4 +--- lib/reportportal.rb | 11 +++++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 54788ed..b5f53c3 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -173,16 +173,14 @@ def test_step_finished(event, desired_time) def test_run_finished(_event, desired_time) end_feature(desired_time) unless @parent_item_node.is_root? @logger.info("Test run finish,") + close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature if parallel if ParallelTests.first_process? ParallelTests.wait_for_other_processes_to_finish - close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature File.delete(lock_file) @logger.info("close launch , delete lock") complete_launch(desired_time) end - else - close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature end end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index d026c2d..86fd619 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -155,15 +155,18 @@ def process_request(path, method, *options) response = JSON.parse(e.response) m = response['message'].match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) if m - time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') - data[:start_time] = (time.to_f * 1000).to_i + 1000 - ReportPortal.last_used_time = data[:start_time] + parent_time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') + data = JSON.parse(options[0]) + self.logger.warn("RP error : 40025, time of a child: [#{data['start_time']}], paren time: [#{(parent_time.to_f * 1000).to_i}]") + data['start_time'] = (parent_time.to_f * 1000).to_i + 1000 + options[0] = data.to_json + ReportPortal.last_used_time = time else self.logger.error("RestClient::Exception -> response: [#{response}]") self.logger.error("TRACE[#{e.backtrace}]") raise end - + retry unless (tries -= 1).zero? end JSON.parse(response) From 1c3b42f3bf5cee995fbd318097fe9d6194b9f795 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 27 Jun 2019 22:49:27 -0400 Subject: [PATCH 75/98] fixing value for time, removing cucumber IO as it's no longer needed --- lib/report_portal/cucumber/formatter.rb | 2 -- lib/reportportal.rb | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index 1fcf12f..d108b4e 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -1,12 +1,10 @@ require 'thread' -require 'cucumber/formatter/io' require_relative 'report' module ReportPortal module Cucumber class Formatter - include ::Cucumber::Formatter::Io # @api private def initialize(config) @logger ||= Logger.new(config.out_stream) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 86fd619..f9b7c14 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -160,7 +160,7 @@ def process_request(path, method, *options) self.logger.warn("RP error : 40025, time of a child: [#{data['start_time']}], paren time: [#{(parent_time.to_f * 1000).to_i}]") data['start_time'] = (parent_time.to_f * 1000).to_i + 1000 options[0] = data.to_json - ReportPortal.last_used_time = time + ReportPortal.last_used_time = data['start_time'] else self.logger.error("RestClient::Exception -> response: [#{response}]") self.logger.error("TRACE[#{e.backtrace}]") From 8cf2206f00fc3b66653f4f5d66762c83991b4674 Mon Sep 17 00:00:00 2001 From: vveliev Date: Fri, 28 Jun 2019 07:57:10 -0400 Subject: [PATCH 76/98] - adding logging - when run in parallel, child process should not close items in process(folder) --- lib/report_portal/cucumber/report.rb | 17 ++++++++++++++++- lib/reportportal.rb | 14 +++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index b5f53c3..7bb6da4 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -31,8 +31,10 @@ def initialize(logger) set_parallel_tests_vars if ParallelTests.first_process? + @logger.debug("First process: #{@pid_of_parallel_tests}") start_launch(ReportPortal.now) else + @logger.debug("Child process: #{@pid_of_parallel_tests}") start_time = monotonic_time loop do break if File.exist?(lock_file) @@ -175,6 +177,7 @@ def test_run_finished(_event, desired_time) @logger.info("Test run finish,") close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature if parallel + @logger.debug("Parallel process: #{@pid_of_parallel_tests}") if ParallelTests.first_process? ParallelTests.wait_for_other_processes_to_finish File.delete(lock_file) @@ -326,9 +329,21 @@ def end_feature(desired_time) end def close_all_children_of(root_node) + @logger.debug("close_all_children_of: [#{root_node}]") root_node.postordered_each do |node| + @logger.debug("close_all_children_of:postordered_each [#{node.content}]") if !node.is_root? && !node.content.closed - ReportPortal.finish_item(node.content) + begin + item = ReportPortal.remote_item(node.content[:id]) + @logger.debug("item details: [#{item}]") + if started_launch + ReportPortal.close_child_items(node.content[:id]) if item['status'].eql?('IN_PROGRESS') + ReportPortal.finish_item(node.content) + + else + ReportPortal.finish_item(node.content) unless item['status'].eql?('IN_PROGRESS') + end + end end end end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index f9b7c14..fcc3657 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -101,14 +101,22 @@ def send_file(status, path, label = nil, time = now, mime_type = 'image/png') end end - def item_id_of(name, parent_node) + def get_item(name, parent_node) if parent_node.is_root? # folder without parent folder url = "item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{URI.escape(name)}&filter.size.path=0" else url = "item?filter.eq.launch=#{@launch_id}&filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" end - data = process_request(url,:get) - if data.key? 'content' + process_request(url,:get) + end + + def remote_item(item_id) + process_request("item/#{item_id}",:get) + end + + def item_id_of(name, parent_node) + data = get_item(name, parent_node) + if data.key? 'content' data['content'].empty? ? nil : data['content'][0]['id'] else nil # item isn't started yet From e559f3f497452b7363d2c2b9895cbecbbf0f40c8 Mon Sep 17 00:00:00 2001 From: vveliev Date: Fri, 28 Jun 2019 14:56:59 -0400 Subject: [PATCH 77/98] - adding logging - add exception handling when closing item , if already closed , ignore RP 40018 error code --- lib/report_portal/cucumber/report.rb | 31 +++++++++++++++++++--------- lib/reportportal.rb | 8 ++++++- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 7bb6da4..f1209cd 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -174,11 +174,11 @@ def test_step_finished(event, desired_time) def test_run_finished(_event, desired_time) end_feature(desired_time) unless @parent_item_node.is_root? - @logger.info("Test run finish,") + @logger.info("Test run finish: [#{@parent_item_node}]") close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature if parallel @logger.debug("Parallel process: #{@pid_of_parallel_tests}") - if ParallelTests.first_process? + if ParallelTests.first_process? && started_launch ParallelTests.wait_for_other_processes_to_finish File.delete(lock_file) @logger.info("close launch , delete lock") @@ -220,18 +220,18 @@ def lock_file(file_path = nil) def set_parallel_tests_vars process_list = Sys::ProcTable.ps + @logger.debug("set_test_variables: #{process_list}") runner_process ||= get_parallel_test_process(process_list) runner_process ||= get_cucumber_test_process(process_list) raise 'Failed to find any cucumber related test process' if runner_process.nil? - @pid_of_parallel_tests = runner_process.pid - @cmd_args_of_parallel_tests = runner_process.cmdline.split(' ', 2).pop end def get_parallel_test_process(process_list) process_list.each do |process| if process.cmdline.match(%r{bin(?:\/|\\)parallel_(?:cucumber|test)(.+)}) @parallel = true + @logger.debug("get_parallel_test_process: #{process.cmdline}") return process end end @@ -240,7 +240,10 @@ def get_parallel_test_process(process_list) def get_cucumber_test_process(process_list) process_list.each do |process| - return process if process.cmdline.match(%r{bin(?:\/|\\)(?:cucumber)(.+)}) + if process.cmdline.match(%r{bin(?:\/|\\)(?:cucumber)(.+)}) + @logger.debug("get_cucumber_test_process: #{process.cmdline}") + return process + end end nil end @@ -329,19 +332,27 @@ def end_feature(desired_time) end def close_all_children_of(root_node) - @logger.debug("close_all_children_of: [#{root_node}]") + @logger.debug("close_all_children_of: [#{root_node.children}]") root_node.postordered_each do |node| @logger.debug("close_all_children_of:postordered_each [#{node.content}]") - if !node.is_root? && !node.content.closed + unless node.is_root? || node.content.closed begin item = ReportPortal.remote_item(node.content[:id]) @logger.debug("item details: [#{item}]") + @logger.debug("Start launch: [#{started_launch}], item[:status] [#{item['status']}]") if started_launch - ReportPortal.close_child_items(node.content[:id]) if item['status'].eql?('IN_PROGRESS') + if item.key?('end_time') + @logger.warn("Main process: item already closed skipping.") + else + ReportPortal.close_child_items(node.content[:id]) ReportPortal.finish_item(node.content) - + end else - ReportPortal.finish_item(node.content) unless item['status'].eql?('IN_PROGRESS') + if item.key?('end_time') + ReportPortal.finish_item(node.content) + else + @logger.warn("Child process: item in use cannot close it. [#{item}]") + end end end end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index fcc3657..ba2c4d1 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -69,7 +69,13 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) data[:issue] = { issue_type: 'NOT_ISSUE' } end self.logger.debug "finish_item:id[#{item}], data: #{data} " - process_request("item/#{item.id}",:put,data.to_json) + begin + process_request("item/#{item.id}", :put, data.to_json) + rescue RestClient::Exception => e + response = JSON.parse(e.response) + + raise e unless response['error_code'] == 40018 + end item.closed = true end end From 2cb359720e5aceb07af42283163130895f999d06 Mon Sep 17 00:00:00 2001 From: vveliev Date: Tue, 2 Jul 2019 14:50:05 -0400 Subject: [PATCH 78/98] - fixing file lock priority - adding more logging --- lib/report_portal/cucumber/report.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index f1209cd..586ae54 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -212,9 +212,14 @@ def complete_launch(desired_time) def lock_file(file_path = nil) file_path ||= ReportPortal::Settings.instance.file_with_launch_id - file_path ||= Dir.tmpdir + "/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" + @logger.debug("Lock file (RReportPortal::Settings.instance.file_with_launch_id): #{file_path}") if file_path file_path ||= Dir.tmpdir + "/report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid + @logger.debug("Lock file (ReportPortal::Settings.instance.launch_uuid): #{file_path}") if file_path + file_path ||= Dir.tmpdir + "/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" if @pid_of_parallel_tests + @logger.debug("Lock file (/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock): #{file_path}") if file_path file_path ||= Dir.tmpdir + '/rp_launch_id.tmp' + @logger.debug("Lock file (/rp_launch_id.tmp): #{file_path}") if file_path + file_path end @@ -222,8 +227,11 @@ def set_parallel_tests_vars process_list = Sys::ProcTable.ps @logger.debug("set_test_variables: #{process_list}") runner_process ||= get_parallel_test_process(process_list) + @logger.debug("Parallel cucumber runner pid: #{runner_process.pid}") if runner_process runner_process ||= get_cucumber_test_process(process_list) + @logger.debug("Cucumber runner pid: #{runner_process.pid}") if runner_process raise 'Failed to find any cucumber related test process' if runner_process.nil? + debugger @pid_of_parallel_tests = runner_process.pid end From 638a7bf4c5c74acc077f96ffe1d959445966890f Mon Sep 17 00:00:00 2001 From: vveliev Date: Tue, 2 Jul 2019 16:55:32 -0400 Subject: [PATCH 79/98] remove debugger adding logger --- lib/report_portal/cucumber/report.rb | 1 - lib/reportportal.rb | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 586ae54..98cca23 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -231,7 +231,6 @@ def set_parallel_tests_vars runner_process ||= get_cucumber_test_process(process_list) @logger.debug("Cucumber runner pid: #{runner_process.pid}") if runner_process raise 'Failed to find any cucumber related test process' if runner_process.nil? - debugger @pid_of_parallel_tests = runner_process.pid end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index ba2c4d1..b499dc2 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -165,7 +165,8 @@ def process_request(path, method, *options) begin response = project_resource[path].send(method, *options) rescue RestClient::Exception => e - self.logger.warn("Exception[#{e}],response:[#{e.response}], retry_count: [#{tries}]") + self.logger.warn("Exception[#{e}],response:[#{e.class}], retry_count: [#{tries}]") + self.logger.error("TRACE[#{e.backtrace}]") response = JSON.parse(e.response) m = response['message'].match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) if m From a79d2079560e281e677ae87668e864596fcd0ab5 Mon Sep 17 00:00:00 2001 From: vveliev Date: Tue, 2 Jul 2019 23:53:25 -0400 Subject: [PATCH 80/98] updating logger message on Error rescue --- lib/reportportal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index b499dc2..43bb751 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -165,7 +165,7 @@ def process_request(path, method, *options) begin response = project_resource[path].send(method, *options) rescue RestClient::Exception => e - self.logger.warn("Exception[#{e}],response:[#{e.class}], retry_count: [#{tries}]") + self.logger.warn("Exception[#{e}],class:[#{e.class}],class:[#{e.class}], retry_count: [#{tries}]") self.logger.error("TRACE[#{e.backtrace}]") response = JSON.parse(e.response) m = response['message'].match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) From 45b47a64bf49cd558446d33cec286fac6a850a4c Mon Sep 17 00:00:00 2001 From: vveliev Date: Mon, 8 Jul 2019 12:10:42 -0400 Subject: [PATCH 81/98] adding patch for multipart --- Gemfile | 2 +- lib/report_portal/cucumber/formatter.rb | 5 ++ lib/report_portal/logging/logger.rb | 2 +- lib/report_portal/patches/fariday.rb | 19 +++++++ lib/report_portal/patches/rest_client.rb | 45 ---------------- lib/reportportal.rb | 69 +++++++++++++++++------- 6 files changed, 76 insertions(+), 66 deletions(-) create mode 100644 lib/report_portal/patches/fariday.rb delete mode 100644 lib/report_portal/patches/rest_client.rb diff --git a/Gemfile b/Gemfile index e61f802..b78c327 100644 --- a/Gemfile +++ b/Gemfile @@ -2,12 +2,12 @@ source 'https://rubygems.org' gemspec +gem 'faraday' gem 'cucumber', '~> 3' gem 'log4r' gem 'logging' gem 'parallel_tests', '~> 2.15.0' gem 'rake' -gem 'rest-client' gem 'rspec' gem 'rubytree', git: 'https://github.com/razboev/RubyTree' gem 'sys-proctable', '~> 1.1.5' diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index d108b4e..c25ddfd 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -40,7 +40,12 @@ def setup_message_processing @thread = Thread.new do loop do method_arr = @queue.pop + begin report.public_send(*method_arr) + rescue => e + debugger + puts e + end end end @thread.abort_on_exception = true diff --git a/lib/report_portal/logging/logger.rb b/lib/report_portal/logging/logger.rb index 7b50111..9db43ce 100644 --- a/lib/report_portal/logging/logger.rb +++ b/lib/report_portal/logging/logger.rb @@ -2,7 +2,7 @@ module ReportPortal class << self - # Monkey-patch for built-in Logger class + # Monkey-patches for built-in Logger class def patch_logger Logger.class_eval do alias_method :orig_add, :add diff --git a/lib/report_portal/patches/fariday.rb b/lib/report_portal/patches/fariday.rb new file mode 100644 index 0000000..a1b4a26 --- /dev/null +++ b/lib/report_portal/patches/fariday.rb @@ -0,0 +1,19 @@ +class Faraday::Request::Multipart + def create_multipart(env, params) + debugger + boundary = env.request.boundary + parts = process_params(params) do |key, value| + if (JSON.parse(value) rescue false) + Faraday::Parts::Part.new(boundary, key, value, 'Content-Type' => 'application/json') + else + Faraday::Parts::Part.new(boundary, key, value) + end + end + parts << Faraday::Parts::EpiloguePart.new(boundary) + + body = Faraday::CompositeReadIO.new(parts) + env.request_headers[Faraday::Env::ContentLength] = body.length.to_s + debugger + return body + end +end \ No newline at end of file diff --git a/lib/report_portal/patches/rest_client.rb b/lib/report_portal/patches/rest_client.rb deleted file mode 100644 index 0318c39..0000000 --- a/lib/report_portal/patches/rest_client.rb +++ /dev/null @@ -1,45 +0,0 @@ -# monkey-patch to temporarily solve the issue in https://github.com/rest-client/rest-client/pull/222 -module RestClient - module Payload - extend self - class Multipart < Base - def build_stream(params) - content_type = params.delete(:content_type) - b = '--' + boundary - - @stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}") - @stream.binmode - @stream.write(b + EOL) - - case params - when Hash, ParamsArray - x = Utils.flatten_params(params) - else - x = params - end - - last_index = x.length - 1 - x.each_with_index do |a, index| - k, v = * a - if v.respond_to?(:read) && v.respond_to?(:path) - create_file_field(@stream, k, v) - else - create_regular_field(@stream, k, v, content_type) - end - @stream.write(EOL + b) - @stream.write(EOL) unless last_index == index - end - @stream.write('--') - @stream.write(EOL) - @stream.seek(0) - end - - def create_regular_field(s, k, v, type = nil) - s.write("Content-Disposition: form-data; name=\"#{k}\"") - s.write("#{EOL}Content-Type: #{type}#{EOL}") if type - s.write(EOL) - s.write(v) - end - end - end -end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 43bb751..ffcf676 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -1,18 +1,18 @@ require 'json' -require 'rest_client' +require 'faraday' require 'uri' require 'pathname' require 'tempfile' require_relative 'report_portal/settings' -require_relative 'report_portal/patches/rest_client' +require_relative 'report_portal/patches/fariday' module ReportPortal TestItem = Struct.new(:name, :type, :id, :start_time, :description, :closed, :tags) LOG_LEVELS = { error: 'ERROR', warn: 'WARN', info: 'INFO', debug: 'DEBUG', trace: 'TRACE', fatal: 'FATAL', unknown: 'UNKNOWN' } - class << self attr_accessor :launch_id, :current_scenario, :last_used_time, :logger + @verbose = false def now (Time.now.to_f * 1000).to_i @@ -91,6 +91,8 @@ def send_log(status, message, time) end def send_file(status, path, label = nil, time = now, mime_type = 'image/png') + debugger + @verbose = true unless File.file?(path) extension = ".#{MIME::Types[mime_type].first.extensions.first}" temp = Tempfile.open(['file', extension]) @@ -102,7 +104,7 @@ def send_file(status, path, label = nil, time = now, mime_type = 'image/png') File.open(File.realpath(path), 'rb') do |file| label ||= File.basename(file) json = { level: status_to_level(status), message: label, item_id: @current_scenario.id, time: time, file: { name: File.basename(file) } } - data = { :json_request_part => [json].to_json, label => file, :multipart => true, :content_type => 'application/json' } + data = { :json_request_part => [json].to_json, File.basename(file) => file, :content_type => 'application/json' } process_request("log",:post,data, content_type: 'multipart/form-data') end end @@ -163,8 +165,9 @@ def close_child_items(parent_id) def process_request(path, method, *options) tries = 5 begin - response = project_resource[path].send(method, *options) + response = project_resource(method, path, *options) rescue RestClient::Exception => e + debugger self.logger.warn("Exception[#{e}],class:[#{e.class}],class:[#{e.class}], retry_count: [#{tries}]") self.logger.error("TRACE[#{e.backtrace}]") response = JSON.parse(e.response) @@ -184,21 +187,49 @@ def process_request(path, method, *options) retry unless (tries -= 1).zero? end - JSON.parse(response) - end - - def project_resource - options = {} - options[:headers] = { - :Authorization => "Bearer #{Settings.instance.uuid}", - content_type: :json - } - verify_ssl = Settings.instance.disable_ssl_verification - options[:verify_ssl] = !verify_ssl unless verify_ssl.nil? - RestClient::Resource.new(Settings.instance.project_url, options) do |response, request, _, &block| - self.logger.debug "Request: #{request.args[:method].upcase} #{request.args[:url]}, data: #{request.args[:payload]}" - response.return!(&block) + debugger if @verbose + JSON.parse(response.body) + end + + def project_resource(method, path,*options) + + # settings = {headers: {'content_type' => 'application/json'}} + # pop_items = [] + # options.each_with_index do |option, i| + # if option.is_a?(Hash) + # if option.key?('content_type') + # pop_items.push(i) + # settings[:headers]['content_type'] = option['content_type'] + # end + # end + # end + # + # settings.each do |setting_name, setting_value| + # if setting_value.is_a?(Hash) + # setting_value.each do |key, value| + # tmp = rp_client.send(setting_name).merge + # rp_client.send(setting_name+ '=') + # end + # else + # + # end + # rp_client.send() + # end + debugger if @verbose + rp_client.send(method, path , *options) + end + + def rp_client + @connection ||= Faraday.new(url: Settings.instance.project_url) do |f| + f.headers={Authorization: "Bearer #{Settings.instance.uuid}", Accept: 'application/json','Content-type': 'application/json'} + verify_ssl = Settings.instance.disable_ssl_verification + f.ssl.verify = !verify_ssl unless verify_ssl.nil? + f.request :multipart + f.request :url_encoded + f.adapter :net_http end + + @connection end end end From abb0cb80e6c269da8cf981b88bd8110321734efc Mon Sep 17 00:00:00 2001 From: vveliev Date: Wed, 10 Jul 2019 11:18:22 -0400 Subject: [PATCH 82/98] fixing multipart post , adding some logs --- lib/report_portal/cucumber/formatter.rb | 5 --- lib/report_portal/cucumber/report.rb | 6 ++- lib/report_portal/patches/fariday.rb | 2 - lib/reportportal.rb | 53 +++++++------------------ 4 files changed, 18 insertions(+), 48 deletions(-) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index c25ddfd..d108b4e 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -40,12 +40,7 @@ def setup_message_processing @thread = Thread.new do loop do method_arr = @queue.pop - begin report.public_send(*method_arr) - rescue => e - debugger - puts e - end end end @thread.abort_on_exception = true diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 98cca23..e684ec8 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -184,6 +184,8 @@ def test_run_finished(_event, desired_time) @logger.info("close launch , delete lock") complete_launch(desired_time) end + else + complete_launch(desired_time) end end @@ -195,6 +197,7 @@ def add_process_description def puts(message, desired_time) ReportPortal.send_log(:info, message, time_to_send(desired_time)) + ReportPortal.send_log(:info, message, time_to_send(desired_time)) end def embed(src, mime_type, label, desired_time) @@ -345,8 +348,7 @@ def close_all_children_of(root_node) unless node.is_root? || node.content.closed begin item = ReportPortal.remote_item(node.content[:id]) - @logger.debug("item details: [#{item}]") - @logger.debug("Start launch: [#{started_launch}], item[:status] [#{item['status']}]") + @logger.debug("started_launch?: [#{started_launch}], item details: [#{item}]") if started_launch if item.key?('end_time') @logger.warn("Main process: item already closed skipping.") diff --git a/lib/report_portal/patches/fariday.rb b/lib/report_portal/patches/fariday.rb index a1b4a26..4a5a67d 100644 --- a/lib/report_portal/patches/fariday.rb +++ b/lib/report_portal/patches/fariday.rb @@ -1,6 +1,5 @@ class Faraday::Request::Multipart def create_multipart(env, params) - debugger boundary = env.request.boundary parts = process_params(params) do |key, value| if (JSON.parse(value) rescue false) @@ -13,7 +12,6 @@ def create_multipart(env, params) body = Faraday::CompositeReadIO.new(parts) env.request_headers[Faraday::Env::ContentLength] = body.length.to_s - debugger return body end end \ No newline at end of file diff --git a/lib/reportportal.rb b/lib/reportportal.rb index ffcf676..1c1e78a 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -60,7 +60,10 @@ def start_item(item_node) end def finish_item(item, status = nil, end_time = nil, force_issue = nil) - unless item.nil? || item.id.nil? || item.closed + + if item.nil? || item.id.nil? || item.closed + self.logger.debug "finish_item: Item details are missing or already closed" + else data = { end_time: end_time.nil? ? now : end_time } data[:status] = status unless status.nil? if force_issue && status != :passed # TODO: check for :passed status is probably not needed @@ -70,7 +73,8 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) end self.logger.debug "finish_item:id[#{item}], data: #{data} " begin - process_request("item/#{item.id}", :put, data.to_json) + response = process_request("item/#{item.id}", :put, data.to_json) + self.logger.debug "finish_item: response [#{response}] " rescue RestClient::Exception => e response = JSON.parse(e.response) @@ -91,7 +95,6 @@ def send_log(status, message, time) end def send_file(status, path, label = nil, time = now, mime_type = 'image/png') - debugger @verbose = true unless File.file?(path) extension = ".#{MIME::Types[mime_type].first.extensions.first}" @@ -101,12 +104,13 @@ def send_file(status, path, label = nil, time = now, mime_type = 'image/png') temp.rewind path = temp end - File.open(File.realpath(path), 'rb') do |file| - label ||= File.basename(file) - json = { level: status_to_level(status), message: label, item_id: @current_scenario.id, time: time, file: { name: File.basename(file) } } - data = { :json_request_part => [json].to_json, File.basename(file) => file, :content_type => 'application/json' } - process_request("log",:post,data, content_type: 'multipart/form-data') - end + file_name = File.basename(path) + label ||= file_name + json = { level: status_to_level(status), message: label, item_id: @current_scenario.id, time: time, file: { name: "#{file_name}" }, "Content-Type": 'application/json' } + headers = {'Content-Type': 'multipart/form-data'} + payload = { :json_request_part => [json].to_json, + file_name => Faraday::UploadIO.new(path, mime_type) } + process_request("log",:post,payload, headers) end def get_item(name, parent_node) @@ -165,9 +169,8 @@ def close_child_items(parent_id) def process_request(path, method, *options) tries = 5 begin - response = project_resource(method, path, *options) + response = rp_client.send(method, path, *options) rescue RestClient::Exception => e - debugger self.logger.warn("Exception[#{e}],class:[#{e.class}],class:[#{e.class}], retry_count: [#{tries}]") self.logger.error("TRACE[#{e.backtrace}]") response = JSON.parse(e.response) @@ -187,37 +190,9 @@ def process_request(path, method, *options) retry unless (tries -= 1).zero? end - debugger if @verbose JSON.parse(response.body) end - def project_resource(method, path,*options) - - # settings = {headers: {'content_type' => 'application/json'}} - # pop_items = [] - # options.each_with_index do |option, i| - # if option.is_a?(Hash) - # if option.key?('content_type') - # pop_items.push(i) - # settings[:headers]['content_type'] = option['content_type'] - # end - # end - # end - # - # settings.each do |setting_name, setting_value| - # if setting_value.is_a?(Hash) - # setting_value.each do |key, value| - # tmp = rp_client.send(setting_name).merge - # rp_client.send(setting_name+ '=') - # end - # else - # - # end - # rp_client.send() - # end - debugger if @verbose - rp_client.send(method, path , *options) - end def rp_client @connection ||= Faraday.new(url: Settings.instance.project_url) do |f| From c3a7f7927d2eb7c4a8df64fd1ee219bab5a71308 Mon Sep 17 00:00:00 2001 From: vveliev Date: Wed, 10 Jul 2019 11:25:25 -0400 Subject: [PATCH 83/98] reverting unwanted changes --- lib/report_portal/cucumber/report.rb | 1 - lib/report_portal/logging/logger.rb | 2 +- lib/reportportal.rb | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index e684ec8..98d3965 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -197,7 +197,6 @@ def add_process_description def puts(message, desired_time) ReportPortal.send_log(:info, message, time_to_send(desired_time)) - ReportPortal.send_log(:info, message, time_to_send(desired_time)) end def embed(src, mime_type, label, desired_time) diff --git a/lib/report_portal/logging/logger.rb b/lib/report_portal/logging/logger.rb index 9db43ce..7b50111 100644 --- a/lib/report_portal/logging/logger.rb +++ b/lib/report_portal/logging/logger.rb @@ -2,7 +2,7 @@ module ReportPortal class << self - # Monkey-patches for built-in Logger class + # Monkey-patch for built-in Logger class def patch_logger Logger.class_eval do alias_method :orig_add, :add diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 1c1e78a..1af33c1 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -95,7 +95,6 @@ def send_log(status, message, time) end def send_file(status, path, label = nil, time = now, mime_type = 'image/png') - @verbose = true unless File.file?(path) extension = ".#{MIME::Types[mime_type].first.extensions.first}" temp = Tempfile.open(['file', extension]) From 083a83ac111fd436c4680fce9fd4795b9ff77cd4 Mon Sep 17 00:00:00 2001 From: vveliev Date: Wed, 10 Jul 2019 15:14:55 -0400 Subject: [PATCH 84/98] updating version --- lib/report_portal/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/report_portal/version.rb b/lib/report_portal/version.rb index 3d4011d..1194435 100644 --- a/lib/report_portal/version.rb +++ b/lib/report_portal/version.rb @@ -1,3 +1,3 @@ module ReportPortal - VERSION = '0.7' + VERSION = '0.8' end From a64ff9e09490b97dcf90800fd59843c588709028 Mon Sep 17 00:00:00 2001 From: vveliev-tc <40637521+vveliev-tc@users.noreply.github.com> Date: Wed, 10 Jul 2019 15:24:47 -0400 Subject: [PATCH 85/98] various updates (#23) * Set description to cmd args of parallel_tests process * Fix matcher to detect when start time needs to be updated * Clean up formatting in report.rb * Implement a folder creation lock file to address duplicate folders being created * Delay each process by its ID number to create a staggered start to reporting in Report Portal (addresses load issues) * Formatting and cleanup in parallel_report.rb * Use temporary directory for tracking folder creation * Add commenting around why we are delaying threads * Make sure to use a "full path" for folder creation tracking * fixing report description when run in parallel - refactoring how runner process is detected - added fallback to cucumber if parallel test was not for parallel formatter * - reverting some changes - launch_uuid related fixes * Fix Cucumber parallel formatter * Fix parallel_tests version and possible endless loop * Add debug logging to debug some issue * Add more debug logging * Remove retrying of requests to Report Portal * Set last_used_time to a time of the parent item * Set description to cmd args of parallel_tests process * Fix URLs to include required "filter.eq.launch" parameter * Fix matcher to detect when start time needs to be updated * Clean up formatting in report.rb * Implement a folder creation lock file to address duplicate folders being created * Delay each process by its ID number to create a staggered start to reporting in Report Portal (addresses load issues) * fixing report description when run in parallel - if launch_id or file_with_launch_id is provided launch will not be marked as finished - moved sleep time, since we are waiting for report to be created anyways * - reverting file lock changes for reading the file - moving lock file related functions to private * - fixing temp file path generation - refactoring how runner process is detected - added fallback to cucumber if parallel test was not for parallel formatter * using single formatter for both parallel_cucumber and cucumber * adding logger for report portal * updating readme and adding settings for logging level * updating how response processing is handling * fixing issue with child process not closing the folder's items * fixing value for time, removing cucumber IO as it's no longer needed * - adding logging - when run in parallel, child process should not close items in process(folder) * - adding logging - add exception handling when closing item , if already closed , ignore RP 40018 error code * adding patch for multipart * updating version --- .rubocop_todo.yml | 67 +++++- Gemfile | 9 +- README.md | 46 ++-- lib/report_portal/cucumber/formatter.rb | 10 +- .../cucumber/parallel_formatter.rb | 14 -- lib/report_portal/cucumber/parallel_report.rb | 54 ----- lib/report_portal/cucumber/report.rb | 222 +++++++++++++++--- lib/report_portal/patches/fariday.rb | 17 ++ lib/report_portal/patches/rest_client.rb | 45 ---- lib/report_portal/settings.rb | 3 +- lib/report_portal/version.rb | 2 +- lib/reportportal.rb | 171 ++++++++------ 12 files changed, 409 insertions(+), 251 deletions(-) delete mode 100644 lib/report_portal/cucumber/parallel_formatter.rb delete mode 100644 lib/report_portal/cucumber/parallel_report.rb create mode 100644 lib/report_portal/patches/fariday.rb delete mode 100644 lib/report_portal/patches/rest_client.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6120c8b..61057f4 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,6 +6,14 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: with_first_argument, with_fixed_indentation +Layout/AlignArguments: + Exclude: + - 'lib/report_portal/rspec/formatter.rb' + # Offense count: 2 # Cop supports --auto-correct. Layout/EmptyLineAfterGuardClause: @@ -13,6 +21,60 @@ Layout/EmptyLineAfterGuardClause: - 'lib/report_portal/cucumber/json_slurper.rb' - 'tests/features/step_definitions/steps.rb' +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. +Layout/ExtraSpacing: + Exclude: + - 'lib/report_portal/logging/logging_appender.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Layout/LeadingCommentSpace: + Enabled: true + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: aligned, indented +Layout/MultilineOperationIndentation: + Exclude: + - 'lib/report_portal/cucumber/report.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +Layout/SpaceAfterComma: + Exclude: + - 'lib/report_portal/cucumber/report.rb' + - 'lib/reportportal.rb' + - 'tests/formatter_spec.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: space, no_space +Layout/SpaceAroundEqualsInParameterDefault: + Exclude: + - 'lib/reportportal.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment. +Layout/SpaceAroundOperators: + Exclude: + - 'lib/report_portal/cucumber/report.rb' + - 'lib/report_portal/logging/logging_appender.rb' + - 'lib/report_portal/rspec/formatter.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. +# SupportedStyles: space, no_space +# SupportedStylesForEmptyBraces: space, no_space +Layout/SpaceInsideBlockBraces: + Exclude: + - 'tests/formatter_spec.rb' + # Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. @@ -58,7 +120,7 @@ Metrics/BlockLength: # Offense count: 2 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 169 + Max: 250 # Offense count: 3 Metrics/CyclomaticComplexity: @@ -118,8 +180,6 @@ Style/Documentation: - 'spec/**/*' - 'test/**/*' - 'lib/report_portal/cucumber/formatter.rb' - - 'lib/report_portal/cucumber/parallel_formatter.rb' - - 'lib/report_portal/cucumber/parallel_report.rb' - 'lib/report_portal/logging/logger.rb' - 'lib/report_portal/patches/rest_client.rb' - 'lib/report_portal/rspec/formatter.rb' @@ -174,7 +234,6 @@ Style/GlobalVars: # Configuration parameters: MinBodyLength. Style/GuardClause: Exclude: - - 'lib/report_portal/cucumber/parallel_report.rb' - 'lib/report_portal/cucumber/report.rb' - 'lib/report_portal/rspec/formatter.rb' - 'lib/reportportal.rb' diff --git a/Gemfile b/Gemfile index e45c32c..b78c327 100644 --- a/Gemfile +++ b/Gemfile @@ -2,11 +2,12 @@ source 'https://rubygems.org' gemspec +gem 'faraday' gem 'cucumber', '~> 3' -gem 'parallel_tests' +gem 'log4r' +gem 'logging' +gem 'parallel_tests', '~> 2.15.0' gem 'rake' gem 'rspec' gem 'rubytree', git: 'https://github.com/razboev/RubyTree' - -gem 'log4r' -gem 'logging' +gem 'sys-proctable', '~> 1.1.5' diff --git a/README.md b/README.md index 0de4110..e4eca83 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,11 @@ Add `gem 'reportportal', git: 'https://github.com/reportportal/agent-ruby.git'` * With Cucumber: -```cucumber -f ReportPortal::Cucumber::Formatter``` +```cucumber -f ReportPortal::Cucumber::Formatter -o ''``` * With Cucumber and parallel_tests gem: -```parallel_cucumber -o ' -f ReportPortal::Cucumber::ParallelFormatter'``` +```parallel_cucumber -o ' -f ReportPortal::Cucumber::Formatter'``` * With RSpec: @@ -54,6 +54,7 @@ Supported settings: - launch_id - id of previously created launch (to be used if formatter_modes contains attach_to_launch) - file_with_launch_id - path to file with id of launch (to be used if formatter_modes contains attach_to_launch) - disable_ssl_verification - set to true to disable SSL verification on connect to ReportPortal (potential security hole!). Set `disable_ssl_verification` to `true` if you see the following error: + - log_level - this is log level for report_portal agent (useful for troubleshooting issued when run in parallel mode) ``` Request to https://rp.epam.com/reportportal-ws/api/v1/pass-team/launch//finish produced an exception: RestClient::SSLCertificateNotVerified: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed ``` @@ -75,12 +76,35 @@ WebMock.disable_net_connect!(:net_http_connect_on_start => true, :allow_localhos ## Formatter modes The following modes are supported: - -| Name | Purpose | -| --- | --- | -| attach_to_launch | Do not create a new launch but add executing features/scenarios to an existing launch. Use launch_id or file_with_launch_id settings to configure that. If they are not present client will check rp_launch_id.tmp in `Dir.tmpdir`) -| use_same_thread_for_reporting | Send reporting commands in the same main thread used for running tests. This mode is useful for debugging this Report Portal client. It changes default behavior to send commands in the separate thread. Default behavior is there not to slow test execution. | -| skip_reporting_hierarchy | Do not create items for folders and feature files | + + + + + + + + + + + + + + + +
NamePurpose
attach_to_launch +Add executing features/scenarios to an existing launch. +Use following options to configure that. + + 1. launch_id + 2. file_with_launch_id + 3. rp_launch_id.tmp in `Dir.tmpdir` + + If above options not present client will create new launch +
use_same_thread_for_reporting +Send reporting commands in the same main thread used for running tests. This mode is useful for debugging +Report Portal client. It changes default behavior to send commands in the separate thread. +Default behavior is there not to slow test execution.
skip_reporting_hierarchy +Do not create items for folders and feature files
## Logging Experimental support for three common logging frameworks was added: @@ -91,12 +115,6 @@ Experimental support for three common logging frameworks was added: To use Logger, set use_standard_logger parameter to true (see Configuration chapter). For the other two corresponding appenders/outputters are available under reportportal/logging. -## Parallel formatter - -ReportPortal::Cucumber::ParallelFormatter can be used for tests started via parallel_tests gem. - -Note: Launch id is shared between independent processes (as is the case with parallel_tests gem) via a file in `Dir.tmpdir`. - ## Links - [ReportPortal](https://github.com/reportportal/) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index ac45900..d108b4e 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -7,12 +7,10 @@ module Cucumber class Formatter # @api private def initialize(config) - ENV['REPORT_PORTAL_USED'] = 'true' - + @logger ||= Logger.new(config.out_stream) + @logger.level = ReportPortal::Settings.instance.log_level || :warn setup_message_processing - @io = config.out_stream - [:test_case_started, :test_case_finished, :test_step_started, :test_step_finished, :test_run_finished].each do |event_name| config.on_event event_name do |event| process_message(event_name, event) @@ -23,8 +21,6 @@ def initialize(config) def puts(message) process_message(:puts, message) - @io.puts(message) - @io.flush end def embed(*args) @@ -34,7 +30,7 @@ def embed(*args) private def report - @report ||= ReportPortal::Cucumber::Report.new + @report ||= ReportPortal::Cucumber::Report.new(@logger) end def setup_message_processing diff --git a/lib/report_portal/cucumber/parallel_formatter.rb b/lib/report_portal/cucumber/parallel_formatter.rb deleted file mode 100644 index a769685..0000000 --- a/lib/report_portal/cucumber/parallel_formatter.rb +++ /dev/null @@ -1,14 +0,0 @@ -require_relative 'formatter' -require_relative 'parallel_report' - -module ReportPortal - module Cucumber - class ParallelFormatter < Formatter - private - - def report - @report ||= ReportPortal::Cucumber::ParallelReport.new - end - end - end -end diff --git a/lib/report_portal/cucumber/parallel_report.rb b/lib/report_portal/cucumber/parallel_report.rb deleted file mode 100644 index f1648e3..0000000 --- a/lib/report_portal/cucumber/parallel_report.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'parallel_tests' - -require_relative 'report' - -module ReportPortal - module Cucumber - class ParallelReport < Report - FILE_WITH_LAUNCH_ID = Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{Process.ppid}.lck" - - def parallel? - true - end - - def initialize - @root_node = Tree::TreeNode.new('') - @parent_item_node = @root_node - @last_used_time ||= 0 - - if ParallelTests.first_process? - File.open(FILE_WITH_LAUNCH_ID, 'w') do |f| - f.flock(File::LOCK_EX) - start_launch - f.write(ReportPortal.launch_id) - f.flush - f.flock(File::LOCK_UN) - end - else - File.open(FILE_WITH_LAUNCH_ID, 'r') do |f| - f.flock(File::LOCK_SH) - ReportPortal.launch_id = f.read - f.flock(File::LOCK_UN) - end - end - end - - def test_run_finished(_event, desired_time = ReportPortal.now) - end_feature(desired_time) unless @parent_item_node.is_root? - - if ParallelTests.first_process? - ParallelTests.wait_for_other_processes_to_finish - - File.delete(FILE_WITH_LAUNCH_ID) - - unless attach_to_launch? - $stdout.puts "Finishing launch #{ReportPortal.launch_id}" - ReportPortal.close_child_items(nil) - time_to_send = time_to_send(desired_time) - ReportPortal.finish_launch(time_to_send) - end - end - end - end - end -end diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index c3e6370..98d3965 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -2,6 +2,10 @@ require 'cucumber/formatter/hook_query_visitor' require 'tree' require 'securerandom' +require 'tempfile' +require 'parallel_tests' +require 'sys/proctable' +require 'fileutils' require_relative '../../reportportal' require_relative '../logging/logger' @@ -10,39 +14,90 @@ module ReportPortal module Cucumber # @api private class Report - def parallel? - false - end + attr_accessor :parallel, :started_launch + @folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" def attach_to_launch? ReportPortal::Settings.instance.formatter_modes.include?('attach_to_launch') end - def initialize - @last_used_time = 0 + def initialize(logger) + @logger = logger + ReportPortal.last_used_time = 0 + ReportPortal.logger = logger @root_node = Tree::TreeNode.new('') @parent_item_node = @root_node - start_launch + + set_parallel_tests_vars + + if ParallelTests.first_process? + @logger.debug("First process: #{@pid_of_parallel_tests}") + start_launch(ReportPortal.now) + else + @logger.debug("Child process: #{@pid_of_parallel_tests}") + start_time = monotonic_time + loop do + break if File.exist?(lock_file) + if monotonic_time - start_time > wait_time_for_launch_create + raise "File with launch ID wasn't created after waiting #{wait_time_for_launch_create} seconds" + end + @logger.debug "File with launch ID wasn't created after waiting #{monotonic_time - start_time} seconds" + + sleep 0.5 + end + ReportPortal.launch_id = read_lock_file(lock_file) + @logger.debug "Attaching to launch using lock_file [#{lock_file}], launch_id: [#{ReportPortal.launch_id}] " + add_process_description + end end - def start_launch(desired_time = ReportPortal.now) + + def start_launch(desired_time, cmd_args = ARGV) + # Expected behavior that make sense: + # 1. If launch_id present attach to existing (simple use case) + # 2. If launch_id not present check if exist rp_launch_id.tmp + # 3. [ADDED] If launch_id is not present check if lock exist with launch_uuid if attach_to_launch? ReportPortal.launch_id = if ReportPortal::Settings.instance.launch_id ReportPortal::Settings.instance.launch_id else - file_path = ReportPortal::Settings.instance.file_with_launch_id || (Pathname(Dir.tmpdir) + 'rp_launch_id.tmp') - File.read(file_path) + self.started_launch = true + file_path = lock_file + File.file?(file_path) ? read_lock_file(file_path) : new_launch(desired_time, cmd_args, file_path) end - $stdout.puts "Attaching to launch #{ReportPortal.launch_id}" + @logger.info "Attaching to launch #{ReportPortal.launch_id}" else - description = ReportPortal::Settings.instance.description - description ||= ARGV.map { |arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]") }.join(' ') - ReportPortal.start_launch(description, time_to_send(desired_time)) + new_launch(desired_time, cmd_args) end end - def test_case_started(event, desired_time = ReportPortal.now) # TODO: time should be a required argument + def new_launch(desired_time, cmd_args = ARGV, lock_file = nil) + @logger.info("Creating new launch at: [#{desired_time}], with cmd: [#{cmd_args}] and file lock: [#{lock_file}]") + ReportPortal.start_launch(description(cmd_args), time_to_send(desired_time)) + set_file_lock_with_launch_id(lock_file, ReportPortal.launch_id) if lock_file + ReportPortal.launch_id + end + + def description(cmd_args = ARGV) + description ||= ReportPortal::Settings.instance.description + description ||= cmd_args.map { |arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]") }.join(' ') + description + end + + def set_file_lock_with_launch_id(lock_file, launch_id) + FileUtils.mkdir_p File.dirname(lock_file) + File.open(lock_file, 'w') do |f| + f.flock(File::LOCK_EX) + f.write(launch_id) + f.flush + f.flock(File::LOCK_UN) + end + end + + # scenario starts in separate treads + def test_case_started(event, desired_time) + @logger.debug "test_case_started: [#{event}], " test_case = event.test_case feature = test_case.feature if report_hierarchy? && !same_feature_as_previous_test_case?(feature) @@ -61,7 +116,8 @@ def test_case_started(event, desired_time = ReportPortal.now) # TODO: time shoul ReportPortal.current_scenario.id = ReportPortal.start_item(scenario_node) end - def test_case_finished(event, desired_time = ReportPortal.now) + def test_case_finished(event, desired_time) + @logger.debug "test_case_finished: [#{event}], " result = event.result status = result.to_sym issue = nil @@ -73,7 +129,8 @@ def test_case_finished(event, desired_time = ReportPortal.now) ReportPortal.current_scenario = nil end - def test_step_started(event, desired_time = ReportPortal.now) + def test_step_started(event, desired_time) + @logger.debug "test_step_started: [#{event}], " test_step = event.test_step if step?(test_step) # `after_test_step` is also invoked for hooks step_source = test_step.source.last @@ -87,7 +144,7 @@ def test_step_started(event, desired_time = ReportPortal.now) end end - def test_step_finished(event, desired_time = ReportPortal.now) + def test_step_finished(event, desired_time) test_step = event.test_step result = event.result status = result.to_sym @@ -115,26 +172,109 @@ def test_step_finished(event, desired_time = ReportPortal.now) end end - def test_run_finished(_event, desired_time = ReportPortal.now) + def test_run_finished(_event, desired_time) end_feature(desired_time) unless @parent_item_node.is_root? - - unless attach_to_launch? - close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature - time_to_send = time_to_send(desired_time) - ReportPortal.finish_launch(time_to_send) + @logger.info("Test run finish: [#{@parent_item_node}]") + close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature + if parallel + @logger.debug("Parallel process: #{@pid_of_parallel_tests}") + if ParallelTests.first_process? && started_launch + ParallelTests.wait_for_other_processes_to_finish + File.delete(lock_file) + @logger.info("close launch , delete lock") + complete_launch(desired_time) + end + else + complete_launch(desired_time) end end - def puts(message, desired_time = ReportPortal.now) + def add_process_description + description = ReportPortal.remote_launch['description'].split(' ') + description.push(self.description.split(' ')).flatten! + ReportPortal.update_launch(description: description.uniq.join(' ')) + end + + def puts(message, desired_time) ReportPortal.send_log(:info, message, time_to_send(desired_time)) end - def embed(src, mime_type, label, desired_time = ReportPortal.now) + def embed(src, mime_type, label, desired_time) ReportPortal.send_file(:info, src, label, time_to_send(desired_time), mime_type) end private + def complete_launch(desired_time) + if started_launch || !attach_to_launch? + time_to_send = time_to_send(desired_time) + ReportPortal.finish_launch(time_to_send) + end + end + + def lock_file(file_path = nil) + file_path ||= ReportPortal::Settings.instance.file_with_launch_id + @logger.debug("Lock file (RReportPortal::Settings.instance.file_with_launch_id): #{file_path}") if file_path + file_path ||= Dir.tmpdir + "/report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid + @logger.debug("Lock file (ReportPortal::Settings.instance.launch_uuid): #{file_path}") if file_path + file_path ||= Dir.tmpdir + "/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" if @pid_of_parallel_tests + @logger.debug("Lock file (/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock): #{file_path}") if file_path + file_path ||= Dir.tmpdir + '/rp_launch_id.tmp' + @logger.debug("Lock file (/rp_launch_id.tmp): #{file_path}") if file_path + + file_path + end + + def set_parallel_tests_vars + process_list = Sys::ProcTable.ps + @logger.debug("set_test_variables: #{process_list}") + runner_process ||= get_parallel_test_process(process_list) + @logger.debug("Parallel cucumber runner pid: #{runner_process.pid}") if runner_process + runner_process ||= get_cucumber_test_process(process_list) + @logger.debug("Cucumber runner pid: #{runner_process.pid}") if runner_process + raise 'Failed to find any cucumber related test process' if runner_process.nil? + @pid_of_parallel_tests = runner_process.pid + end + + def get_parallel_test_process(process_list) + process_list.each do |process| + if process.cmdline.match(%r{bin(?:\/|\\)parallel_(?:cucumber|test)(.+)}) + @parallel = true + @logger.debug("get_parallel_test_process: #{process.cmdline}") + return process + end + end + nil + end + + def get_cucumber_test_process(process_list) + process_list.each do |process| + if process.cmdline.match(%r{bin(?:\/|\\)(?:cucumber)(.+)}) + @logger.debug("get_cucumber_test_process: #{process.cmdline}") + return process + end + end + nil + end + + def wait_time_for_launch_create + ENV['rp_parallel_launch_wait_time'] || 60 + end + + def monotonic_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + + def read_lock_file(file_path) + content = nil + File.open(file_path, 'r') do |f| + f.flock(File::LOCK_SH) + content = File.read(file_path) + f.flock(File::LOCK_UN) + end + content + end + # Report Portal sorts logs by time. However, several logs might have the same time. # So to get Report Portal sort them properly the time should be different in all logs related to the same item. # And thus it should be stored. @@ -143,10 +283,10 @@ def embed(src, mime_type, label, desired_time = ReportPortal.now) # * that process/thread can't start the next test until it's done with the previous one def time_to_send(desired_time) time_to_send = desired_time - if time_to_send <= @last_used_time - time_to_send = @last_used_time + 1 + if time_to_send <= ReportPortal.last_used_time + time_to_send = ReportPortal.last_used_time + 1 end - @last_used_time = time_to_send + ReportPortal.last_used_time = time_to_send end def same_feature_as_previous_test_case?(feature) @@ -154,6 +294,7 @@ def same_feature_as_previous_test_case?(feature) end def start_feature_with_parentage(feature, desired_time) + @logger.debug("start_feature_with_parentage: [#{feature}], [#{desired_time}]") parent_node = @root_node child_node = nil path_components = feature.location.file.split(File::SEPARATOR) @@ -172,7 +313,7 @@ def start_feature_with_parentage(feature, desired_time) type = :TEST end # TODO: multithreading # Parallel formatter always executes scenarios inside the same feature in the same process - if parallel? && + if parallel && index < path_components.size - 1 && # is folder? (id_of_created_item = ReportPortal.item_id_of(name, parent_node)) # get id for folder from report portal # get child id from other process @@ -200,9 +341,28 @@ def end_feature(desired_time) end def close_all_children_of(root_node) + @logger.debug("close_all_children_of: [#{root_node.children}]") root_node.postordered_each do |node| - if !node.is_root? && !node.content.closed - ReportPortal.finish_item(node.content) + @logger.debug("close_all_children_of:postordered_each [#{node.content}]") + unless node.is_root? || node.content.closed + begin + item = ReportPortal.remote_item(node.content[:id]) + @logger.debug("started_launch?: [#{started_launch}], item details: [#{item}]") + if started_launch + if item.key?('end_time') + @logger.warn("Main process: item already closed skipping.") + else + ReportPortal.close_child_items(node.content[:id]) + ReportPortal.finish_item(node.content) + end + else + if item.key?('end_time') + ReportPortal.finish_item(node.content) + else + @logger.warn("Child process: item in use cannot close it. [#{item}]") + end + end + end end end end diff --git a/lib/report_portal/patches/fariday.rb b/lib/report_portal/patches/fariday.rb new file mode 100644 index 0000000..4a5a67d --- /dev/null +++ b/lib/report_portal/patches/fariday.rb @@ -0,0 +1,17 @@ +class Faraday::Request::Multipart + def create_multipart(env, params) + boundary = env.request.boundary + parts = process_params(params) do |key, value| + if (JSON.parse(value) rescue false) + Faraday::Parts::Part.new(boundary, key, value, 'Content-Type' => 'application/json') + else + Faraday::Parts::Part.new(boundary, key, value) + end + end + parts << Faraday::Parts::EpiloguePart.new(boundary) + + body = Faraday::CompositeReadIO.new(parts) + env.request_headers[Faraday::Env::ContentLength] = body.length.to_s + return body + end +end \ No newline at end of file diff --git a/lib/report_portal/patches/rest_client.rb b/lib/report_portal/patches/rest_client.rb deleted file mode 100644 index 0318c39..0000000 --- a/lib/report_portal/patches/rest_client.rb +++ /dev/null @@ -1,45 +0,0 @@ -# monkey-patch to temporarily solve the issue in https://github.com/rest-client/rest-client/pull/222 -module RestClient - module Payload - extend self - class Multipart < Base - def build_stream(params) - content_type = params.delete(:content_type) - b = '--' + boundary - - @stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}") - @stream.binmode - @stream.write(b + EOL) - - case params - when Hash, ParamsArray - x = Utils.flatten_params(params) - else - x = params - end - - last_index = x.length - 1 - x.each_with_index do |a, index| - k, v = * a - if v.respond_to?(:read) && v.respond_to?(:path) - create_file_field(@stream, k, v) - else - create_regular_field(@stream, k, v, content_type) - end - @stream.write(EOL + b) - @stream.write(EOL) unless last_index == index - end - @stream.write('--') - @stream.write(EOL) - @stream.seek(0) - end - - def create_regular_field(s, k, v, type = nil) - s.write("Content-Disposition: form-data; name=\"#{k}\"") - s.write("#{EOL}Content-Type: #{type}#{EOL}") if type - s.write(EOL) - s.write(v) - end - end - end -end diff --git a/lib/report_portal/settings.rb b/lib/report_portal/settings.rb index a8f48fa..56edb44 100644 --- a/lib/report_portal/settings.rb +++ b/lib/report_portal/settings.rb @@ -24,10 +24,11 @@ def initialize 'tags' => false, 'is_debug' => false, 'disable_ssl_verification' => false, - # for parallel execution only 'use_standard_logger' => false, 'launch_id' => false, 'file_with_launch_id' => false, + 'launch_uuid' => false, + 'log_level' => false } keys.each do |key, is_required| diff --git a/lib/report_portal/version.rb b/lib/report_portal/version.rb index 3d4011d..1194435 100644 --- a/lib/report_portal/version.rb +++ b/lib/report_portal/version.rb @@ -1,3 +1,3 @@ module ReportPortal - VERSION = '0.7' + VERSION = '0.8' end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index b0487b3..1af33c1 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -1,29 +1,18 @@ require 'json' -require 'rest_client' +require 'faraday' require 'uri' require 'pathname' require 'tempfile' require_relative 'report_portal/settings' -require_relative 'report_portal/patches/rest_client' +require_relative 'report_portal/patches/fariday' module ReportPortal TestItem = Struct.new(:name, :type, :id, :start_time, :description, :closed, :tags) LOG_LEVELS = { error: 'ERROR', warn: 'WARN', info: 'INFO', debug: 'DEBUG', trace: 'TRACE', fatal: 'FATAL', unknown: 'UNKNOWN' } - - @response_handler = proc do |response, request, result, &block| - if (200..207).include? response.code - response - else - p "ReportPortal API returned #{response}" - p "Offending request method/URL: #{request.args[:method].upcase} #{request.args[:url]}" - p "Offending request payload: #{request.args[:payload]}}" - response.return!(request, result, &block) - end - end - class << self - attr_accessor :launch_id, :current_scenario + attr_accessor :launch_id, :current_scenario, :last_used_time, :logger + @verbose = false def now (Time.now.to_f * 1000).to_i @@ -43,35 +32,38 @@ def status_to_level(status) end def start_launch(description, start_time = now) - url = "#{Settings.instance.project_url}/launch" data = { name: Settings.instance.launch, start_time: start_time, tags: Settings.instance.tags, description: description, mode: Settings.instance.launch_mode } - @launch_id = do_request(url) do |resource| - JSON.parse(resource.post(data.to_json, content_type: :json, &@response_handler))['id'] - end + @launch_id = process_request('launch',:post,data.to_json)['id'] + end + + def remote_launch + process_request("launch/#{@launch_id}",:get) + end + + def update_launch(data) + process_request("launch/#{@launch_id}/update",:put, data.to_json) end def finish_launch(end_time = now) - url = "#{Settings.instance.project_url}/launch/#{@launch_id}/finish" + self.logger.debug "finish_launch: [#{end_time}]" data = { end_time: end_time } - do_request(url) do |resource| - resource.put data.to_json, content_type: :json, &@response_handler - end + process_request("launch/#{@launch_id}/finish",:put,data.to_json) end def start_item(item_node) - url = "#{Settings.instance.project_url}/item" - url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? item = item_node.content data = { start_time: item.start_time, name: item.name[0, 255], type: item.type.to_s, launch_id: @launch_id, description: item.description } data[:tags] = item.tags unless item.tags.empty? - do_request(url) do |resource| - JSON.parse(resource.post(data.to_json, content_type: :json, &@response_handler))['id'] - end + url = 'item' + url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? + process_request(url,:post,data.to_json)['id'] end def finish_item(item, status = nil, end_time = nil, force_issue = nil) - unless item.nil? || item.id.nil? || item.closed - url = "#{Settings.instance.project_url}/item/#{item.id}" + + if item.nil? || item.id.nil? || item.closed + self.logger.debug "finish_item: Item details are missing or already closed" + else data = { end_time: end_time.nil? ? now : end_time } data[:status] = status unless status.nil? if force_issue && status != :passed # TODO: check for :passed status is probably not needed @@ -79,8 +71,14 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) elsif status == :skipped data[:issue] = { issue_type: 'NOT_ISSUE' } end - do_request(url) do |resource| - resource.put data.to_json, content_type: :json, &@response_handler + self.logger.debug "finish_item:id[#{item}], data: #{data} " + begin + response = process_request("item/#{item.id}", :put, data.to_json) + self.logger.debug "finish_item: response [#{response}] " + rescue RestClient::Exception => e + response = JSON.parse(e.response) + + raise e unless response['error_code'] == 40018 end item.closed = true end @@ -89,17 +87,14 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) # TODO: implement force finish def send_log(status, message, time) + @logger.debug "send_log: [#{status}],[#{message}], #{@current_scenario} " unless @current_scenario.nil? || @current_scenario.closed # it can be nil if scenario outline in expand mode is executed - url = "#{Settings.instance.project_url}/log" data = { item_id: @current_scenario.id, time: time, level: status_to_level(status), message: message.to_s } - do_request(url) do |resource| - resource.post(data.to_json, content_type: :json, &@response_handler) - end + process_request("log",:post,data.to_json) end end def send_file(status, path, label = nil, time = now, mime_type = 'image/png') - url = "#{Settings.instance.project_url}/log" unless File.file?(path) extension = ".#{MIME::Types[mime_type].first.extensions.first}" temp = Tempfile.open(['file', extension]) @@ -108,43 +103,47 @@ def send_file(status, path, label = nil, time = now, mime_type = 'image/png') temp.rewind path = temp end - File.open(File.realpath(path), 'rb') do |file| - label ||= File.basename(file) - json = { level: status_to_level(status), message: label, item_id: @current_scenario.id, time: time, file: { name: File.basename(file) } } - data = { :json_request_part => [json].to_json, label => file, :multipart => true, :content_type => 'application/json' } - do_request(url) do |resource| - resource.post(data, { content_type: 'multipart/form-data' }, &@response_handler) - end - end + file_name = File.basename(path) + label ||= file_name + json = { level: status_to_level(status), message: label, item_id: @current_scenario.id, time: time, file: { name: "#{file_name}" }, "Content-Type": 'application/json' } + headers = {'Content-Type': 'multipart/form-data'} + payload = { :json_request_part => [json].to_json, + file_name => Faraday::UploadIO.new(path, mime_type) } + process_request("log",:post,payload, headers) end - # needed for parallel formatter - def item_id_of(name, parent_node) + def get_item(name, parent_node) if parent_node.is_root? # folder without parent folder - url = "#{Settings.instance.project_url}/item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{URI.escape(name)}&filter.size.path=0" + url = "item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{URI.escape(name)}&filter.size.path=0" else - url = "#{Settings.instance.project_url}/item?filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" + url = "item?filter.eq.launch=#{@launch_id}&filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" end - do_request(url) do |resource| - data = JSON.parse(resource.get) - if data.key? 'content' - data['content'].empty? ? nil : data['content'][0]['id'] - else - nil # item isn't started yet - end + process_request(url,:get) + end + + def remote_item(item_id) + process_request("item/#{item_id}",:get) + end + + def item_id_of(name, parent_node) + data = get_item(name, parent_node) + if data.key? 'content' + data['content'].empty? ? nil : data['content'][0]['id'] + else + nil # item isn't started yet end end - # needed for parallel formatter def close_child_items(parent_id) + self.logger.debug "closing child items: #{parent_id} " if parent_id.nil? - url = "#{Settings.instance.project_url}/item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" + url = "item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" else - url = "#{Settings.instance.project_url}/item?filter.eq.parent=#{parent_id}&page.page=1&page.size=100" + url = "item?filter.eq.launch=#{@launch_id}&filter.eq.parent=#{parent_id}&page.page=1&page.size=100" end ids = [] loop do - response = do_request(url) { |r| JSON.parse(r.get) } + response = process_request(url,:get) if response.key?('links') link = response['links'].find { |i| i['rel'] == 'next' } url = link.nil? ? nil : link['href'] @@ -166,25 +165,45 @@ def close_child_items(parent_id) private - def create_resource(url) - props = { :headers => {:Authorization => "Bearer #{Settings.instance.uuid}"}} - verify_ssl = Settings.instance.disable_ssl_verification - props[:verify_ssl] = !verify_ssl unless verify_ssl.nil? - RestClient::Resource.new url, props - end - - def do_request(url) - resource = create_resource(url) - tries = 3 + def process_request(path, method, *options) + tries = 5 begin - yield resource - rescue - p "Request to #{url} produced an exception: #{$!.class}: #{$!}" - $!.backtrace.each { |l| p l } + response = rp_client.send(method, path, *options) + rescue RestClient::Exception => e + self.logger.warn("Exception[#{e}],class:[#{e.class}],class:[#{e.class}], retry_count: [#{tries}]") + self.logger.error("TRACE[#{e.backtrace}]") + response = JSON.parse(e.response) + m = response['message'].match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) + if m + parent_time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') + data = JSON.parse(options[0]) + self.logger.warn("RP error : 40025, time of a child: [#{data['start_time']}], paren time: [#{(parent_time.to_f * 1000).to_i}]") + data['start_time'] = (parent_time.to_f * 1000).to_i + 1000 + options[0] = data.to_json + ReportPortal.last_used_time = data['start_time'] + else + self.logger.error("RestClient::Exception -> response: [#{response}]") + self.logger.error("TRACE[#{e.backtrace}]") + raise + end + retry unless (tries -= 1).zero? - p "Failed to execute request to #{url} after 3 attempts." - nil end + JSON.parse(response.body) + end + + + def rp_client + @connection ||= Faraday.new(url: Settings.instance.project_url) do |f| + f.headers={Authorization: "Bearer #{Settings.instance.uuid}", Accept: 'application/json','Content-type': 'application/json'} + verify_ssl = Settings.instance.disable_ssl_verification + f.ssl.verify = !verify_ssl unless verify_ssl.nil? + f.request :multipart + f.request :url_encoded + f.adapter :net_http + end + + @connection end end end From 1e50fec459222c87bbd5b0ccf96023b9923f4df4 Mon Sep 17 00:00:00 2001 From: vveliev Date: Wed, 10 Jul 2019 18:10:35 -0400 Subject: [PATCH 86/98] fixing layout errors Fix sending plain text to attachment removing trailing whitespace --- .rubocop_todo.yml | 18 +++----- Gemfile | 2 +- lib/report_portal/cucumber/formatter.rb | 2 +- lib/report_portal/cucumber/report.rb | 31 ++++++-------- lib/report_portal/patches/fariday.rb | 36 ++++++++++------ lib/reportportal.rb | 57 ++++++++++++++----------- 6 files changed, 75 insertions(+), 71 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 61057f4..6741bc2 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-05-31 14:29:36 +0200 using RuboCop version 0.71.0. +# on 2019-07-01 15:09:18 +0200 using RuboCop version 0.69.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -85,12 +85,6 @@ Layout/SpaceInsideHashLiteralBraces: - 'lib/reportportal.rb' - 'tests/formatter_spec.rb' -# Offense count: 100 -# Cop supports --auto-correct. -# Configuration parameters: AllowInHeredoc. -Layout/TrailingWhitespace: - Enabled: false - # Offense count: 1 Lint/ParenthesesAsGroupedExpression: Exclude: @@ -120,13 +114,13 @@ Metrics/BlockLength: # Offense count: 2 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 250 + Max: 350 # Offense count: 3 Metrics/CyclomaticComplexity: Max: 13 -# Offense count: 21 +# Offense count: 19 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: Max: 79 @@ -134,7 +128,7 @@ Metrics/MethodLength: # Offense count: 1 # Configuration parameters: CountComments. Metrics/ModuleLength: - Max: 158 + Max: 250 # Offense count: 5 Metrics/PerceivedComplexity: @@ -336,7 +330,7 @@ Style/StringLiterals: - 'lib/report_portal/cucumber/report.rb' - 'lib/report_portal/tasks.rb' -# Offense count: 5 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: MinSize. # SupportedStyles: percent, brackets @@ -360,7 +354,7 @@ Style/TrailingCommaInHashLiteral: Exclude: - 'lib/report_portal/settings.rb' -# Offense count: 102 +# Offense count: 104 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https diff --git a/Gemfile b/Gemfile index b78c327..1d759e7 100644 --- a/Gemfile +++ b/Gemfile @@ -2,8 +2,8 @@ source 'https://rubygems.org' gemspec -gem 'faraday' gem 'cucumber', '~> 3' +gem 'faraday' gem 'log4r' gem 'logging' gem 'parallel_tests', '~> 2.15.0' diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index d108b4e..0afb37c 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -7,7 +7,7 @@ module Cucumber class Formatter # @api private def initialize(config) - @logger ||= Logger.new(config.out_stream) + @logger = Logger.new(config.out_stream) @logger.level = ReportPortal::Settings.instance.log_level || :warn setup_message_processing diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 98d3965..d4d11c6 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -41,6 +41,7 @@ def initialize(logger) if monotonic_time - start_time > wait_time_for_launch_create raise "File with launch ID wasn't created after waiting #{wait_time_for_launch_create} seconds" end + @logger.debug "File with launch ID wasn't created after waiting #{monotonic_time - start_time} seconds" sleep 0.5 @@ -51,7 +52,6 @@ def initialize(logger) end end - def start_launch(desired_time, cmd_args = ARGV) # Expected behavior that make sense: # 1. If launch_id present attach to existing (simple use case) @@ -233,16 +233,17 @@ def set_parallel_tests_vars runner_process ||= get_cucumber_test_process(process_list) @logger.debug("Cucumber runner pid: #{runner_process.pid}") if runner_process raise 'Failed to find any cucumber related test process' if runner_process.nil? + @pid_of_parallel_tests = runner_process.pid end def get_parallel_test_process(process_list) process_list.each do |process| - if process.cmdline.match(%r{bin(?:\/|\\)parallel_(?:cucumber|test)(.+)}) - @parallel = true - @logger.debug("get_parallel_test_process: #{process.cmdline}") - return process - end + next unless process.cmdline.match(%r{bin(?:\/|\\)parallel_(?:cucumber|test)(.+)}) + + @parallel = true + @logger.debug("get_parallel_test_process: #{process.cmdline}") + return process end nil end @@ -348,19 +349,13 @@ def close_all_children_of(root_node) begin item = ReportPortal.remote_item(node.content[:id]) @logger.debug("started_launch?: [#{started_launch}], item details: [#{item}]") - if started_launch - if item.key?('end_time') - @logger.warn("Main process: item already closed skipping.") - else - ReportPortal.close_child_items(node.content[:id]) - ReportPortal.finish_item(node.content) - end + if item.key?('end_time') + started_launch ? @logger.warn("Main process: item already closed skipping.") : ReportPortal.finish_item(node.content) + elsif started_launch + ReportPortal.close_child_items(node.content[:id]) + ReportPortal.finish_item(node.content) else - if item.key?('end_time') - ReportPortal.finish_item(node.content) - else - @logger.warn("Child process: item in use cannot close it. [#{item}]") - end + @logger.warn("Child process: item in use cannot close it. [#{item}]") end end end diff --git a/lib/report_portal/patches/fariday.rb b/lib/report_portal/patches/fariday.rb index 4a5a67d..9f92d11 100644 --- a/lib/report_portal/patches/fariday.rb +++ b/lib/report_portal/patches/fariday.rb @@ -1,17 +1,25 @@ -class Faraday::Request::Multipart - def create_multipart(env, params) - boundary = env.request.boundary - parts = process_params(params) do |key, value| - if (JSON.parse(value) rescue false) - Faraday::Parts::Part.new(boundary, key, value, 'Content-Type' => 'application/json') - else - Faraday::Parts::Part.new(boundary, key, value) +module Faraday + class Requests + # Middleware for supporting multi-part requests. + class Multipart + def create_multipart(env, params) + boundary = env.request.boundary + parts = process_params(params) do |key, value| + if begin + JSON.parse(value) + rescue StandardError + false + end + Faraday::Parts::Part.new(boundary, key, value, 'Content-Type' => 'application/json') + else + Faraday::Parts::Part.new(boundary, key, value) + end + end + parts << Faraday::Parts::EpiloguePart.new(boundary) + body = Faraday::CompositeReadIO.new(parts) + env.request_headers[Faraday::Env::ContentLength] = body.length.to_s + body end end - parts << Faraday::Parts::EpiloguePart.new(boundary) - - body = Faraday::CompositeReadIO.new(parts) - env.request_headers[Faraday::Env::ContentLength] = body.length.to_s - return body end -end \ No newline at end of file +end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 1af33c1..2948010 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -45,7 +45,7 @@ def update_launch(data) end def finish_launch(end_time = now) - self.logger.debug "finish_launch: [#{end_time}]" + logger.debug "finish_launch: [#{end_time}]" data = { end_time: end_time } process_request("launch/#{@launch_id}/finish",:put,data.to_json) end @@ -60,9 +60,8 @@ def start_item(item_node) end def finish_item(item, status = nil, end_time = nil, force_issue = nil) - if item.nil? || item.id.nil? || item.closed - self.logger.debug "finish_item: Item details are missing or already closed" + logger.debug 'finish_item: Item details are missing or already closed' else data = { end_time: end_time.nil? ? now : end_time } data[:status] = status unless status.nil? @@ -71,14 +70,14 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) elsif status == :skipped data[:issue] = { issue_type: 'NOT_ISSUE' } end - self.logger.debug "finish_item:id[#{item}], data: #{data} " + logger.debug "finish_item:id[#{item}], data: #{data} " begin response = process_request("item/#{item.id}", :put, data.to_json) - self.logger.debug "finish_item: response [#{response}] " + logger.debug "finish_item: response [#{response}] " rescue RestClient::Exception => e response = JSON.parse(e.response) - raise e unless response['error_code'] == 40018 + raise e unless response['error_code'] == 40_018 end item.closed = true end @@ -90,26 +89,35 @@ def send_log(status, message, time) @logger.debug "send_log: [#{status}],[#{message}], #{@current_scenario} " unless @current_scenario.nil? || @current_scenario.closed # it can be nil if scenario outline in expand mode is executed data = { item_id: @current_scenario.id, time: time, level: status_to_level(status), message: message.to_s } - process_request("log",:post,data.to_json) + process_request('log',:post,data.to_json) end end def send_file(status, path, label = nil, time = now, mime_type = 'image/png') unless File.file?(path) + if mime_type =~ /;base64$/ + mime_type = mime_type[0..-8] + path = Base64.decode64(path) + end extension = ".#{MIME::Types[mime_type].first.extensions.first}" temp = Tempfile.open(['file', extension]) temp.binmode - temp.write(Base64.decode64(path)) + temp.write(path) temp.rewind path = temp end - file_name = File.basename(path) - label ||= file_name - json = { level: status_to_level(status), message: label, item_id: @current_scenario.id, time: time, file: { name: "#{file_name}" }, "Content-Type": 'application/json' } - headers = {'Content-Type': 'multipart/form-data'} - payload = { :json_request_part => [json].to_json, - file_name => Faraday::UploadIO.new(path, mime_type) } - process_request("log",:post,payload, headers) + file_name = File.basename(path) + label ||= file_name + json = { level: status_to_level(status), + message: label, + item_id: @current_scenario.id, + time: time, + file: { name: file_name.to_s }, + "Content-Type": 'application/json' } + headers = {'Content-Type': 'multipart/form-data'} + payload = { :json_request_part => [json].to_json, + file_name => Faraday::UploadIO.new(path, mime_type) } + process_request('log',:post,payload, headers) end def get_item(name, parent_node) @@ -127,7 +135,7 @@ def remote_item(item_id) def item_id_of(name, parent_node) data = get_item(name, parent_node) - if data.key? 'content' + if data.key? 'content' data['content'].empty? ? nil : data['content'][0]['id'] else nil # item isn't started yet @@ -135,7 +143,7 @@ def item_id_of(name, parent_node) end def close_child_items(parent_id) - self.logger.debug "closing child items: #{parent_id} " + logger.debug "closing child items: #{parent_id} " if parent_id.nil? url = "item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" else @@ -170,20 +178,20 @@ def process_request(path, method, *options) begin response = rp_client.send(method, path, *options) rescue RestClient::Exception => e - self.logger.warn("Exception[#{e}],class:[#{e.class}],class:[#{e.class}], retry_count: [#{tries}]") - self.logger.error("TRACE[#{e.backtrace}]") + logger.warn("Exception[#{e}],class:[#{e.class}],class:[#{e.class}], retry_count: [#{tries}]") + logger.error("TRACE[#{e.backtrace}]") response = JSON.parse(e.response) m = response['message'].match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) if m parent_time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') data = JSON.parse(options[0]) - self.logger.warn("RP error : 40025, time of a child: [#{data['start_time']}], paren time: [#{(parent_time.to_f * 1000).to_i}]") + logger.warn("RP error : 40025, time of a child: [#{data['start_time']}], paren time: [#{(parent_time.to_f * 1000).to_i}]") data['start_time'] = (parent_time.to_f * 1000).to_i + 1000 options[0] = data.to_json ReportPortal.last_used_time = data['start_time'] else - self.logger.error("RestClient::Exception -> response: [#{response}]") - self.logger.error("TRACE[#{e.backtrace}]") + logger.error("RestClient::Exception -> response: [#{response}]") + logger.error("TRACE[#{e.backtrace}]") raise end @@ -192,10 +200,9 @@ def process_request(path, method, *options) JSON.parse(response.body) end - def rp_client - @connection ||= Faraday.new(url: Settings.instance.project_url) do |f| - f.headers={Authorization: "Bearer #{Settings.instance.uuid}", Accept: 'application/json','Content-type': 'application/json'} + @connection ||= Faraday.new(url: Settings.instance.project_url) do |f| + f.headers = {Authorization: "Bearer #{Settings.instance.uuid}", Accept: 'application/json','Content-type': 'application/json'} verify_ssl = Settings.instance.disable_ssl_verification f.ssl.verify = !verify_ssl unless verify_ssl.nil? f.request :multipart From f8e75c683c631e95a03bc62cc3507b790667a9b2 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 11 Jul 2019 05:26:47 -0400 Subject: [PATCH 87/98] adding gemspec dependency --- Gemfile | 2 +- lib/report_portal/cucumber/report.rb | 1 - lib/report_portal/version.rb | 2 +- reportportal.gemspec | 13 +++++++++++-- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 1d759e7..86a906a 100644 --- a/Gemfile +++ b/Gemfile @@ -9,5 +9,5 @@ gem 'logging' gem 'parallel_tests', '~> 2.15.0' gem 'rake' gem 'rspec' -gem 'rubytree', git: 'https://github.com/razboev/RubyTree' +gem 'rubytree' gem 'sys-proctable', '~> 1.1.5' diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index d4d11c6..9e6b296 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -15,7 +15,6 @@ module Cucumber # @api private class Report attr_accessor :parallel, :started_launch - @folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" def attach_to_launch? ReportPortal::Settings.instance.formatter_modes.include?('attach_to_launch') diff --git a/lib/report_portal/version.rb b/lib/report_portal/version.rb index 1194435..13ac7d5 100644 --- a/lib/report_portal/version.rb +++ b/lib/report_portal/version.rb @@ -1,3 +1,3 @@ module ReportPortal - VERSION = '0.8' + VERSION = '1.0' end diff --git a/reportportal.gemspec b/reportportal.gemspec index c5a110f..4cd0387 100644 --- a/reportportal.gemspec +++ b/reportportal.gemspec @@ -14,8 +14,17 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.3.0' s.license = 'Apache-2.0' - s.add_dependency('rest-client', '~> 2.0') - s.add_dependency('rubytree', '>=0.9.3') + s.add_runtime_dependency('faraday', '~> 0.13') + + s.add_runtime_dependency('cucumber', '~> 3.0') + s.add_runtime_dependency('parallel_tests', '~> 2.15') + s.add_runtime_dependency('rake', '~> 12.3') + s.add_runtime_dependency('rspec', '~> 3.8') + s.add_runtime_dependency('rubytree', '~> 1.0') + s.add_runtime_dependency('sys-proctable', '1.1.5') + + s.add_runtime_dependency('log4r', '~> 1.1') + s.add_runtime_dependency('logging', '~> 2.2') s.add_development_dependency('rubocop', '0.71') end From 1d37021bcd5b2fe926760fd90e6d202a6f510fe0 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 11 Jul 2019 16:48:33 -0400 Subject: [PATCH 88/98] updating readme --- README.md | 22 +++++++++++++++------- lib/report_portal/cucumber/report.rb | 6 ++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e4eca83..443e592 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,16 @@ Add `gem 'reportportal', git: 'https://github.com/reportportal/agent-ruby.git'` ```cucumber -f ReportPortal::Cucumber::Formatter -o ''``` -* With Cucumber and parallel_tests gem: - -```parallel_cucumber -o ' -f ReportPortal::Cucumber::Formatter'``` +* With Cucumber (Advanced) +```ruby +AfterConfiguration do |config| + ... + #rp_log_file = + ... + config.formats.push(["ReportPortal::Cucumber::Formatter", {}, rp_log_file]) +end +``` * With RSpec: ```rspec -f ReportPortal::RSpec::Formatter``` @@ -54,6 +60,7 @@ Supported settings: - launch_id - id of previously created launch (to be used if formatter_modes contains attach_to_launch) - file_with_launch_id - path to file with id of launch (to be used if formatter_modes contains attach_to_launch) - disable_ssl_verification - set to true to disable SSL verification on connect to ReportPortal (potential security hole!). Set `disable_ssl_verification` to `true` if you see the following error: + - launch_uuid - when formatter_mode is `attach_to_launch`, launch_uuid will be used to create uniq report group(tmp dir should be shared across all lunches) - log_level - this is log level for report_portal agent (useful for troubleshooting issued when run in parallel mode) ``` Request to https://rp.epam.com/reportportal-ws/api/v1/pass-team/launch//finish produced an exception: RestClient::SSLCertificateNotVerified: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed @@ -81,14 +88,15 @@ The following modes are supported: attach_to_launch -Add executing features/scenarios to an existing launch. -Use following options to configure that. +Add executing features/scenarios to same launch. +Use following options are available to configure that. 1. launch_id 2. file_with_launch_id - 3. rp_launch_id.tmp in `Dir.tmpdir` + 3. launch_uuid + 4. rp_launch_id_for_.lock in `Dir.tmpdir` - If above options not present client will create new launch + *** If launch is not created, cucumber process will create one. Such that user does not need to worry about creating new launch. diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 9e6b296..16dea30 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -216,10 +216,8 @@ def lock_file(file_path = nil) @logger.debug("Lock file (RReportPortal::Settings.instance.file_with_launch_id): #{file_path}") if file_path file_path ||= Dir.tmpdir + "/report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid @logger.debug("Lock file (ReportPortal::Settings.instance.launch_uuid): #{file_path}") if file_path - file_path ||= Dir.tmpdir + "/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" if @pid_of_parallel_tests - @logger.debug("Lock file (/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock): #{file_path}") if file_path - file_path ||= Dir.tmpdir + '/rp_launch_id.tmp' - @logger.debug("Lock file (/rp_launch_id.tmp): #{file_path}") if file_path + file_path ||= Dir.tmpdir + "/rp_launch_id_for_#{@pid_of_parallel_tests}.lock" if @pid_of_parallel_tests + @logger.debug("Lock file (/rp_launch_id_for_#{@pid_of_parallel_tests}.lock): #{file_path}") if file_path file_path end From 9fd2cda8d483471baceb20ce3a46cc5592f0e037 Mon Sep 17 00:00:00 2001 From: vveliev Date: Wed, 17 Jul 2019 02:39:37 -0400 Subject: [PATCH 89/98] fixing gemspec, code formatting --- .rubocop_todo.yml | 62 ---------------------------- lib/report_portal/cucumber/report.rb | 2 - lib/reportportal.rb | 22 +++++----- reportportal.gemspec | 8 +--- 4 files changed, 13 insertions(+), 81 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 96e8e09..ac8c024 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,14 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: with_first_argument, with_fixed_indentation -Layout/AlignArguments: - Exclude: - - 'lib/report_portal/rspec/formatter.rb' - # Offense count: 2 # Cop supports --auto-correct. Layout/EmptyLineAfterGuardClause: @@ -21,60 +13,6 @@ Layout/EmptyLineAfterGuardClause: - 'lib/report_portal/cucumber/json_slurper.rb' - 'tests/features/step_definitions/steps.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. -Layout/ExtraSpacing: - Exclude: - - 'lib/report_portal/logging/logging_appender.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Layout/LeadingCommentSpace: - Enabled: true - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: aligned, indented -Layout/MultilineOperationIndentation: - Exclude: - - 'lib/report_portal/cucumber/report.rb' - -# Offense count: 3 -# Cop supports --auto-correct. -Layout/SpaceAfterComma: - Exclude: - - 'lib/report_portal/cucumber/report.rb' - - 'lib/reportportal.rb' - - 'tests/formatter_spec.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space -Layout/SpaceAroundEqualsInParameterDefault: - Exclude: - - 'lib/reportportal.rb' - -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment. -Layout/SpaceAroundOperators: - Exclude: - - 'lib/report_portal/cucumber/report.rb' - - 'lib/report_portal/logging/logging_appender.rb' - - 'lib/report_portal/rspec/formatter.rb' - -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. -# SupportedStyles: space, no_space -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceInsideBlockBraces: - Exclude: - - 'tests/formatter_spec.rb' - # Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 16dea30..88b9cc3 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -1,5 +1,3 @@ -require 'cucumber/formatter/io' -require 'cucumber/formatter/hook_query_visitor' require 'tree' require 'securerandom' require 'tempfile' diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 2948010..3d796c9 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -33,21 +33,21 @@ def status_to_level(status) def start_launch(description, start_time = now) data = { name: Settings.instance.launch, start_time: start_time, tags: Settings.instance.tags, description: description, mode: Settings.instance.launch_mode } - @launch_id = process_request('launch',:post,data.to_json)['id'] + @launch_id = process_request('launch', :post, data.to_json)['id'] end def remote_launch - process_request("launch/#{@launch_id}",:get) + process_request("launch/#{@launch_id}", :get) end def update_launch(data) - process_request("launch/#{@launch_id}/update",:put, data.to_json) + process_request("launch/#{@launch_id}/update", :put, data.to_json) end def finish_launch(end_time = now) logger.debug "finish_launch: [#{end_time}]" data = { end_time: end_time } - process_request("launch/#{@launch_id}/finish",:put,data.to_json) + process_request("launch/#{@launch_id}/finish", :put, data.to_json) end def start_item(item_node) @@ -56,7 +56,7 @@ def start_item(item_node) data[:tags] = item.tags unless item.tags.empty? url = 'item' url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? - process_request(url,:post,data.to_json)['id'] + process_request(url, :post, data.to_json)['id'] end def finish_item(item, status = nil, end_time = nil, force_issue = nil) @@ -89,7 +89,7 @@ def send_log(status, message, time) @logger.debug "send_log: [#{status}],[#{message}], #{@current_scenario} " unless @current_scenario.nil? || @current_scenario.closed # it can be nil if scenario outline in expand mode is executed data = { item_id: @current_scenario.id, time: time, level: status_to_level(status), message: message.to_s } - process_request('log',:post,data.to_json) + process_request('log', :post, data.to_json) end end @@ -117,7 +117,7 @@ def send_file(status, path, label = nil, time = now, mime_type = 'image/png') headers = {'Content-Type': 'multipart/form-data'} payload = { :json_request_part => [json].to_json, file_name => Faraday::UploadIO.new(path, mime_type) } - process_request('log',:post,payload, headers) + process_request('log', :post, payload, headers) end def get_item(name, parent_node) @@ -126,11 +126,11 @@ def get_item(name, parent_node) else url = "item?filter.eq.launch=#{@launch_id}&filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" end - process_request(url,:get) + process_request(url, :get) end def remote_item(item_id) - process_request("item/#{item_id}",:get) + process_request("item/#{item_id}", :get) end def item_id_of(name, parent_node) @@ -151,7 +151,7 @@ def close_child_items(parent_id) end ids = [] loop do - response = process_request(url,:get) + response = process_request(url, :get) if response.key?('links') link = response['links'].find { |i| i['rel'] == 'next' } url = link.nil? ? nil : link['href'] @@ -202,7 +202,7 @@ def process_request(path, method, *options) def rp_client @connection ||= Faraday.new(url: Settings.instance.project_url) do |f| - f.headers = {Authorization: "Bearer #{Settings.instance.uuid}", Accept: 'application/json','Content-type': 'application/json'} + f.headers = {Authorization: "Bearer #{Settings.instance.uuid}", Accept: 'application/json', 'Content-type': 'application/json'} verify_ssl = Settings.instance.disable_ssl_verification f.ssl.verify = !verify_ssl unless verify_ssl.nil? f.request :multipart diff --git a/reportportal.gemspec b/reportportal.gemspec index 4cd0387..f7ff659 100644 --- a/reportportal.gemspec +++ b/reportportal.gemspec @@ -14,17 +14,13 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.3.0' s.license = 'Apache-2.0' - s.add_runtime_dependency('faraday', '~> 0.13') - s.add_runtime_dependency('cucumber', '~> 3.0') + s.add_runtime_dependency('faraday', '~> 0.13') + s.add_runtime_dependency('logging', '~> 2.2') s.add_runtime_dependency('parallel_tests', '~> 2.15') - s.add_runtime_dependency('rake', '~> 12.3') s.add_runtime_dependency('rspec', '~> 3.8') s.add_runtime_dependency('rubytree', '~> 1.0') s.add_runtime_dependency('sys-proctable', '1.1.5') - s.add_runtime_dependency('log4r', '~> 1.1') - s.add_runtime_dependency('logging', '~> 2.2') - s.add_development_dependency('rubocop', '0.71') end From 2c447d8affc440c482f773f13b16931385edbfcc Mon Sep 17 00:00:00 2001 From: vveliev Date: Wed, 17 Jul 2019 03:32:42 -0400 Subject: [PATCH 90/98] fixing typo, gemspec update --- README.md | 2 +- reportportal.gemspec | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 443e592..6331145 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Use following options are available to configure that. 1. launch_id 2. file_with_launch_id 3. launch_uuid - 4. rp_launch_id_for_.lock in `Dir.tmpdir` + 4. rp_launch_id_for_.lock in `Dir.tmpdir` *** If launch is not created, cucumber process will create one. Such that user does not need to worry about creating new launch. diff --git a/reportportal.gemspec b/reportportal.gemspec index f7ff659..402a329 100644 --- a/reportportal.gemspec +++ b/reportportal.gemspec @@ -14,13 +14,8 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.3.0' s.license = 'Apache-2.0' - s.add_runtime_dependency('cucumber', '~> 3.0') s.add_runtime_dependency('faraday', '~> 0.13') - s.add_runtime_dependency('logging', '~> 2.2') - s.add_runtime_dependency('parallel_tests', '~> 2.15') - s.add_runtime_dependency('rspec', '~> 3.8') s.add_runtime_dependency('rubytree', '~> 1.0') - s.add_runtime_dependency('sys-proctable', '1.1.5') s.add_development_dependency('rubocop', '0.71') end From 7be5353c31be361a2a73b93b83f8e4dffacd8c1b Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 18 Jul 2019 04:09:06 -0400 Subject: [PATCH 91/98] fixing issue with formatter when file with launch id is provided --- lib/report_portal/cucumber/report.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 88b9cc3..5a3f9ff 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -50,18 +50,18 @@ def initialize(logger) end def start_launch(desired_time, cmd_args = ARGV) - # Expected behavior that make sense: - # 1. If launch_id present attach to existing (simple use case) - # 2. If launch_id not present check if exist rp_launch_id.tmp - # 3. [ADDED] If launch_id is not present check if lock exist with launch_uuid if attach_to_launch? ReportPortal.launch_id = if ReportPortal::Settings.instance.launch_id ReportPortal::Settings.instance.launch_id else - self.started_launch = true file_path = lock_file - File.file?(file_path) ? read_lock_file(file_path) : new_launch(desired_time, cmd_args, file_path) + if File.file?(file_path) + read_lock_file(file_path) + else + self.started_launch = true + new_launch(desired_time, cmd_args, file_path) + end end @logger.info "Attaching to launch #{ReportPortal.launch_id}" else From 92af68db52d038bb6c3ddde568ce88757762dcf8 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 8 Aug 2019 21:15:38 -0400 Subject: [PATCH 92/98] fixing typos --- README.md | 2 +- lib/report_portal/patches/{fariday.rb => faraday.rb} | 0 lib/reportportal.rb | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename lib/report_portal/patches/{fariday.rb => faraday.rb} (100%) diff --git a/README.md b/README.md index 6331145..d5fe6ff 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Supported settings: - file_with_launch_id - path to file with id of launch (to be used if formatter_modes contains attach_to_launch) - disable_ssl_verification - set to true to disable SSL verification on connect to ReportPortal (potential security hole!). Set `disable_ssl_verification` to `true` if you see the following error: - launch_uuid - when formatter_mode is `attach_to_launch`, launch_uuid will be used to create uniq report group(tmp dir should be shared across all lunches) - - log_level - this is log level for report_portal agent (useful for troubleshooting issued when run in parallel mode) + - log_level - this is log level for report_portal agent (useful for troubleshooting issues when run in parallel mode) ``` Request to https://rp.epam.com/reportportal-ws/api/v1/pass-team/launch//finish produced an exception: RestClient::SSLCertificateNotVerified: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed ``` diff --git a/lib/report_portal/patches/fariday.rb b/lib/report_portal/patches/faraday.rb similarity index 100% rename from lib/report_portal/patches/fariday.rb rename to lib/report_portal/patches/faraday.rb diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 3d796c9..419d5b8 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -5,7 +5,7 @@ require 'tempfile' require_relative 'report_portal/settings' -require_relative 'report_portal/patches/fariday' +require_relative 'report_portal/patches/faraday' module ReportPortal TestItem = Struct.new(:name, :type, :id, :start_time, :description, :closed, :tags) From 72d496afda973849c45985ea44f4783c403d7553 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 8 Aug 2019 21:27:38 -0400 Subject: [PATCH 93/98] fixing typos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5fe6ff..986125c 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Supported settings: - launch_id - id of previously created launch (to be used if formatter_modes contains attach_to_launch) - file_with_launch_id - path to file with id of launch (to be used if formatter_modes contains attach_to_launch) - disable_ssl_verification - set to true to disable SSL verification on connect to ReportPortal (potential security hole!). Set `disable_ssl_verification` to `true` if you see the following error: - - launch_uuid - when formatter_mode is `attach_to_launch`, launch_uuid will be used to create uniq report group(tmp dir should be shared across all lunches) + - launch_uuid - when formatter_modes contains `attach_to_launch`, launch_uuid will be used to create uniq report group (tmp dir should be shared across all launches) - log_level - this is log level for report_portal agent (useful for troubleshooting issues when run in parallel mode) ``` Request to https://rp.epam.com/reportportal-ws/api/v1/pass-team/launch//finish produced an exception: RestClient::SSLCertificateNotVerified: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed From efc07c0d0b5cfe8db93a7387a0a8778f45e3e776 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 8 Aug 2019 22:38:32 -0400 Subject: [PATCH 94/98] fixing typos --- lib/reportportal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 419d5b8..99994dc 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -77,7 +77,7 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) rescue RestClient::Exception => e response = JSON.parse(e.response) - raise e unless response['error_code'] == 40_018 + raise e unless response['error_code'] == 40018 end item.closed = true end From d657458d63946a1ea3ff2936debf0476a13a2a01 Mon Sep 17 00:00:00 2001 From: vveliev-tc <40637521+vveliev-tc@users.noreply.github.com> Date: Thu, 8 Aug 2019 22:40:14 -0400 Subject: [PATCH 95/98] Feature/cucumber parrallel fariday (#24) * Fix sending plain text to attachment * fixing layout errors Fix sending plain text to attachment removing trailing whitespace * adding gemspec dependency * updating readme * fixing gemspec, code formatting * fixing typo, gemspec update * fixing issue with formatter when file with launch id is provided * fixing typos * fixing typos * fixing typos --- .rubocop_todo.yml | 81 +++---------------------- Gemfile | 4 +- README.md | 24 +++++--- lib/report_portal/cucumber/formatter.rb | 2 +- lib/report_portal/cucumber/report.rb | 52 +++++++--------- lib/report_portal/patches/faraday.rb | 25 ++++++++ lib/report_portal/patches/fariday.rb | 17 ------ lib/report_portal/version.rb | 2 +- lib/reportportal.rb | 73 ++++++++++++---------- reportportal.gemspec | 4 +- 10 files changed, 115 insertions(+), 169 deletions(-) create mode 100644 lib/report_portal/patches/faraday.rb delete mode 100644 lib/report_portal/patches/fariday.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 61057f4..ac8c024 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,19 +1,11 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-05-31 14:29:36 +0200 using RuboCop version 0.71.0. +# on 2019-07-01 15:09:18 +0200 using RuboCop version 0.69.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: with_first_argument, with_fixed_indentation -Layout/AlignArguments: - Exclude: - - 'lib/report_portal/rspec/formatter.rb' - # Offense count: 2 # Cop supports --auto-correct. Layout/EmptyLineAfterGuardClause: @@ -21,60 +13,6 @@ Layout/EmptyLineAfterGuardClause: - 'lib/report_portal/cucumber/json_slurper.rb' - 'tests/features/step_definitions/steps.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. -Layout/ExtraSpacing: - Exclude: - - 'lib/report_portal/logging/logging_appender.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Layout/LeadingCommentSpace: - Enabled: true - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: aligned, indented -Layout/MultilineOperationIndentation: - Exclude: - - 'lib/report_portal/cucumber/report.rb' - -# Offense count: 3 -# Cop supports --auto-correct. -Layout/SpaceAfterComma: - Exclude: - - 'lib/report_portal/cucumber/report.rb' - - 'lib/reportportal.rb' - - 'tests/formatter_spec.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space -Layout/SpaceAroundEqualsInParameterDefault: - Exclude: - - 'lib/reportportal.rb' - -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment. -Layout/SpaceAroundOperators: - Exclude: - - 'lib/report_portal/cucumber/report.rb' - - 'lib/report_portal/logging/logging_appender.rb' - - 'lib/report_portal/rspec/formatter.rb' - -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. -# SupportedStyles: space, no_space -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceInsideBlockBraces: - Exclude: - - 'tests/formatter_spec.rb' - # Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. @@ -85,12 +23,6 @@ Layout/SpaceInsideHashLiteralBraces: - 'lib/reportportal.rb' - 'tests/formatter_spec.rb' -# Offense count: 100 -# Cop supports --auto-correct. -# Configuration parameters: AllowInHeredoc. -Layout/TrailingWhitespace: - Enabled: false - # Offense count: 1 Lint/ParenthesesAsGroupedExpression: Exclude: @@ -120,13 +52,13 @@ Metrics/BlockLength: # Offense count: 2 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 250 + Max: 350 # Offense count: 3 Metrics/CyclomaticComplexity: Max: 13 -# Offense count: 21 +# Offense count: 19 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: Max: 79 @@ -134,7 +66,8 @@ Metrics/MethodLength: # Offense count: 1 # Configuration parameters: CountComments. Metrics/ModuleLength: - Max: 158 + Max: 250 + # Offense count: 5 Metrics/PerceivedComplexity: @@ -336,7 +269,7 @@ Style/StringLiterals: - 'lib/report_portal/cucumber/report.rb' - 'lib/report_portal/tasks.rb' -# Offense count: 5 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: MinSize. # SupportedStyles: percent, brackets @@ -360,7 +293,7 @@ Style/TrailingCommaInHashLiteral: Exclude: - 'lib/report_portal/settings.rb' -# Offense count: 102 +# Offense count: 104 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https diff --git a/Gemfile b/Gemfile index b78c327..86a906a 100644 --- a/Gemfile +++ b/Gemfile @@ -2,12 +2,12 @@ source 'https://rubygems.org' gemspec -gem 'faraday' gem 'cucumber', '~> 3' +gem 'faraday' gem 'log4r' gem 'logging' gem 'parallel_tests', '~> 2.15.0' gem 'rake' gem 'rspec' -gem 'rubytree', git: 'https://github.com/razboev/RubyTree' +gem 'rubytree' gem 'sys-proctable', '~> 1.1.5' diff --git a/README.md b/README.md index e4eca83..986125c 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,16 @@ Add `gem 'reportportal', git: 'https://github.com/reportportal/agent-ruby.git'` ```cucumber -f ReportPortal::Cucumber::Formatter -o ''``` -* With Cucumber and parallel_tests gem: - -```parallel_cucumber -o ' -f ReportPortal::Cucumber::Formatter'``` +* With Cucumber (Advanced) +```ruby +AfterConfiguration do |config| + ... + #rp_log_file = + ... + config.formats.push(["ReportPortal::Cucumber::Formatter", {}, rp_log_file]) +end +``` * With RSpec: ```rspec -f ReportPortal::RSpec::Formatter``` @@ -54,7 +60,8 @@ Supported settings: - launch_id - id of previously created launch (to be used if formatter_modes contains attach_to_launch) - file_with_launch_id - path to file with id of launch (to be used if formatter_modes contains attach_to_launch) - disable_ssl_verification - set to true to disable SSL verification on connect to ReportPortal (potential security hole!). Set `disable_ssl_verification` to `true` if you see the following error: - - log_level - this is log level for report_portal agent (useful for troubleshooting issued when run in parallel mode) + - launch_uuid - when formatter_modes contains `attach_to_launch`, launch_uuid will be used to create uniq report group (tmp dir should be shared across all launches) + - log_level - this is log level for report_portal agent (useful for troubleshooting issues when run in parallel mode) ``` Request to https://rp.epam.com/reportportal-ws/api/v1/pass-team/launch//finish produced an exception: RestClient::SSLCertificateNotVerified: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed ``` @@ -81,14 +88,15 @@ The following modes are supported: attach_to_launch -Add executing features/scenarios to an existing launch. -Use following options to configure that. +Add executing features/scenarios to same launch. +Use following options are available to configure that. 1. launch_id 2. file_with_launch_id - 3. rp_launch_id.tmp in `Dir.tmpdir` + 3. launch_uuid + 4. rp_launch_id_for_.lock in `Dir.tmpdir` - If above options not present client will create new launch + *** If launch is not created, cucumber process will create one. Such that user does not need to worry about creating new launch. diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index d108b4e..0afb37c 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -7,7 +7,7 @@ module Cucumber class Formatter # @api private def initialize(config) - @logger ||= Logger.new(config.out_stream) + @logger = Logger.new(config.out_stream) @logger.level = ReportPortal::Settings.instance.log_level || :warn setup_message_processing diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 98d3965..5a3f9ff 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -1,5 +1,3 @@ -require 'cucumber/formatter/io' -require 'cucumber/formatter/hook_query_visitor' require 'tree' require 'securerandom' require 'tempfile' @@ -15,7 +13,6 @@ module Cucumber # @api private class Report attr_accessor :parallel, :started_launch - @folder_creation_tracking_file = Pathname(Dir.tmpdir) + "folder_creation_tracking.lck" def attach_to_launch? ReportPortal::Settings.instance.formatter_modes.include?('attach_to_launch') @@ -41,6 +38,7 @@ def initialize(logger) if monotonic_time - start_time > wait_time_for_launch_create raise "File with launch ID wasn't created after waiting #{wait_time_for_launch_create} seconds" end + @logger.debug "File with launch ID wasn't created after waiting #{monotonic_time - start_time} seconds" sleep 0.5 @@ -51,20 +49,19 @@ def initialize(logger) end end - def start_launch(desired_time, cmd_args = ARGV) - # Expected behavior that make sense: - # 1. If launch_id present attach to existing (simple use case) - # 2. If launch_id not present check if exist rp_launch_id.tmp - # 3. [ADDED] If launch_id is not present check if lock exist with launch_uuid if attach_to_launch? ReportPortal.launch_id = if ReportPortal::Settings.instance.launch_id ReportPortal::Settings.instance.launch_id else - self.started_launch = true file_path = lock_file - File.file?(file_path) ? read_lock_file(file_path) : new_launch(desired_time, cmd_args, file_path) + if File.file?(file_path) + read_lock_file(file_path) + else + self.started_launch = true + new_launch(desired_time, cmd_args, file_path) + end end @logger.info "Attaching to launch #{ReportPortal.launch_id}" else @@ -217,10 +214,8 @@ def lock_file(file_path = nil) @logger.debug("Lock file (RReportPortal::Settings.instance.file_with_launch_id): #{file_path}") if file_path file_path ||= Dir.tmpdir + "/report_portal_#{ReportPortal::Settings.instance.launch_uuid}.lock" if ReportPortal::Settings.instance.launch_uuid @logger.debug("Lock file (ReportPortal::Settings.instance.launch_uuid): #{file_path}") if file_path - file_path ||= Dir.tmpdir + "/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock" if @pid_of_parallel_tests - @logger.debug("Lock file (/parallel_launch_id_for_#{@pid_of_parallel_tests}.lock): #{file_path}") if file_path - file_path ||= Dir.tmpdir + '/rp_launch_id.tmp' - @logger.debug("Lock file (/rp_launch_id.tmp): #{file_path}") if file_path + file_path ||= Dir.tmpdir + "/rp_launch_id_for_#{@pid_of_parallel_tests}.lock" if @pid_of_parallel_tests + @logger.debug("Lock file (/rp_launch_id_for_#{@pid_of_parallel_tests}.lock): #{file_path}") if file_path file_path end @@ -233,16 +228,17 @@ def set_parallel_tests_vars runner_process ||= get_cucumber_test_process(process_list) @logger.debug("Cucumber runner pid: #{runner_process.pid}") if runner_process raise 'Failed to find any cucumber related test process' if runner_process.nil? + @pid_of_parallel_tests = runner_process.pid end def get_parallel_test_process(process_list) process_list.each do |process| - if process.cmdline.match(%r{bin(?:\/|\\)parallel_(?:cucumber|test)(.+)}) - @parallel = true - @logger.debug("get_parallel_test_process: #{process.cmdline}") - return process - end + next unless process.cmdline.match(%r{bin(?:\/|\\)parallel_(?:cucumber|test)(.+)}) + + @parallel = true + @logger.debug("get_parallel_test_process: #{process.cmdline}") + return process end nil end @@ -348,19 +344,13 @@ def close_all_children_of(root_node) begin item = ReportPortal.remote_item(node.content[:id]) @logger.debug("started_launch?: [#{started_launch}], item details: [#{item}]") - if started_launch - if item.key?('end_time') - @logger.warn("Main process: item already closed skipping.") - else - ReportPortal.close_child_items(node.content[:id]) - ReportPortal.finish_item(node.content) - end + if item.key?('end_time') + started_launch ? @logger.warn("Main process: item already closed skipping.") : ReportPortal.finish_item(node.content) + elsif started_launch + ReportPortal.close_child_items(node.content[:id]) + ReportPortal.finish_item(node.content) else - if item.key?('end_time') - ReportPortal.finish_item(node.content) - else - @logger.warn("Child process: item in use cannot close it. [#{item}]") - end + @logger.warn("Child process: item in use cannot close it. [#{item}]") end end end diff --git a/lib/report_portal/patches/faraday.rb b/lib/report_portal/patches/faraday.rb new file mode 100644 index 0000000..9f92d11 --- /dev/null +++ b/lib/report_portal/patches/faraday.rb @@ -0,0 +1,25 @@ +module Faraday + class Requests + # Middleware for supporting multi-part requests. + class Multipart + def create_multipart(env, params) + boundary = env.request.boundary + parts = process_params(params) do |key, value| + if begin + JSON.parse(value) + rescue StandardError + false + end + Faraday::Parts::Part.new(boundary, key, value, 'Content-Type' => 'application/json') + else + Faraday::Parts::Part.new(boundary, key, value) + end + end + parts << Faraday::Parts::EpiloguePart.new(boundary) + body = Faraday::CompositeReadIO.new(parts) + env.request_headers[Faraday::Env::ContentLength] = body.length.to_s + body + end + end + end +end diff --git a/lib/report_portal/patches/fariday.rb b/lib/report_portal/patches/fariday.rb deleted file mode 100644 index 4a5a67d..0000000 --- a/lib/report_portal/patches/fariday.rb +++ /dev/null @@ -1,17 +0,0 @@ -class Faraday::Request::Multipart - def create_multipart(env, params) - boundary = env.request.boundary - parts = process_params(params) do |key, value| - if (JSON.parse(value) rescue false) - Faraday::Parts::Part.new(boundary, key, value, 'Content-Type' => 'application/json') - else - Faraday::Parts::Part.new(boundary, key, value) - end - end - parts << Faraday::Parts::EpiloguePart.new(boundary) - - body = Faraday::CompositeReadIO.new(parts) - env.request_headers[Faraday::Env::ContentLength] = body.length.to_s - return body - end -end \ No newline at end of file diff --git a/lib/report_portal/version.rb b/lib/report_portal/version.rb index 1194435..13ac7d5 100644 --- a/lib/report_portal/version.rb +++ b/lib/report_portal/version.rb @@ -1,3 +1,3 @@ module ReportPortal - VERSION = '0.8' + VERSION = '1.0' end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 1af33c1..99994dc 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -5,7 +5,7 @@ require 'tempfile' require_relative 'report_portal/settings' -require_relative 'report_portal/patches/fariday' +require_relative 'report_portal/patches/faraday' module ReportPortal TestItem = Struct.new(:name, :type, :id, :start_time, :description, :closed, :tags) @@ -33,21 +33,21 @@ def status_to_level(status) def start_launch(description, start_time = now) data = { name: Settings.instance.launch, start_time: start_time, tags: Settings.instance.tags, description: description, mode: Settings.instance.launch_mode } - @launch_id = process_request('launch',:post,data.to_json)['id'] + @launch_id = process_request('launch', :post, data.to_json)['id'] end def remote_launch - process_request("launch/#{@launch_id}",:get) + process_request("launch/#{@launch_id}", :get) end def update_launch(data) - process_request("launch/#{@launch_id}/update",:put, data.to_json) + process_request("launch/#{@launch_id}/update", :put, data.to_json) end def finish_launch(end_time = now) - self.logger.debug "finish_launch: [#{end_time}]" + logger.debug "finish_launch: [#{end_time}]" data = { end_time: end_time } - process_request("launch/#{@launch_id}/finish",:put,data.to_json) + process_request("launch/#{@launch_id}/finish", :put, data.to_json) end def start_item(item_node) @@ -56,13 +56,12 @@ def start_item(item_node) data[:tags] = item.tags unless item.tags.empty? url = 'item' url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? - process_request(url,:post,data.to_json)['id'] + process_request(url, :post, data.to_json)['id'] end def finish_item(item, status = nil, end_time = nil, force_issue = nil) - if item.nil? || item.id.nil? || item.closed - self.logger.debug "finish_item: Item details are missing or already closed" + logger.debug 'finish_item: Item details are missing or already closed' else data = { end_time: end_time.nil? ? now : end_time } data[:status] = status unless status.nil? @@ -71,10 +70,10 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) elsif status == :skipped data[:issue] = { issue_type: 'NOT_ISSUE' } end - self.logger.debug "finish_item:id[#{item}], data: #{data} " + logger.debug "finish_item:id[#{item}], data: #{data} " begin response = process_request("item/#{item.id}", :put, data.to_json) - self.logger.debug "finish_item: response [#{response}] " + logger.debug "finish_item: response [#{response}] " rescue RestClient::Exception => e response = JSON.parse(e.response) @@ -90,26 +89,35 @@ def send_log(status, message, time) @logger.debug "send_log: [#{status}],[#{message}], #{@current_scenario} " unless @current_scenario.nil? || @current_scenario.closed # it can be nil if scenario outline in expand mode is executed data = { item_id: @current_scenario.id, time: time, level: status_to_level(status), message: message.to_s } - process_request("log",:post,data.to_json) + process_request('log', :post, data.to_json) end end def send_file(status, path, label = nil, time = now, mime_type = 'image/png') unless File.file?(path) + if mime_type =~ /;base64$/ + mime_type = mime_type[0..-8] + path = Base64.decode64(path) + end extension = ".#{MIME::Types[mime_type].first.extensions.first}" temp = Tempfile.open(['file', extension]) temp.binmode - temp.write(Base64.decode64(path)) + temp.write(path) temp.rewind path = temp end - file_name = File.basename(path) - label ||= file_name - json = { level: status_to_level(status), message: label, item_id: @current_scenario.id, time: time, file: { name: "#{file_name}" }, "Content-Type": 'application/json' } - headers = {'Content-Type': 'multipart/form-data'} - payload = { :json_request_part => [json].to_json, - file_name => Faraday::UploadIO.new(path, mime_type) } - process_request("log",:post,payload, headers) + file_name = File.basename(path) + label ||= file_name + json = { level: status_to_level(status), + message: label, + item_id: @current_scenario.id, + time: time, + file: { name: file_name.to_s }, + "Content-Type": 'application/json' } + headers = {'Content-Type': 'multipart/form-data'} + payload = { :json_request_part => [json].to_json, + file_name => Faraday::UploadIO.new(path, mime_type) } + process_request('log', :post, payload, headers) end def get_item(name, parent_node) @@ -118,16 +126,16 @@ def get_item(name, parent_node) else url = "item?filter.eq.launch=#{@launch_id}&filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" end - process_request(url,:get) + process_request(url, :get) end def remote_item(item_id) - process_request("item/#{item_id}",:get) + process_request("item/#{item_id}", :get) end def item_id_of(name, parent_node) data = get_item(name, parent_node) - if data.key? 'content' + if data.key? 'content' data['content'].empty? ? nil : data['content'][0]['id'] else nil # item isn't started yet @@ -135,7 +143,7 @@ def item_id_of(name, parent_node) end def close_child_items(parent_id) - self.logger.debug "closing child items: #{parent_id} " + logger.debug "closing child items: #{parent_id} " if parent_id.nil? url = "item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" else @@ -143,7 +151,7 @@ def close_child_items(parent_id) end ids = [] loop do - response = process_request(url,:get) + response = process_request(url, :get) if response.key?('links') link = response['links'].find { |i| i['rel'] == 'next' } url = link.nil? ? nil : link['href'] @@ -170,20 +178,20 @@ def process_request(path, method, *options) begin response = rp_client.send(method, path, *options) rescue RestClient::Exception => e - self.logger.warn("Exception[#{e}],class:[#{e.class}],class:[#{e.class}], retry_count: [#{tries}]") - self.logger.error("TRACE[#{e.backtrace}]") + logger.warn("Exception[#{e}],class:[#{e.class}],class:[#{e.class}], retry_count: [#{tries}]") + logger.error("TRACE[#{e.backtrace}]") response = JSON.parse(e.response) m = response['message'].match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) if m parent_time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') data = JSON.parse(options[0]) - self.logger.warn("RP error : 40025, time of a child: [#{data['start_time']}], paren time: [#{(parent_time.to_f * 1000).to_i}]") + logger.warn("RP error : 40025, time of a child: [#{data['start_time']}], paren time: [#{(parent_time.to_f * 1000).to_i}]") data['start_time'] = (parent_time.to_f * 1000).to_i + 1000 options[0] = data.to_json ReportPortal.last_used_time = data['start_time'] else - self.logger.error("RestClient::Exception -> response: [#{response}]") - self.logger.error("TRACE[#{e.backtrace}]") + logger.error("RestClient::Exception -> response: [#{response}]") + logger.error("TRACE[#{e.backtrace}]") raise end @@ -192,10 +200,9 @@ def process_request(path, method, *options) JSON.parse(response.body) end - def rp_client - @connection ||= Faraday.new(url: Settings.instance.project_url) do |f| - f.headers={Authorization: "Bearer #{Settings.instance.uuid}", Accept: 'application/json','Content-type': 'application/json'} + @connection ||= Faraday.new(url: Settings.instance.project_url) do |f| + f.headers = {Authorization: "Bearer #{Settings.instance.uuid}", Accept: 'application/json', 'Content-type': 'application/json'} verify_ssl = Settings.instance.disable_ssl_verification f.ssl.verify = !verify_ssl unless verify_ssl.nil? f.request :multipart diff --git a/reportportal.gemspec b/reportportal.gemspec index c5a110f..402a329 100644 --- a/reportportal.gemspec +++ b/reportportal.gemspec @@ -14,8 +14,8 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.3.0' s.license = 'Apache-2.0' - s.add_dependency('rest-client', '~> 2.0') - s.add_dependency('rubytree', '>=0.9.3') + s.add_runtime_dependency('faraday', '~> 0.13') + s.add_runtime_dependency('rubytree', '~> 1.0') s.add_development_dependency('rubocop', '0.71') end From ead9cc13933670b6ce540ab235017a9a1d6b42f1 Mon Sep 17 00:00:00 2001 From: vveliev Date: Thu, 15 Aug 2019 22:43:18 -0400 Subject: [PATCH 96/98] splitting client to stand alone class, need to add error handling --- lib/report_portal/client.rb | 66 +++++++++++++++++++++ lib/report_portal/cucumber/formatter.rb | 4 ++ lib/report_portal/cucumber/report.rb | 2 +- lib/report_portal/patches/faraday.rb | 1 + lib/reportportal.rb | 76 +++++++------------------ 5 files changed, 91 insertions(+), 58 deletions(-) create mode 100644 lib/report_portal/client.rb diff --git a/lib/report_portal/client.rb b/lib/report_portal/client.rb new file mode 100644 index 0000000..aae4d69 --- /dev/null +++ b/lib/report_portal/client.rb @@ -0,0 +1,66 @@ +require 'faraday' +require_relative 'patches/faraday' + +module ReportPortal + class Client + attr_accessor :logger + + def initialize(logger) + @logger = logger + end + + def process_request(path, method, *options) + tries = 5 + begin + response = rp_client.send(method, path, *options) + rescue => e + logger.warn("Exception[#{e}],class:[#{e.class}],class:[#{e.class}], retry_count: [#{tries}]") + logger.error("TRACE[#{e.backtrace}]") + response = JSON.parse(e.response) + m = response['message'].match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) + debugger + if m + parent_time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') + data = JSON.parse(options[0]) + logger.warn("RP error : 40025, time of a child: [#{data['start_time']}], paren time: [#{(parent_time.to_f * 1000).to_i}]") + data['start_time'] = (parent_time.to_f * 1000).to_i + 1000 + options[0] = data.to_json + ReportPortal.last_used_time = data['start_time'] + else + logger.error("RestClient::Exception -> response: [#{response}]") + logger.error("TRACE[#{e.backtrace}]") + raise + end + + retry unless (tries -= 1).zero? + end + logger.error("Response:[#{response.status}] [#{response.body}], r") unless response.status == 201 + JSON.parse(response.body) + + end + + def rp_client + @connection ||= Faraday.new(url: Settings.instance.project_url) do |f| + f.headers = {Authorization: "Bearer #{Settings.instance.uuid}", Accept: 'application/json', 'Content-type': 'application/json'} + verify_ssl = Settings.instance.disable_ssl_verification + f.ssl.verify = !verify_ssl unless verify_ssl.nil? + f.request :multipart + f.request :url_encoded + f.adapter :net_http + end + + @connection + end + end + module ERROR + class CustomErrors < Faraday::Response::Middleware + def on_complete(env) + debugger + case env[:status] + when 404 + raise RuntimeError, 'Custom 404 response' + end + end + end + end +end \ No newline at end of file diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index 0afb37c..ea6304b 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -65,6 +65,10 @@ def process_message(report_method_name, *method_args) def use_same_thread_for_reporting? ReportPortal::Settings.instance.formatter_modes.include?('use_same_thread_for_reporting') end + + def client + @client ||= ReportPortal::Client.new(@logger) + end end end end diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 5a3f9ff..16642bc 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -21,7 +21,7 @@ def attach_to_launch? def initialize(logger) @logger = logger ReportPortal.last_used_time = 0 - ReportPortal.logger = logger + ReportPortal.initialize(logger) @root_node = Tree::TreeNode.new('') @parent_item_node = @root_node diff --git a/lib/report_portal/patches/faraday.rb b/lib/report_portal/patches/faraday.rb index 9f92d11..23d773a 100644 --- a/lib/report_portal/patches/faraday.rb +++ b/lib/report_portal/patches/faraday.rb @@ -18,6 +18,7 @@ def create_multipart(env, params) parts << Faraday::Parts::EpiloguePart.new(boundary) body = Faraday::CompositeReadIO.new(parts) env.request_headers[Faraday::Env::ContentLength] = body.length.to_s + debugger body end end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 99994dc..64565c2 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -1,18 +1,21 @@ require 'json' -require 'faraday' require 'uri' require 'pathname' require 'tempfile' require_relative 'report_portal/settings' -require_relative 'report_portal/patches/faraday' +require_relative 'report_portal/client' module ReportPortal TestItem = Struct.new(:name, :type, :id, :start_time, :description, :closed, :tags) LOG_LEVELS = { error: 'ERROR', warn: 'WARN', info: 'INFO', debug: 'DEBUG', trace: 'TRACE', fatal: 'FATAL', unknown: 'UNKNOWN' } class << self attr_accessor :launch_id, :current_scenario, :last_used_time, :logger - @verbose = false + + def initialize(logger) # tried 1. initialize, 2. new, 3. self.initialize, 4. self.new + @logger = logger + @client = ReportPortal::Client.new(logger) + end def now (Time.now.to_f * 1000).to_i @@ -33,21 +36,21 @@ def status_to_level(status) def start_launch(description, start_time = now) data = { name: Settings.instance.launch, start_time: start_time, tags: Settings.instance.tags, description: description, mode: Settings.instance.launch_mode } - @launch_id = process_request('launch', :post, data.to_json)['id'] + @launch_id = @client.process_request('launch', :post, data.to_json)['id'] end def remote_launch - process_request("launch/#{@launch_id}", :get) + @client.process_request("launch/#{@launch_id}", :get) end def update_launch(data) - process_request("launch/#{@launch_id}/update", :put, data.to_json) + @client.process_request("launch/#{@launch_id}/update", :put, data.to_json) end def finish_launch(end_time = now) logger.debug "finish_launch: [#{end_time}]" data = { end_time: end_time } - process_request("launch/#{@launch_id}/finish", :put, data.to_json) + @client.process_request("launch/#{@launch_id}/finish", :put, data.to_json) end def start_item(item_node) @@ -56,7 +59,7 @@ def start_item(item_node) data[:tags] = item.tags unless item.tags.empty? url = 'item' url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? - process_request(url, :post, data.to_json)['id'] + @client.process_request(url, :post, data.to_json)['id'] end def finish_item(item, status = nil, end_time = nil, force_issue = nil) @@ -72,7 +75,7 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) end logger.debug "finish_item:id[#{item}], data: #{data} " begin - response = process_request("item/#{item.id}", :put, data.to_json) + response = @client.process_request("item/#{item.id}", :put, data.to_json) logger.debug "finish_item: response [#{response}] " rescue RestClient::Exception => e response = JSON.parse(e.response) @@ -89,7 +92,7 @@ def send_log(status, message, time) @logger.debug "send_log: [#{status}],[#{message}], #{@current_scenario} " unless @current_scenario.nil? || @current_scenario.closed # it can be nil if scenario outline in expand mode is executed data = { item_id: @current_scenario.id, time: time, level: status_to_level(status), message: message.to_s } - process_request('log', :post, data.to_json) + @client.process_request('log', :post, data.to_json) end end @@ -115,9 +118,10 @@ def send_file(status, path, label = nil, time = now, mime_type = 'image/png') file: { name: file_name.to_s }, "Content-Type": 'application/json' } headers = {'Content-Type': 'multipart/form-data'} - payload = { :json_request_part => [json].to_json, + payload = { 'json_request_part': [json].to_json, file_name => Faraday::UploadIO.new(path, mime_type) } - process_request('log', :post, payload, headers) + debugger + @client.process_request('log', :post, payload, headers) end def get_item(name, parent_node) @@ -126,11 +130,11 @@ def get_item(name, parent_node) else url = "item?filter.eq.launch=#{@launch_id}&filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" end - process_request(url, :get) + @client.process_request(url, :get) end def remote_item(item_id) - process_request("item/#{item_id}", :get) + @client.process_request("item/#{item_id}", :get) end def item_id_of(name, parent_node) @@ -151,7 +155,7 @@ def close_child_items(parent_id) end ids = [] loop do - response = process_request(url, :get) + response = @client.process_request(url, :get) if response.key?('links') link = response['links'].find { |i| i['rel'] == 'next' } url = link.nil? ? nil : link['href'] @@ -170,47 +174,5 @@ def close_child_items(parent_id) finish_item(TestItem.new(nil, nil, id, nil, nil, nil, nil)) end end - - private - - def process_request(path, method, *options) - tries = 5 - begin - response = rp_client.send(method, path, *options) - rescue RestClient::Exception => e - logger.warn("Exception[#{e}],class:[#{e.class}],class:[#{e.class}], retry_count: [#{tries}]") - logger.error("TRACE[#{e.backtrace}]") - response = JSON.parse(e.response) - m = response['message'].match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) - if m - parent_time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') - data = JSON.parse(options[0]) - logger.warn("RP error : 40025, time of a child: [#{data['start_time']}], paren time: [#{(parent_time.to_f * 1000).to_i}]") - data['start_time'] = (parent_time.to_f * 1000).to_i + 1000 - options[0] = data.to_json - ReportPortal.last_used_time = data['start_time'] - else - logger.error("RestClient::Exception -> response: [#{response}]") - logger.error("TRACE[#{e.backtrace}]") - raise - end - - retry unless (tries -= 1).zero? - end - JSON.parse(response.body) - end - - def rp_client - @connection ||= Faraday.new(url: Settings.instance.project_url) do |f| - f.headers = {Authorization: "Bearer #{Settings.instance.uuid}", Accept: 'application/json', 'Content-type': 'application/json'} - verify_ssl = Settings.instance.disable_ssl_verification - f.ssl.verify = !verify_ssl unless verify_ssl.nil? - f.request :multipart - f.request :url_encoded - f.adapter :net_http - end - - @connection - end end end From 8414edcc1a2c9acb87c3c244094b6ed1aa239a70 Mon Sep 17 00:00:00 2001 From: vveliev Date: Fri, 16 Aug 2019 05:27:58 -0400 Subject: [PATCH 97/98] fixing patch typo, making sure attachments work --- lib/report_portal/client.rb | 42 +++++++++---------------- lib/report_portal/cucumber/formatter.rb | 4 --- lib/report_portal/patches/faraday.rb | 7 ++--- lib/reportportal.rb | 11 +++---- reportportal.gemspec | 5 ++- 5 files changed, 26 insertions(+), 43 deletions(-) diff --git a/lib/report_portal/client.rb b/lib/report_portal/client.rb index aae4d69..138eca9 100644 --- a/lib/report_portal/client.rb +++ b/lib/report_portal/client.rb @@ -1,7 +1,5 @@ -require 'faraday' -require_relative 'patches/faraday' - module ReportPortal + # @api private class Client attr_accessor :logger @@ -10,15 +8,19 @@ def initialize(logger) end def process_request(path, method, *options) - tries = 5 + tries = 2 begin response = rp_client.send(method, path, *options) - rescue => e - logger.warn("Exception[#{e}],class:[#{e.class}],class:[#{e.class}], retry_count: [#{tries}]") + rescue Faraday::ClientError => e logger.error("TRACE[#{e.backtrace}]") - response = JSON.parse(e.response) + response = JSON.parse(e.response[:body]) + logger.warn("Exception[#{e}], response:[#{response}]], retry_count: [#{tries}]") m = response['message'].match(%r{Start time of child \['(.+)'\] item should be same or later than start time \['(.+)'\] of the parent item\/launch '.+'}) - debugger + case response['error_code'] + when 4001 + return + end + if m parent_time = Time.strptime(m[2], '%a %b %d %H:%M:%S %z %Y') data = JSON.parse(options[0]) @@ -26,41 +28,25 @@ def process_request(path, method, *options) data['start_time'] = (parent_time.to_f * 1000).to_i + 1000 options[0] = data.to_json ReportPortal.last_used_time = data['start_time'] - else - logger.error("RestClient::Exception -> response: [#{response}]") - logger.error("TRACE[#{e.backtrace}]") - raise end retry unless (tries -= 1).zero? end - logger.error("Response:[#{response.status}] [#{response.body}], r") unless response.status == 201 JSON.parse(response.body) - end def rp_client @connection ||= Faraday.new(url: Settings.instance.project_url) do |f| - f.headers = {Authorization: "Bearer #{Settings.instance.uuid}", Accept: 'application/json', 'Content-type': 'application/json'} + f.headers = { Authorization: "Bearer #{Settings.instance.uuid}", Accept: 'application/json', 'Content-type': 'application/json' } verify_ssl = Settings.instance.disable_ssl_verification f.ssl.verify = !verify_ssl unless verify_ssl.nil? f.request :multipart f.request :url_encoded - f.adapter :net_http + f.response :raise_error + f.adapter :net_http_persistent end @connection end end - module ERROR - class CustomErrors < Faraday::Response::Middleware - def on_complete(env) - debugger - case env[:status] - when 404 - raise RuntimeError, 'Custom 404 response' - end - end - end - end -end \ No newline at end of file +end diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index ea6304b..0afb37c 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -65,10 +65,6 @@ def process_message(report_method_name, *method_args) def use_same_thread_for_reporting? ReportPortal::Settings.instance.formatter_modes.include?('use_same_thread_for_reporting') end - - def client - @client ||= ReportPortal::Client.new(@logger) - end end end end diff --git a/lib/report_portal/patches/faraday.rb b/lib/report_portal/patches/faraday.rb index 23d773a..2ce6c86 100644 --- a/lib/report_portal/patches/faraday.rb +++ b/lib/report_portal/patches/faraday.rb @@ -1,15 +1,15 @@ module Faraday - class Requests + class Request # Middleware for supporting multi-part requests. class Multipart def create_multipart(env, params) boundary = env.request.boundary parts = process_params(params) do |key, value| if begin - JSON.parse(value) + JSON.parse(value) rescue StandardError false - end + end Faraday::Parts::Part.new(boundary, key, value, 'Content-Type' => 'application/json') else Faraday::Parts::Part.new(boundary, key, value) @@ -18,7 +18,6 @@ def create_multipart(env, params) parts << Faraday::Parts::EpiloguePart.new(boundary) body = Faraday::CompositeReadIO.new(parts) env.request_headers[Faraday::Env::ContentLength] = body.length.to_s - debugger body end end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 64565c2..3f5b5e0 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -2,7 +2,9 @@ require 'uri' require 'pathname' require 'tempfile' +require 'faraday' +require_relative 'report_portal/patches/faraday' require_relative 'report_portal/settings' require_relative 'report_portal/client' @@ -12,7 +14,7 @@ module ReportPortal class << self attr_accessor :launch_id, :current_scenario, :last_used_time, :logger - def initialize(logger) # tried 1. initialize, 2. new, 3. self.initialize, 4. self.new + def initialize(logger) @logger = logger @client = ReportPortal::Client.new(logger) end @@ -79,8 +81,7 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) logger.debug "finish_item: response [#{response}] " rescue RestClient::Exception => e response = JSON.parse(e.response) - - raise e unless response['error_code'] == 40018 + raise e unless response['error_code'] == 40_018 end item.closed = true end @@ -117,11 +118,9 @@ def send_file(status, path, label = nil, time = now, mime_type = 'image/png') time: time, file: { name: file_name.to_s }, "Content-Type": 'application/json' } - headers = {'Content-Type': 'multipart/form-data'} payload = { 'json_request_part': [json].to_json, file_name => Faraday::UploadIO.new(path, mime_type) } - debugger - @client.process_request('log', :post, payload, headers) + @client.process_request('log', :post, payload, content_type: 'multipart/form-data') end def get_item(name, parent_node) diff --git a/reportportal.gemspec b/reportportal.gemspec index 402a329..e645d8a 100644 --- a/reportportal.gemspec +++ b/reportportal.gemspec @@ -14,8 +14,11 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.3.0' s.license = 'Apache-2.0' - s.add_runtime_dependency('faraday', '~> 0.13') + s.add_dependency('net-http-persistent', '~> 3.0') + s.add_runtime_dependency('faraday', '~> 0.15') + s.add_runtime_dependency('parallel_tests', '~> 2.15') s.add_runtime_dependency('rubytree', '~> 1.0') + s.add_runtime_dependency('sys-proctable', '1.1.5') s.add_development_dependency('rubocop', '0.71') end From 2de8740a7e5a89dd3430d62020df69558ef56985 Mon Sep 17 00:00:00 2001 From: vveliev Date: Fri, 16 Aug 2019 11:12:13 -0400 Subject: [PATCH 98/98] rubocop fix --- lib/report_portal/cucumber/report.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index b82266b..16642bc 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -235,6 +235,7 @@ def set_parallel_tests_vars def get_parallel_test_process(process_list) process_list.each do |process| next unless process.cmdline.match(%r{bin(?:\/|\\)parallel_(?:cucumber|test)(.+)}) + @parallel = true @logger.debug("get_parallel_test_process: #{process.cmdline}") return process