rpruby 1.2 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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