reportportal 0.7

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