rpruby 1.2 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,54 @@
1
+ require 'parallel_tests'
2
+
3
+ require_relative 'report'
4
+
5
+ module Rpruby
6
+ module Cucumber
7
+ class ParallelReport < Report
8
+ FILE_WITH_LAUNCH_ID = Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{Process.ppid}.lck"
9
+
10
+ def parallel?
11
+ true
12
+ end
13
+
14
+ def initialize
15
+ @root_node = Tree::TreeNode.new('')
16
+ @parent_item_node = @root_node
17
+ @last_used_time ||= 0
18
+
19
+ if ParallelTests.first_process?
20
+ File.open(FILE_WITH_LAUNCH_ID, 'w') do |f|
21
+ f.flock(File::LOCK_EX)
22
+ start_launch
23
+ f.write(Rpruby.launch_id)
24
+ f.flush
25
+ f.flock(File::LOCK_UN)
26
+ end
27
+ else
28
+ File.open(FILE_WITH_LAUNCH_ID, 'r') do |f|
29
+ f.flock(File::LOCK_SH)
30
+ ReportPortal.launch_id = f.read
31
+ f.flock(File::LOCK_UN)
32
+ end
33
+ end
34
+ end
35
+
36
+ def test_run_finished(_event, desired_time = Rpruby.now)
37
+ end_feature(desired_time) unless @parent_item_node.is_root?
38
+
39
+ if ParallelTests.first_process?
40
+ ParallelTests.wait_for_other_processes_to_finish
41
+
42
+ File.delete(FILE_WITH_LAUNCH_ID)
43
+
44
+ unless attach_to_launch?
45
+ $stdout.puts "Finishing launch #{Rpruby.launch_id}"
46
+ Rpruby.close_child_items(nil)
47
+ time_to_send = time_to_send(desired_time)
48
+ Rpruby.finish_launch(time_to_send)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,220 @@
1
+ require 'cucumber/formatter/io'
2
+ require 'cucumber/formatter/hook_query_visitor'
3
+ require 'tree'
4
+ require 'securerandom'
5
+
6
+ require_relative '../../rpruby'
7
+ require_relative '../logging/logger'
8
+
9
+ module Rpruby
10
+ module Cucumber
11
+ # @api private
12
+ class Report
13
+ def parallel?
14
+ false
15
+ end
16
+
17
+ def attach_to_launch?
18
+ Rpruby::Settings.instance.formatter_modes.include?('attach_to_launch')
19
+ end
20
+
21
+ def initialize
22
+ @last_used_time = 0
23
+ @root_node = Tree::TreeNode.new('')
24
+ @parent_item_node = @root_node
25
+ start_launch
26
+ end
27
+
28
+ def start_launch(desired_time = Rpruby.now)
29
+ if attach_to_launch?
30
+ Rpruby.launch_id =
31
+ if Rpruby::Settings.instance.launch_id
32
+ Rpruby::Settings.instance.launch_id
33
+ else
34
+ file_path = Rpruby::Settings.instance.file_with_launch_id || (Pathname(Dir.tmpdir) + 'rp_launch_id.tmp')
35
+ File.read(file_path)
36
+ end
37
+ $stdout.puts "Attaching to launch #{Rpruby.launch_id}"
38
+ else
39
+ description = Rpruby::Settings.instance.description
40
+ description ||= ARGV.map { |arg| arg.gsub(/rp_uuid=.+/, 'rp_uuid=[FILTERED]') }.join(' ')
41
+ Rpruby.start_launch(description, time_to_send(desired_time))
42
+ end
43
+ end
44
+
45
+ # TODO: time should be a required argument
46
+ def test_case_started(event, desired_time = Rpruby.now)
47
+ test_case = event.test_case
48
+ feature = test_case.feature
49
+ if report_hierarchy? && !same_feature_as_previous_test_case?(feature)
50
+ end_feature(desired_time) unless @parent_item_node.is_root?
51
+ start_feature_with_parentage(feature, desired_time)
52
+ end
53
+
54
+ name = "#{test_case.keyword}: #{test_case.name}"
55
+ description = test_case.location.to_s
56
+ tags = test_case.tags.map(&:name)
57
+ type = :STEP
58
+
59
+ Rpruby.current_scenario = Rpruby::TestItem.new(name: name, type: type, id: nil, start_time: time_to_send(desired_time), description: description, closed: false, tags: tags)
60
+ scenario_node = Tree::TreeNode.new(SecureRandom.hex, Rpruby.current_scenario)
61
+ @parent_item_node << scenario_node
62
+ Rpruby.current_scenario.id = Rpruby.start_item(scenario_node)
63
+ end
64
+
65
+ def test_case_finished(event, desired_time = Rpruby.now)
66
+ result = event.result
67
+ status = result.to_sym
68
+ issue = nil
69
+ if %i[undefined pending].include?(status)
70
+ status = :failed
71
+ issue = result.message
72
+ end
73
+ Rpruby.finish_item(Rpruby.current_scenario, status, time_to_send(desired_time), issue)
74
+ Rpruby.current_scenario = nil
75
+ end
76
+
77
+ def test_step_started(event, desired_time = Rpruby.now)
78
+ test_step = event.test_step
79
+ if step?(test_step) # `after_test_step` is also invoked for hooks
80
+ step_source = test_step.source.last
81
+ message = "-- #{step_source.keyword}#{step_source.text} --"
82
+ if step_source.multiline_arg.doc_string?
83
+ message << %(\n"""\n#{step_source.multiline_arg.content}\n""")
84
+ elsif step_source.multiline_arg.data_table?
85
+ message << step_source.multiline_arg.raw.reduce("\n") { |acc, row| acc << "| #{row.join(' | ')} |\n" }
86
+ end
87
+ Rpruby.send_log(:trace, message, time_to_send(desired_time))
88
+ end
89
+ end
90
+
91
+ def test_step_finished(event, desired_time = Rpruby.now)
92
+ test_step = event.test_step
93
+ result = event.result
94
+ status = result.to_sym
95
+
96
+ if %i[failed pending undefined].include?(status)
97
+ exception_info = if %i[failed pending].include?(status)
98
+ ex = result.exception
99
+ format("%s: %s\n %s", ex.class.name, ex.message, ex.backtrace.join("\n "))
100
+ else
101
+ format("Undefined step: %s:\n%s", test_step.text, test_step.source.last.backtrace_line)
102
+ end
103
+ Rpruby.send_log(:error, exception_info, time_to_send(desired_time))
104
+ end
105
+
106
+ if status != :passed
107
+ log_level = status == :skipped ? :warn : :error
108
+ step_type = if step?(test_step)
109
+ 'Step'
110
+ else
111
+ hook_class_name = test_step.source.last.class.name.split('::').last
112
+ location = test_step.location
113
+ "#{hook_class_name} at `#{location}`"
114
+ end
115
+ Rpruby.send_log(log_level, "#{step_type} #{status}", time_to_send(desired_time))
116
+ end
117
+ end
118
+
119
+ def test_run_finished(_event, desired_time = Rpruby.now)
120
+ end_feature(desired_time) unless @parent_item_node.is_root?
121
+
122
+ unless attach_to_launch?
123
+ close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature
124
+ time_to_send = time_to_send(desired_time)
125
+ Rpruby.finish_launch(time_to_send)
126
+ end
127
+ end
128
+
129
+ def puts(message, desired_time = Rpruby.now)
130
+ Rpruby.send_log(:info, message, time_to_send(desired_time))
131
+ end
132
+
133
+ def embed(path_or_src, mime_type, label, desired_time = Rpruby.now)
134
+ Rpruby.send_file(:info, path_or_src, label, time_to_send(desired_time), mime_type)
135
+ end
136
+
137
+ private
138
+
139
+ # Report Portal sorts logs by time. However, several logs might have the same time.
140
+ # So to get Report Portal sort them properly the time should be different in all logs related to the same item.
141
+ # And thus it should be stored.
142
+ # Only the last time needs to be stored as:
143
+ # * only one test framework process/thread may send data for a single Report Portal item
144
+ # * that process/thread can't start the next test until it's done with the previous one
145
+ def time_to_send(desired_time)
146
+ time_to_send = desired_time
147
+ if time_to_send <= @last_used_time
148
+ time_to_send = @last_used_time + 1
149
+ end
150
+ @last_used_time = time_to_send
151
+ end
152
+
153
+ def same_feature_as_previous_test_case?(feature)
154
+ @parent_item_node.name == feature.location.file.split(File::SEPARATOR).last
155
+ end
156
+
157
+ def start_feature_with_parentage(feature, desired_time)
158
+ parent_node = @root_node
159
+ child_node = nil
160
+ path_components = feature.location.file.split(File::SEPARATOR)
161
+ path_components.each_with_index do |path_component, index|
162
+ child_node = parent_node[path_component]
163
+ unless child_node # if child node was not created yet
164
+ if index < path_components.size - 1
165
+ name = "Folder: #{path_component}"
166
+ description = nil
167
+ tags = []
168
+ type = :SUITE
169
+ else
170
+ name = "#{feature.keyword}: #{feature.name}"
171
+ description = feature.file # TODO: consider adding feature description and comments
172
+ tags = feature.tags.map(&:name)
173
+ type = :TEST
174
+ end
175
+ # TODO: multithreading # Parallel formatter always executes scenarios inside the same feature in the same process
176
+ if parallel? &&
177
+ index < path_components.size - 1 && # is folder?
178
+ (id_of_created_item = Rpruby.item_id_of(name, parent_node)) # get id for folder from report portal
179
+ # get child id from other process
180
+ item = Rpruby::TestItem.new(name: name, type: type, id: id_of_created_item, start_time: time_to_send(desired_time), description: description, closed: false, tags: tags)
181
+ child_node = Tree::TreeNode.new(path_component, item)
182
+ parent_node << child_node
183
+ else
184
+ item = Rpruby::TestItem.new(name: name, type: type, id: nil, start_time: time_to_send(desired_time), description: description, closed: false, tags: tags)
185
+ child_node = Tree::TreeNode.new(path_component, item)
186
+ parent_node << child_node
187
+ item.id = Rpruby.start_item(child_node) # TODO: multithreading
188
+ end
189
+ end
190
+ parent_node = child_node
191
+ end
192
+ @parent_item_node = child_node
193
+ end
194
+
195
+ def end_feature(desired_time)
196
+ Rpruby.finish_item(@parent_item_node.content, nil, time_to_send(desired_time))
197
+ # Folder items can't be finished here because when the folder started we didn't track
198
+ # which features the folder contains.
199
+ # It's not easy to do it using Cucumber currently:
200
+ # https://github.com/cucumber/cucumber-ruby/issues/887
201
+ end
202
+
203
+ def close_all_children_of(root_node)
204
+ root_node.postordered_each do |node|
205
+ if !node.is_root? && !node.content.closed
206
+ Rpruby.finish_item(node.content)
207
+ end
208
+ end
209
+ end
210
+
211
+ def step?(test_step)
212
+ !::Cucumber::Formatter::HookQueryVisitor.new(test_step).hook?
213
+ end
214
+
215
+ def report_hierarchy?
216
+ !Rpruby::Settings.instance.formatter_modes.include?('skip_reporting_hierarchy')
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,30 @@
1
+ require_relative 'events/prepare_start_item_request'
2
+
3
+ module Rpruby
4
+ # @api private
5
+ class EventBus
6
+ attr_reader :event_types
7
+
8
+ def initialize
9
+ @event_types = {
10
+ prepare_start_item_request: Events::PrepareStartItemRequest
11
+ }
12
+ @handlers = {}
13
+ end
14
+
15
+ def on(event_name, &proc)
16
+ handlers_for(event_name) << proc
17
+ end
18
+
19
+ def broadcast(event_name, **attributes)
20
+ event = event_types.fetch(event_name).new(**attributes)
21
+ handlers_for(event_name).each { |handler| handler.call(event) }
22
+ end
23
+
24
+ private
25
+
26
+ def handlers_for(event_name)
27
+ @handlers[event_name] ||= []
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ module Rpruby
2
+ module Events
3
+ # An event executed before sending a StartTestItem request.
4
+ class PrepareStartItemRequest
5
+ # A hash that contains keys like item's `start_time`, `name`, `description`.
6
+ attr_reader :request_data
7
+
8
+ def initialize(request_data:)
9
+ @request_data = request_data
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,64 @@
1
+ require 'http'
2
+
3
+ module Rpruby
4
+ # @api private
5
+ class HttpClient
6
+ def initialize
7
+ create_client
8
+ end
9
+
10
+ def send_request(verb, path, options = {})
11
+ path.prepend("/api/v1/#{Settings.instance.project}/")
12
+ path.prepend(origin) unless use_persistent?
13
+ 3.times do
14
+ begin
15
+ response = @http.request(verb, path, options)
16
+ rescue StandardError => e
17
+ puts "Request #{request_info(verb, path)} produced an exception:"
18
+ puts e
19
+ recreate_client
20
+ else
21
+ return response.parse(:json) if response.status.success?
22
+
23
+ message = "Request #{request_info(verb, path)} returned code #{response.code}."
24
+ message << " Response:\n#{response}" unless response.to_s.empty?
25
+ puts message
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def create_client
33
+ @http = HTTP.auth("Bearer #{Settings.instance.uuid}")
34
+ @http = @http.persistent(origin) if use_persistent?
35
+ add_insecure_ssl_options if Settings.instance.disable_ssl_verification
36
+ end
37
+
38
+ def add_insecure_ssl_options
39
+ ssl_context = OpenSSL::SSL::SSLContext.new
40
+ ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
41
+ @http.default_options = { ssl_context: ssl_context }
42
+ end
43
+
44
+ # Response should be consumed before sending next request via the same persistent connection.
45
+ # If an exception occurred, there may be no response so a connection has to be recreated.
46
+ def recreate_client
47
+ @http.close
48
+ create_client
49
+ end
50
+
51
+ def request_info(verb, path)
52
+ uri = URI.join(origin, path)
53
+ "#{verb.upcase} `#{uri}`"
54
+ end
55
+
56
+ def origin
57
+ Addressable::URI.parse(Settings.instance.endpoint).origin
58
+ end
59
+
60
+ def use_persistent?
61
+ Rpruby::Settings.instance.formatter_modes.include?('use_persistent_connection')
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,16 @@
1
+ require 'log4r/outputter/outputter'
2
+
3
+ require_relative '../../rpruby'
4
+
5
+ module Rpruby
6
+ # Custom ReportPortal outputter for 'log4r' gem
7
+ class Log4rOutputter < Log4r::Outputter
8
+ def canonical_log(logevent)
9
+ synch { write(Log4r::LNAMES[logevent.level], format(logevent)) }
10
+ end
11
+
12
+ def write(level, data)
13
+ Rpruby.send_log(level, data, Rpruby.now)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ require 'logger'
2
+
3
+ module Rpruby
4
+ class << self
5
+ # Monkey-patch for built-in Logger class
6
+ def patch_logger
7
+ Logger.class_eval do
8
+ alias_method :orig_add, :add
9
+ alias_method :orig_write, :<<
10
+ def add(severity, message = nil, progname = nil, &block)
11
+ ret = orig_add(severity, message, progname, &block)
12
+
13
+ unless severity < @level
14
+ progname ||= @progname
15
+ if message.nil?
16
+ if block_given?
17
+ message = yield
18
+ else
19
+ message = progname
20
+ progname = @progname
21
+ end
22
+ end
23
+ Rpruby.send_log(format_severity(severity), format_message(format_severity(severity), Time.now, progname, message.to_s), Rpruby.now)
24
+ end
25
+ ret
26
+ end
27
+
28
+ def <<(msg)
29
+ ret = orig_write(msg)
30
+ Rpruby.send_log(Rpruby::LOG_LEVELS[:unknown], msg.to_s, Rpruby.now)
31
+ ret
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ require 'logging'
2
+
3
+ require_relative '../../rpruby'
4
+
5
+ module Rpruby
6
+ # Custom ReportPortal appender for 'logging' gem
7
+ class LoggingAppender < ::Logging::Appender
8
+ def write(event)
9
+ (str, lvl) = if event.instance_of?(::Logging::LogEvent)
10
+ [layout.format(event), event.level]
11
+ else
12
+ [event.to_s, Rpruby::LOG_LEVELS[:unknown]]
13
+ end
14
+
15
+ Rpruby.send_log(lvl, str, Rpruby.now)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ module Rpruby
2
+ # Options of a request to search items
3
+ class ItemSearchOptions
4
+ MAPPING = {
5
+ launch_id: 'filter.eq.launch',
6
+ name: 'filter.eq.name',
7
+ description: 'filter.eq.description',
8
+ parameter_key: 'filter.eq.parameters$key',
9
+ parameter_value: 'filter.eq.parameters$value',
10
+ page_size: 'page.size',
11
+ page_number: 'page.page'
12
+ }.freeze
13
+
14
+ attr_reader :query_params
15
+
16
+ def initialize(params = {})
17
+ @query_params = params.map { |mapping_key, v| [param_name(mapping_key), v] }.to_h
18
+ end
19
+
20
+ private
21
+
22
+ def param_name(mapping_key)
23
+ MAPPING.fetch(mapping_key) { raise KeyError, "key not found: '#{mapping_key.inspect}'. It should be one of: #{MAPPING.keys}" }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ module Rpruby
2
+ # Represents a test item
3
+ class TestItem
4
+ attr_reader :launch_id, :unique_id, :name, :description, :type, :parameters, :tags, :status, :start_time
5
+ attr_accessor :id, :closed
6
+
7
+ def initialize(options = {})
8
+ options = options.map { |k, v| [k.to_sym, v] }.to_h
9
+ @launch_id = options[:launch_id]
10
+ @unique_id = options[:unique_id]
11
+ @name = options[:name]
12
+ @description = options[:description]
13
+ @type = options[:type]
14
+ @parameters = options[:parameters]
15
+ @tags = options[:tags]
16
+ @status = options[:status]
17
+ @start_time = options[:start_time]
18
+ @id = options[:id]
19
+ @closed = options[:closed]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,112 @@
1
+ require 'securerandom'
2
+ require 'tree'
3
+ require 'rspec/core'
4
+
5
+ require_relative '../../rpruby'
6
+
7
+ # TODO: Screenshots
8
+ # TODO: Logs
9
+ module Rpruby
10
+ module RSpec
11
+ class Formatter
12
+ MAX_DESCRIPTION_LENGTH = 255
13
+ MIN_DESCRIPTION_LENGTH = 3
14
+
15
+ ::RSpec::Core::Formatters.register self, :start, :example_group_started, :example_group_finished,
16
+ :example_started, :example_passed, :example_failed,
17
+ :example_pending, :message, :stop
18
+
19
+ def initialize(_output)
20
+ ENV['REPORT_PORTAL_USED'] = 'true'
21
+ end
22
+
23
+ def start(_start_notification)
24
+ cmd_args = ARGV.map { |arg| arg.include?('rp_uuid=') ? 'rp_uuid=[FILTERED]' : arg }.join(' ')
25
+ Rpruby.start_launch(cmd_args)
26
+ @root_node = Tree::TreeNode.new(SecureRandom.hex)
27
+ @current_group_node = @root_node
28
+ end
29
+
30
+ def example_group_started(group_notification)
31
+ description = group_notification.group.description
32
+ if description.size < MIN_DESCRIPTION_LENGTH
33
+ p "Group description should be at least #{MIN_DESCRIPTION_LENGTH} characters ('group_notification': #{group_notification.inspect})"
34
+ return
35
+ end
36
+ item = Rpruby::TestItem.new(name: description[0..MAX_DESCRIPTION_LENGTH - 1],
37
+ type: :TEST,
38
+ id: nil,
39
+ start_time: Rpruby.now,
40
+ description: '',
41
+ closed: false,
42
+ tags: [])
43
+ group_node = Tree::TreeNode.new(SecureRandom.hex, item)
44
+ if group_node.nil?
45
+ p "Group node is nil for item #{item.inspect}"
46
+ else
47
+ @current_group_node << group_node unless @current_group_node.nil? # make @current_group_node parent of group_node
48
+ @current_group_node = group_node
49
+ group_node.content.id = ReportPortal.start_item(group_node)
50
+ end
51
+ end
52
+
53
+ def example_group_finished(_group_notification)
54
+ unless @current_group_node.nil?
55
+ Rpruby.finish_item(@current_group_node.content)
56
+ @current_group_node = @current_group_node.parent
57
+ end
58
+ end
59
+
60
+ def example_started(notification)
61
+ description = notification.example.description
62
+ if description.size < MIN_DESCRIPTION_LENGTH
63
+ p "Example description should be at least #{MIN_DESCRIPTION_LENGTH} characters ('notification': #{notification.inspect})"
64
+ return
65
+ end
66
+ Rpruby.current_scenario = Rpruby::TestItem.new(name: description[0..MAX_DESCRIPTION_LENGTH - 1],
67
+ type: :STEP,
68
+ id: nil,
69
+ start_time: Rpruby.now,
70
+ description: '',
71
+ closed: false,
72
+ tags: [])
73
+ example_node = Tree::TreeNode.new(SecureRandom.hex, Rpruby.current_scenario)
74
+ if example_node.nil?
75
+ p "Example node is nil for scenario #{Rpruby.current_scenario.inspect}"
76
+ else
77
+ @current_group_node << example_node
78
+ example_node.content.id = Rpruby.start_item(example_node)
79
+ end
80
+ end
81
+
82
+ def example_passed(_notification)
83
+ Rpruby.finish_item(Rpruby.current_scenario, :passed) unless Rpruby.current_scenario.nil?
84
+ Rpruby.current_scenario = nil
85
+ end
86
+
87
+ def example_failed(notification)
88
+ exception = notification.exception
89
+ Rpruby.send_log(:failed, %(#{exception.class}: #{exception.message}\n\nStacktrace: #{notification.formatted_backtrace.join("\n")}), Rpruby.now)
90
+ Rpruby.finish_item(Rpruby.current_scenario, :failed) unless Rpruby.current_scenario.nil?
91
+ Rpruby.current_scenario = nil
92
+ end
93
+
94
+ def example_pending(_notification)
95
+ Rpruby.finish_item(Rpruby.current_scenario, :skipped) unless Rpruby.current_scenario.nil?
96
+ Rpruby.current_scenario = nil
97
+ end
98
+
99
+ def message(notification)
100
+ if notification.message.respond_to?(:read)
101
+ Rpruby.send_file(:passed, notification.message)
102
+ else
103
+ Rpruby.send_log(:passed, notification.message, Rpruby.now)
104
+ end
105
+ end
106
+
107
+ def stop(_notification)
108
+ Rpruby.finish_launch
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,65 @@
1
+ require 'yaml'
2
+ require 'singleton'
3
+
4
+ module Rpruby
5
+ class Settings
6
+ include Singleton
7
+
8
+ def initialize
9
+ filename = ENV.fetch('rp_config') do
10
+ glob = Dir.glob('{,.config/,config/}report{,-,_}portal{.yml,.yaml}')
11
+ p "Multiple configuration files found for ReportPortal. Using the first one: #{glob.first}" if glob.size > 1
12
+ glob.first
13
+ end
14
+
15
+ @properties = filename.nil? ? {} : YAML.load_file(filename)
16
+ keys = {
17
+ 'uuid' => true,
18
+ 'endpoint' => true,
19
+ 'project' => true,
20
+ 'launch' => true,
21
+ 'tags' => false,
22
+ 'description' => false,
23
+ 'attributes' => false,
24
+ 'is_debug' => false,
25
+ 'disable_ssl_verification' => false,
26
+ # for parallel execution only
27
+ 'use_standard_logger' => false,
28
+ 'launch_id' => false,
29
+ 'file_with_launch_id' => false
30
+ }
31
+
32
+ keys.each do |key, is_required|
33
+ define_singleton_method(key.to_sym) { setting(key) }
34
+ next unless is_required && public_send(key).nil?
35
+
36
+ env_variable_name = env_variable_name(key)
37
+ raise "ReportPortal: Define environment variable '#{env_variable_name.upcase}', '#{env_variable_name}' "\
38
+ "or key #{key} in the configuration YAML file"
39
+ end
40
+ end
41
+
42
+ def launch_mode
43
+ is_debug ? 'DEBUG' : 'DEFAULT'
44
+ end
45
+
46
+ def formatter_modes
47
+ setting('formatter_modes') || []
48
+ end
49
+
50
+ private
51
+
52
+ def setting(key)
53
+ env_variable_name = env_variable_name(key)
54
+ return YAML.safe_load(ENV[env_variable_name.upcase]) if ENV.key?(env_variable_name.upcase)
55
+
56
+ return YAML.safe_load(ENV[env_variable_name]) if ENV.key?(env_variable_name)
57
+
58
+ @properties[key]
59
+ end
60
+
61
+ def env_variable_name(key)
62
+ 'rp_' + key
63
+ end
64
+ end
65
+ end