diff --git a/lib/report_portal/rspec/formatter.rb b/lib/report_portal/rspec/formatter.rb index 1d287b7..bae8607 100644 --- a/lib/report_portal/rspec/formatter.rb +++ b/lib/report_portal/rspec/formatter.rb @@ -3,14 +3,13 @@ require 'rspec/core' require_relative '../../reportportal' +require_relative 'report' # TODO: Screenshots # TODO: Logs module ReportPortal module RSpec - class Formatter - MAX_DESCRIPTION_LENGTH = 255 - MIN_DESCRIPTION_LENGTH = 3 + class Formatter < Report ::RSpec::Core::Formatters.register self, :start, :example_group_started, :example_group_finished, :example_started, :example_passed, :example_failed, @@ -27,86 +26,12 @@ def start(_start_notification) @current_group_node = @root_node end - def example_group_started(group_notification) - description = group_notification.group.description - if description.size < MIN_DESCRIPTION_LENGTH - p "Group description should be at least #{MIN_DESCRIPTION_LENGTH} characters ('group_notification': #{group_notification.inspect})" - return - end - item = ReportPortal::TestItem.new(name: description[0..MAX_DESCRIPTION_LENGTH - 1], - type: :TEST, - id: nil, - start_time: ReportPortal.now, - description: '', - closed: false, - tags: []) - group_node = Tree::TreeNode.new(SecureRandom.hex, item) - if group_node.nil? - p "Group node is nil for item #{item.inspect}" - else - @current_group_node << group_node unless @current_group_node.nil? # make @current_group_node parent of group_node - @current_group_node = group_node - group_node.content.id = ReportPortal.start_item(group_node) - end - end - def example_group_finished(_group_notification) unless @current_group_node.nil? ReportPortal.finish_item(@current_group_node.content) @current_group_node = @current_group_node.parent end end - - def example_started(notification) - description = notification.example.description - if description.size < MIN_DESCRIPTION_LENGTH - p "Example description should be at least #{MIN_DESCRIPTION_LENGTH} characters ('notification': #{notification.inspect})" - return - end - ReportPortal.current_scenario = ReportPortal::TestItem.new(name: description[0..MAX_DESCRIPTION_LENGTH - 1], - type: :STEP, - id: nil, - start_time: ReportPortal.now, - description: '', - closed: false, - tags: []) - example_node = Tree::TreeNode.new(SecureRandom.hex, ReportPortal.current_scenario) - if example_node.nil? - p "Example node is nil for scenario #{ReportPortal.current_scenario.inspect}" - else - @current_group_node << example_node - example_node.content.id = ReportPortal.start_item(example_node) - end - end - - def example_passed(_notification) - ReportPortal.finish_item(ReportPortal.current_scenario, :passed) unless ReportPortal.current_scenario.nil? - ReportPortal.current_scenario = nil - end - - def example_failed(notification) - exception = notification.exception - ReportPortal.send_log(:failed, %(#{exception.class}: #{exception.message}\n\nStacktrace: #{notification.formatted_backtrace.join("\n")}), ReportPortal.now) - ReportPortal.finish_item(ReportPortal.current_scenario, :failed) unless ReportPortal.current_scenario.nil? - ReportPortal.current_scenario = nil - end - - def example_pending(_notification) - ReportPortal.finish_item(ReportPortal.current_scenario, :skipped) unless ReportPortal.current_scenario.nil? - ReportPortal.current_scenario = nil - end - - def message(notification) - if notification.message.respond_to?(:read) - ReportPortal.send_file(:passed, notification.message) - else - ReportPortal.send_log(:passed, notification.message, ReportPortal.now) - end - end - - def stop(_notification) - ReportPortal.finish_launch - end end end end diff --git a/lib/report_portal/rspec/parallel_formatter.rb b/lib/report_portal/rspec/parallel_formatter.rb new file mode 100644 index 0000000..d1df60c --- /dev/null +++ b/lib/report_portal/rspec/parallel_formatter.rb @@ -0,0 +1,128 @@ +require 'securerandom' +require 'tree' +require 'rspec/core' +require 'pry' +require_relative '../../reportportal' +require_relative 'report' + +module ReportPortal + module RSpec + class ParallelFormatter < Report + + FILE_WITH_LAUNCH_ID = Dir.pwd + "/parallel_launch_id_for_#{Process.ppid}.lck" + FILE_WITH_PARALLEL_GROUPS_COUNT = Dir.pwd + "/parallel_groups_for_#{Process.ppid}.lck" + + @@parallel_count = ENV['PARALLEL_TEST_GROUPS'].to_i + @@parallel_count_for_fininshing_launch = @@parallel_count + + ::RSpec::Core::Formatters.register self, :example_group_started, :example_group_finished, + :example_started, :example_passed, :example_failed, + :example_pending, :message + + def initialize(_output) + ENV['REPORT_PORTAL_USED'] = 'true' + end + + def start_launch + @root_node = Tree::TreeNode.new(SecureRandom.hex) + @current_group_node = @root_node + description = ReportPortal::Settings.instance.description + description = ARGV.map { |arg| arg.include?('rp_uuid=') ? 'rp_uuid=[FILTERED]' : arg }.join(' ') + if attach_to_launch? + if @@parallel_count.to_i == ENV['PARALLEL_TEST_GROUPS'].to_i && ParallelTests.first_process? + # Start launch and store launch id in FILE_WITH_LAUNCH_ID file + start_and_write_launch_id(description) + write_parallel_groups_count(ENV['PARALLEL_TEST_GROUPS'].to_i - 1) + @@parallel_count = read_parallel_groups_count + p "Single Launch created #{ReportPortal.launch_id}" + else + wait_for_launch + 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 + else + ReportPortal.start_launch(description) + end + end + + def example_group_started(group_notification) + start_launch + description = group_notification.group.description + if description.size < MIN_DESCRIPTION_LENGTH + p "Group description should be at least #{MIN_DESCRIPTION_LENGTH} characters ('group_notification': #{group_notification.inspect})" + return + end + item = ReportPortal::TestItem.new(name: description[0..MAX_DESCRIPTION_LENGTH - 1], + type: :TEST, + id: nil, + start_time: ReportPortal.now, + description: '', + closed: false, + tags: []) + group_node = Tree::TreeNode.new(SecureRandom.hex, item) + if group_node.nil? + p "Group node is nil for item #{item.inspect}" + else + @current_group_node << group_node unless @current_group_node.nil? # make @current_group_node parent of group_node + @current_group_node = group_node + group_node.content.id = ReportPortal.start_item(group_node) + end + end + + def example_group_finished(_group_notification) + ReportPortal.finish_item(@current_group_node.content) unless @current_group_node.nil? + if attach_to_launch? + @@parallel_count_for_fininshing_launch = read_parallel_groups_count + return unless @@parallel_count_for_fininshing_launch.to_i.zero? + + $stdout.puts "Finishing launch #{ReportPortal.launch_id}" + end + ReportPortal.finish_launch(ReportPortal.now) + end + + def attach_to_launch? + ReportPortal::Settings.instance.formatter_modes.include?('attach_to_launch') + end + + def wait_for_launch + until File.exist?(FILE_WITH_LAUNCH_ID) do + p 'Waiting for Launch ID, Note: Launch ID will be created on First Process only' + sleep 1 + end + end + + def write_parallel_groups_count(count) + Dir.glob("#{Dir.pwd}/parallel_groups_for_*.lck").each { |file| File.delete(file) } + File.open(FILE_WITH_PARALLEL_GROUPS_COUNT, 'w+') do |f| + f.flock(File::LOCK_EX) + f.write(count) + f.flush + f.flock(File::LOCK_UN) + end + end + + def read_parallel_groups_count + File.open(FILE_WITH_PARALLEL_GROUPS_COUNT, 'r') do |f| + f.flock(File::LOCK_SH) + group_count = f.read + f.flock(File::LOCK_UN) + return group_count + end + end + + def start_and_write_launch_id(description) + Dir.glob("#{Dir.pwd}/parallel_launch_id_for_*.lck").each { |file| File.delete(file) } + File.open(FILE_WITH_LAUNCH_ID, 'w+') do |f| + f.flock(File::LOCK_EX) + ReportPortal.start_launch(description) + f.write(ReportPortal.launch_id) + f.flush + f.flock(File::LOCK_UN) + end + end + end + end +end diff --git a/lib/report_portal/rspec/report.rb b/lib/report_portal/rspec/report.rb new file mode 100644 index 0000000..ac90ef2 --- /dev/null +++ b/lib/report_portal/rspec/report.rb @@ -0,0 +1,84 @@ +module ReportPortal + module RSpec + # @api private + class Report + + MAX_DESCRIPTION_LENGTH = 255 + MIN_DESCRIPTION_LENGTH = 3 + + def example_group_started(group_notification) + description = group_notification.group.description + if description.size < MIN_DESCRIPTION_LENGTH + p "Group description should be at least #{MIN_DESCRIPTION_LENGTH} characters ('group_notification': #{group_notification.inspect})" + return + end + item = ReportPortal::TestItem.new(name: description[0..MAX_DESCRIPTION_LENGTH - 1], + type: :TEST, + id: nil, + start_time: ReportPortal.now, + description: '', + closed: false, + tags: []) + group_node = Tree::TreeNode.new(SecureRandom.hex, item) + if group_node.nil? + p "Group node is nil for item #{item.inspect}" + else + @current_group_node << group_node unless @current_group_node.nil? # make @current_group_node parent of group_node + @current_group_node = group_node + group_node.content.id = ReportPortal.start_item(group_node) + end + end + + def example_started(notification) + description = notification.example.description + if description.size < MIN_DESCRIPTION_LENGTH + p "Example description should be at least #{MIN_DESCRIPTION_LENGTH} characters ('notification': #{notification.inspect})" + return + end + ReportPortal.current_scenario = ReportPortal::TestItem.new(name: description[0..MAX_DESCRIPTION_LENGTH - 1], + type: :STEP, + id: nil, + start_time: ReportPortal.now, + description: '', + closed: false, + tags: []) + example_node = Tree::TreeNode.new(SecureRandom.hex, ReportPortal.current_scenario) + if example_node.nil? + p "Example node is nil for scenario #{ReportPortal.current_scenario.inspect}" + else + @current_group_node << example_node + example_node.content.id = ReportPortal.start_item(example_node) + end + end + + def example_passed(_notification) + ReportPortal.finish_item(ReportPortal.current_scenario, :passed) unless ReportPortal.current_scenario.nil? + ReportPortal.current_scenario = nil + end + + def example_failed(notification) + exception = notification.exception + ReportPortal.send_log(:failed, %(#{exception.class}: #{exception.message}\n\nStacktrace: #{notification.formatted_backtrace.join("\n")}), ReportPortal.now) + ReportPortal.finish_item(ReportPortal.current_scenario, :failed) unless ReportPortal.current_scenario.nil? + ReportPortal.current_scenario = nil + end + + def example_pending(_notification) + ReportPortal.finish_item(ReportPortal.current_scenario, :skipped) unless ReportPortal.current_scenario.nil? + ReportPortal.current_scenario = nil + end + + def message(notification) + if notification.message.respond_to?(:read) + ReportPortal.send_file(:passed, notification.message) + else + ReportPortal.send_log(:passed, notification.message, ReportPortal.now) + end + end + + def stop(_notification) + ReportPortal.finish_launch + end + end + end +end