Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parallel RSpec Formatter #98

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 2 additions & 77 deletions lib/report_portal/rspec/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
128 changes: 128 additions & 0 deletions lib/report_portal/rspec/parallel_formatter.rb
Original file line number Diff line number Diff line change
@@ -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
84 changes: 84 additions & 0 deletions lib/report_portal/rspec/report.rb
Original file line number Diff line number Diff line change
@@ -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