reportportal 0.7

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,53 @@
1
+ # Copyright 2015 EPAM Systems
2
+ #
3
+ #
4
+ # This file is part of Report Portal.
5
+ #
6
+ # Report Portal is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ReportPortal is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with Report Portal. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require_relative 'formatter'
20
+ require_relative 'parallel_report'
21
+ require 'parallel_tests/gherkin/io'
22
+
23
+ module ReportPortal
24
+ module Cucumber
25
+ class ParallelFormatter < Formatter
26
+ #include ::ParallelTests::Gherkin::Io
27
+
28
+ # @api private
29
+ def initialize(config)
30
+ ENV['REPORT_PORTAL_USED'] = 'true'
31
+
32
+ @queue = Queue.new
33
+ @thread = Thread.new do
34
+ @report = ReportPortal::Cucumber::ParallelReport.new
35
+ loop do
36
+ method_arr = @queue.pop
37
+ @report.public_send(*method_arr)
38
+ end
39
+ end
40
+ @thread.abort_on_exception = true
41
+
42
+ @io = config.out_stream
43
+
44
+ [:test_case_started, :test_case_finished, :test_step_started, :test_step_finished].each do |event_name|
45
+ config.on_event event_name do |event|
46
+ @queue.push([event_name, event, ReportPortal.now])
47
+ end
48
+ end
49
+ config.on_event :test_run_finished, &method(:on_test_run_finished)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,69 @@
1
+ # Copyright 2015 EPAM Systems
2
+ #
3
+ #
4
+ # This file is part of Report Portal.
5
+ #
6
+ # Report Portal is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ReportPortal is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with Report Portal. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require_relative 'report'
20
+
21
+ module ReportPortal
22
+ module Cucumber
23
+ class ParallelReport < Report
24
+ FILE_WITH_LAUNCH_ID = Pathname(Dir.tmpdir) + "parallel_launch_id_for_#{Process.ppid}.lck"
25
+
26
+ def parallel?
27
+ true
28
+ end
29
+
30
+ def initialize
31
+ @root_node = Tree::TreeNode.new('')
32
+ @last_used_time ||= 0
33
+
34
+ if ParallelTests.first_process?
35
+ File.open(FILE_WITH_LAUNCH_ID, 'w') do |f|
36
+ f.flock(File::LOCK_EX)
37
+ start_launch
38
+ f.write(ReportPortal.launch_id)
39
+ f.flush
40
+ f.flock(File::LOCK_UN)
41
+ end
42
+ else
43
+ File.open(FILE_WITH_LAUNCH_ID, 'r') do |f|
44
+ f.flock(File::LOCK_SH)
45
+ ReportPortal.launch_id = f.read
46
+ f.flock(File::LOCK_UN)
47
+ end
48
+ end
49
+ end
50
+
51
+ def done(desired_time = ReportPortal.now)
52
+ end_feature(desired_time) if @feature_node
53
+
54
+ if ParallelTests.first_process?
55
+ ParallelTests.wait_for_other_processes_to_finish
56
+
57
+ File.delete(FILE_WITH_LAUNCH_ID)
58
+
59
+ unless attach_to_launch?
60
+ $stdout.puts "Finishing launch #{ReportPortal.launch_id}"
61
+ ReportPortal.close_child_items(nil)
62
+ time_to_send = time_to_send(desired_time)
63
+ ReportPortal.finish_launch(time_to_send)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,232 @@
1
+ # Copyright 2015 EPAM Systems
2
+ #
3
+ #
4
+ # This file is part of Report Portal.
5
+ #
6
+ # Report Portal is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ReportPortal is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with Report Portal. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require 'cucumber/formatter/io'
20
+ require 'cucumber/formatter/hook_query_visitor'
21
+ require 'tree'
22
+ require 'securerandom'
23
+
24
+ require_relative '../../reportportal'
25
+ require_relative '../logging/logger'
26
+
27
+ module ReportPortal
28
+ module Cucumber
29
+ # @api private
30
+ class Report
31
+ def parallel?
32
+ false
33
+ end
34
+
35
+ def attach_to_launch?
36
+ ReportPortal::Settings.instance.formatter_modes.include?('attach_to_launch')
37
+ end
38
+
39
+ def initialize
40
+ @last_used_time = 0
41
+ @root_node = Tree::TreeNode.new('')
42
+ start_launch
43
+ end
44
+
45
+ def start_launch(desired_time = ReportPortal.now)
46
+ if attach_to_launch?
47
+ ReportPortal.launch_id =
48
+ if ReportPortal::Settings.instance.launch_id
49
+ ReportPortal::Settings.instance.launch_id
50
+ else
51
+ file_path = ReportPortal::Settings.instance.file_with_launch_id || (Pathname(Dir.tmpdir) + 'rp_launch_id.tmp')
52
+ File.read(file_path)
53
+ end
54
+ $stdout.puts "Attaching to launch #{ReportPortal.launch_id}"
55
+ else
56
+ description = ReportPortal::Settings.instance.description
57
+ description ||= ARGV.map { |arg| arg.gsub(/rp_uuid=.+/, "rp_uuid=[FILTERED]") }.join(' ')
58
+ ReportPortal.start_launch(description, time_to_send(desired_time))
59
+ end
60
+ end
61
+
62
+ def test_case_started(event, desired_time = ReportPortal.now) # TODO: time should be a required argument
63
+ test_case = event.test_case
64
+ feature = test_case.feature
65
+ unless same_feature_as_previous_test_case?(feature)
66
+ end_feature(desired_time) if @feature_node
67
+ start_feature_with_parentage(feature, desired_time)
68
+ end
69
+
70
+ name = "#{test_case.keyword}: #{test_case.name}"
71
+ description = test_case.location.to_s
72
+ tags = test_case.tags.map(&:name)
73
+ type = :STEP
74
+
75
+ ReportPortal.current_scenario = ReportPortal::TestItem.new(name, type, nil, time_to_send(desired_time), description, false, tags)
76
+ scenario_node = Tree::TreeNode.new(SecureRandom.hex, ReportPortal.current_scenario)
77
+ @feature_node << scenario_node
78
+ ReportPortal.current_scenario.id = ReportPortal.start_item(scenario_node)
79
+ end
80
+
81
+ def test_case_finished(event, desired_time = ReportPortal.now)
82
+ result = event.result
83
+ status = result.to_sym
84
+ issue = nil
85
+ if [:undefined, :pending].include?(status)
86
+ status = :failed
87
+ issue = result.message
88
+ end
89
+ ReportPortal.finish_item(ReportPortal.current_scenario, status, time_to_send(desired_time), issue)
90
+ ReportPortal.current_scenario = nil
91
+ end
92
+
93
+ def test_step_started(event, desired_time = ReportPortal.now)
94
+ test_step = event.test_step
95
+ if step?(test_step) # `after_test_step` is also invoked for hooks
96
+ step_source = test_step.source.last
97
+ message = "-- #{step_source.keyword}#{step_source.text} --"
98
+ if step_source.multiline_arg.doc_string?
99
+ message << %(\n"""\n#{step_source.multiline_arg.content}\n""")
100
+ elsif step_source.multiline_arg.data_table?
101
+ message << step_source.multiline_arg.raw.reduce("\n") { |acc, row| acc << "| #{row.join(' | ')} |\n" }
102
+ end
103
+ ReportPortal.send_log(:trace, message, time_to_send(desired_time))
104
+ end
105
+ end
106
+
107
+ def test_step_finished(event, desired_time = ReportPortal.now)
108
+ test_step = event.test_step
109
+ result = event.result
110
+ status = result.to_sym
111
+
112
+ if [:failed, :pending, :undefined].include?(status)
113
+ exception_info = if [:failed, :pending].include?(status)
114
+ ex = result.exception
115
+ sprintf("%s: %s\n %s", ex.class.name, ex.message, ex.backtrace.join("\n "))
116
+ else
117
+ sprintf("Undefined step: %s:\n%s", test_step.text, test_step.source.last.backtrace_line)
118
+ end
119
+ ReportPortal.send_log(:error, exception_info, time_to_send(desired_time))
120
+ end
121
+
122
+ if status != :passed
123
+ log_level = (status == :skipped)? :warn : :error
124
+ step_type = if step?(test_step)
125
+ 'Step'
126
+ else
127
+ hook_class_name = test_step.source.last.class.name.split('::').last
128
+ location = test_step.location
129
+ "#{hook_class_name} at `#{location}`"
130
+ end
131
+ ReportPortal.send_log(log_level, "#{step_type} #{status}", time_to_send(desired_time))
132
+ end
133
+ end
134
+
135
+ def done(desired_time = ReportPortal.now)
136
+ end_feature(desired_time) if @feature_node
137
+
138
+ unless attach_to_launch?
139
+ close_all_children_of(@root_node) # Folder items are closed here as they can't be closed after finishing a feature
140
+ time_to_send = time_to_send(desired_time)
141
+ ReportPortal.finish_launch(time_to_send)
142
+ end
143
+ end
144
+
145
+ def puts(message, desired_time = ReportPortal.now)
146
+ ReportPortal.send_log(:info, message, time_to_send(desired_time))
147
+ end
148
+
149
+ def embed(src, mime_type, label, desired_time = ReportPortal.now)
150
+ ReportPortal.send_file(:info, src, label, time_to_send(desired_time),mime_type)
151
+ end
152
+
153
+ private
154
+
155
+ # Report Portal sorts logs by time. However, several logs might have the same time.
156
+ # So to get Report Portal sort them properly the time should be different in all logs related to the same item.
157
+ # And thus it should be stored.
158
+ # Only the last time needs to be stored as:
159
+ # * only one test framework process/thread may send data for a single Report Portal item
160
+ # * that process/thread can't start the next test until it's done with the previous one
161
+ def time_to_send(desired_time)
162
+ time_to_send = desired_time
163
+ if time_to_send <= @last_used_time
164
+ time_to_send = @last_used_time + 1
165
+ end
166
+ @last_used_time = time_to_send
167
+ end
168
+
169
+ def same_feature_as_previous_test_case?(feature)
170
+ @feature_node && @feature_node.name == feature.location.file.split(File::SEPARATOR).last
171
+ end
172
+
173
+ def start_feature_with_parentage(feature, desired_time)
174
+ parent_node = @root_node
175
+ child_node = nil
176
+ path_components = feature.location.file.split(File::SEPARATOR)
177
+ path_components.each_with_index do |path_component, index|
178
+ child_node = parent_node[path_component]
179
+ unless child_node # if child node was not created yet
180
+ if index < path_components.size - 1
181
+ name = "Folder: #{path_component}"
182
+ description = nil
183
+ tags = []
184
+ type = :SUITE
185
+ else
186
+ name = "#{feature.keyword}: #{feature.name}"
187
+ description = feature.file # TODO: consider adding feature description and comments
188
+ tags = feature.tags.map(&:name)
189
+ type = :TEST
190
+ end
191
+ # TODO: multithreading # Parallel formatter always executes scenarios inside the same feature in the same process
192
+ if parallel? &&
193
+ index < path_components.size - 1 && # is folder?
194
+ (id_of_created_item = ReportPortal.item_id_of(name, parent_node)) # get id for folder from report portal
195
+ # get child id from other process
196
+ item = ReportPortal::TestItem.new(name, type, id_of_created_item, time_to_send(desired_time), description, false, tags)
197
+ child_node = Tree::TreeNode.new(path_component, item)
198
+ parent_node << child_node
199
+ else
200
+ item = ReportPortal::TestItem.new(name, type, nil, time_to_send(desired_time), description, false, tags)
201
+ child_node = Tree::TreeNode.new(path_component, item)
202
+ parent_node << child_node
203
+ item.id = ReportPortal.start_item(child_node) # TODO: multithreading
204
+ end
205
+ end
206
+ parent_node = child_node
207
+ end
208
+ @feature_node = child_node
209
+ end
210
+
211
+ def end_feature(desired_time)
212
+ ReportPortal.finish_item(@feature_node.content, nil, time_to_send(desired_time))
213
+ # Folder items can't be finished here because when the folder started we didn't track
214
+ # which features the folder contains.
215
+ # It's not easy to do it using Cucumber currently:
216
+ # https://github.com/cucumber/cucumber-ruby/issues/887
217
+ end
218
+
219
+ def close_all_children_of(root_node)
220
+ root_node.postordered_each do |node|
221
+ if !node.is_root? && !node.content.closed
222
+ ReportPortal.finish_item(node.content)
223
+ end
224
+ end
225
+ end
226
+
227
+ def step?(test_step)
228
+ !::Cucumber::Formatter::HookQueryVisitor.new(test_step).hook?
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,34 @@
1
+ # Copyright 2015 EPAM Systems
2
+ #
3
+ #
4
+ # This file is part of Report Portal.
5
+ #
6
+ # Report Portal is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ReportPortal is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with Report Portal. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require 'log4r/outputter/outputter'
20
+
21
+ require_relative '../../reportportal'
22
+
23
+ module ReportPortal
24
+ # Custom ReportPortal outputter for 'log4r' gem
25
+ class Log4rOutputter < Log4r::Outputter
26
+ def canonical_log(logevent)
27
+ synch { write(Log4r::LNAMES[logevent.level], format(logevent)) }
28
+ end
29
+
30
+ def write(level, data)
31
+ ReportPortal.send_log(level, data, ReportPortal.now)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+ # Copyright 2015 EPAM Systems
2
+ #
3
+ #
4
+ # This file is part of Report Portal.
5
+ #
6
+ # Report Portal is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ReportPortal is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with Report Portal. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require 'logger'
20
+
21
+ module ReportPortal
22
+ class << self
23
+ # Monkey-patch for built-in Logger class
24
+ def patch_logger
25
+ Logger.class_eval do
26
+ alias_method :orig_add, :add
27
+ alias_method :orig_write, :<<
28
+ def add(severity, message = nil, progname = nil, &block)
29
+ ret = orig_add(severity, message, progname, &block)
30
+
31
+ unless severity < @level
32
+ progname ||= @progname
33
+ if message.nil?
34
+ if block_given?
35
+ message = yield
36
+ else
37
+ message = progname
38
+ progname = @progname
39
+ end
40
+ end
41
+ ReportPortal.send_log(format_severity(severity), format_message(format_severity(severity), Time.now, progname, message.to_s), ReportPortal.now)
42
+ end
43
+ ret
44
+ end
45
+
46
+ def <<(msg)
47
+ ret = orig_write(msg)
48
+ ReportPortal.send_log(ReportPortal::LOG_LEVELS[:unknown], msg.to_s, ReportPortal.now)
49
+ ret
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,36 @@
1
+ # Copyright 2015 EPAM Systems
2
+ #
3
+ #
4
+ # This file is part of Report Portal.
5
+ #
6
+ # Report Portal is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ReportPortal is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with Report Portal. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require 'logging'
20
+
21
+ require_relative '../../reportportal'
22
+
23
+ module ReportPortal
24
+ # Custom ReportPortal appender for 'logging' gem
25
+ class LoggingAppender < ::Logging::Appender
26
+ def write(event)
27
+ (str, lvl) = if event.instance_of?(::Logging::LogEvent)
28
+ [layout.format(event), event.level]
29
+ else
30
+ [event.to_s, ReportPortal::LOG_LEVELS[:unknown]]
31
+ end
32
+
33
+ ReportPortal.send_log(lvl, str, ReportPortal.now)
34
+ end
35
+ end
36
+ end