rspec-teamcity 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ module Rspec
2
+ module Teamcity
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,448 @@
1
+ # Copyright 2000-2014 JetBrains s.r.o.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative 'teamcity/utils/logger_util'
16
+ require_relative 'teamcity/rake_exceptions'
17
+ require_relative 'teamcity/rakerunner_consts'
18
+
19
+ require_relative 'teamcity/runner_common'
20
+ require_relative 'teamcity/utils/service_message_factory'
21
+ require_relative 'teamcity/utils/std_capture_helper'
22
+ require_relative 'teamcity/utils/runner_utils'
23
+ require_relative 'teamcity/utils/url_formatter'
24
+
25
+ module Spec
26
+ module Runner
27
+ module Formatter
28
+ class TeamcityFormatter < RSpec::Core::Formatters::BaseFormatter
29
+ include ::Rake::TeamCity::StdCaptureHelper
30
+ include ::Rake::TeamCity::RunnerUtils
31
+ include ::Rake::TeamCity::RunnerCommon
32
+ include ::Rake::TeamCity::Utils::UrlFormatter
33
+
34
+ RSpec::Core::Formatters.register self, :start, :close,
35
+ :example_group_started, :example_group_finished,
36
+ :example_started, :example_passed,
37
+ :example_pending, :example_failed,
38
+ :dump_summary
39
+
40
+ RUNNER_ISNT_COMPATIBLE_MESSAGE = "TeamCity Rake Runner Plugin isn't compatible with this RSpec version.\n\n"
41
+ TEAMCITY_FORMATTER_INTERNAL_ERRORS =[]
42
+ @@reporter_closed = false
43
+
44
+ ########## Teamcity #############################
45
+ def log(msg)
46
+ send_msg(msg)
47
+
48
+ # returns:
49
+ msg
50
+ end
51
+
52
+ def self.closed?()
53
+ @@reporter_closed
54
+ end
55
+
56
+ def self.close()
57
+ @@reporter_closed = true
58
+ end
59
+
60
+ ######## Spec formatter ########################
61
+ def initialize(output)
62
+ super
63
+
64
+ # Initializes
65
+ @groups_stack = []
66
+
67
+ # check out output stream is a Drb stream, in such case all commands should be send there
68
+ if !output.nil? && (defined? DRb::DRbObject) && output.kind_of?(DRb::DRbObject)
69
+ @@original_stdout = output
70
+ end
71
+
72
+ ###############################################
73
+
74
+ # Setups Test runner's MessageFactory
75
+ set_message_factory(::Rake::TeamCity::MessageFactory)
76
+ log_test_reporter_attached
77
+ end
78
+
79
+ def start(count_notification)
80
+ super
81
+
82
+ @example_count = count_notification.count
83
+
84
+ # Log count of examples
85
+ if ::Rake::TeamCity.is_in_idea_mode
86
+ log(@message_factory.create_tests_count(@example_count))
87
+ elsif ::Rake::TeamCity.is_in_buildserver_mode
88
+ log(@message_factory.create_progress_message("Starting.. (#{@example_count} examples)"))
89
+ end
90
+ debug_log("Examples: (#{@example_count} examples)")
91
+
92
+ # Saves STDOUT, STDERR because bugs in RSpec/formatter can break it
93
+ @sout, @serr = copy_stdout_stderr
94
+
95
+ debug_log("Starting..")
96
+ end
97
+
98
+ def example_group_started(group_notification)
99
+ super
100
+ my_add_example_group(group_notification.group.description, group_notification.group)
101
+ end
102
+
103
+
104
+ def example_group_finished(group_notification)
105
+ return if @groups_stack.empty?
106
+
107
+ # get and remove
108
+ current_group_description = @groups_stack.pop
109
+
110
+ debug_log("Closing example group(behaviour): [#{current_group_description}].")
111
+ log(@message_factory.create_suite_finished(current_group_description))
112
+ end
113
+
114
+ #########################################################
115
+ #########################################################
116
+ # start / fail /pass /pending method
117
+ #########################################################
118
+ #########################################################
119
+ @@RUNNING_EXAMPLES_STORAGE = {}
120
+
121
+ def example_started(example_notification)
122
+ example = example_notification.example
123
+ my_running_example_desc = example_description(example)
124
+ debug_log("example started [#{my_running_example_desc}] #{example}")
125
+
126
+ current_group_description = @groups_stack.last
127
+ my_running_example_full_name = "#{current_group_description} #{my_running_example_desc}"
128
+
129
+ # Send open event
130
+ debug_log("Example starting.. - full name = [#{my_running_example_full_name}], desc = [#{my_running_example_desc}]")
131
+ log(@message_factory.create_test_started(my_running_example_full_name, location_from_link(*extract_source_location_from_example(example))))
132
+
133
+ # Start capturing...
134
+ std_files = capture_output_start_external
135
+ started_at_ms = get_time_in_ms(example.execution_result.started_at)
136
+
137
+ debug_log('Output capturing started.')
138
+
139
+ put_data_to_storage(example, RunningExampleData.new(my_running_example_full_name, '', started_at_ms, *std_files))
140
+ end
141
+
142
+ def example_passed(example_notification)
143
+ example = example_notification.example
144
+ debug_log("example_passed[#{example_description(example)}] #{example}")
145
+
146
+ stop_capture_output_and_log_it(example)
147
+
148
+ close_test_block(example)
149
+ end
150
+
151
+ def example_failed(example_notification)
152
+ example = example_notification.example
153
+ debug_log("example failed[#{example_description(example)}] #{example}")
154
+
155
+ stop_capture_output_and_log_it(example)
156
+
157
+ # example service data
158
+ example_data = get_data_from_storage(example)
159
+ additional_flowid_suffix = example_data.additional_flowid_suffix
160
+ running_example_full_name = example_data.full_name
161
+
162
+ failure = example.exception
163
+ # Failure message:
164
+ def failure.expectation_not_met?
165
+ self.exception.kind_of?(RSpec::Expectations::ExpectationNotMetError)
166
+ end
167
+
168
+ def failure.pending_fixed?
169
+ self.exception.kind_of?(RSpec::Core::Pending::PendingExampleFixedError)
170
+ end
171
+
172
+ message = if failure.exception.nil?
173
+ # for unknown failure
174
+ '[Without Exception]'
175
+ elsif failure.expectation_not_met? || failure.pending_fixed?
176
+ failure.exception.message
177
+ else
178
+ # for other exception
179
+ "#{failure.exception.class.name}: #{failure.exception.message}"
180
+ end
181
+
182
+ # Backtrace
183
+ backtrace = example_notification.formatted_backtrace.join("\n")
184
+
185
+ debug_log("Example failing... full name = [#{running_example_full_name}], Message:\n#{message} \n\nBackrace:\n#{backtrace}\n\n, additional flowid suffix=[#{additional_flowid_suffix}]")
186
+
187
+ # Expectation failures will be shown as failures and other exceptions as Errors
188
+ if failure.expectation_not_met?
189
+ log(@message_factory.create_test_failed(running_example_full_name, message, backtrace))
190
+ else
191
+ log(@message_factory.create_test_error(running_example_full_name, message, backtrace))
192
+ end
193
+ close_test_block(example)
194
+ end
195
+
196
+
197
+ def example_pending(example_notification)
198
+ example = example_notification.example
199
+ message = example.execution_result.pending_message
200
+ debug_log("pending: #{example_description(example)}, #{message}, #{example}")
201
+
202
+ # stop capturing
203
+ stop_capture_output_and_log_it(example)
204
+
205
+ # example service data
206
+ example_data = get_data_from_storage(example)
207
+ additional_flowid_suffix = example_data.additional_flowid_suffix
208
+ running_example_full_name = example_data.full_name
209
+
210
+ debug_log("Example pending... [#{@groups_stack.last}].[#{running_example_full_name}] - #{message}, additional flowid suffix=[#{additional_flowid_suffix}]")
211
+ log(@message_factory.create_test_ignored(running_example_full_name, "Pending: #{message}"))
212
+
213
+ close_test_block(example)
214
+ end
215
+
216
+
217
+ # see snippet_extractor.rb
218
+ # Here we can add file link or show code lined
219
+ # def extra_failure_content(failure)
220
+ # require 'spec/runner/formatter/snippet_extractor'
221
+ # @snippet_extractor ||= SnippetExtractor.new
222
+ # " <pre class=\"ruby\"><code>#{@snippet_extractor.snippet(failure.exception)}</code></pre>"
223
+ # end
224
+
225
+ # For Rspec:
226
+ # 4 args - rspec < 2.0
227
+ # 0 args - rspec >= 2.0
228
+ def dump_summary(summary_notification)
229
+ duration = summary_notification.duration
230
+ example_count = summary_notification.example_count
231
+ failure_count = summary_notification.failure_count
232
+ pending_count = summary_notification.pending_count
233
+ # Repairs stdout and stderr just in case
234
+ repair_process_output
235
+ totals = "#{example_count} example#{'s' unless example_count == 1}"
236
+ totals << ", #{failure_count} failure#{'s' unless failure_count == 1}"
237
+ totals << ", #{example_count - failure_count - pending_count} passed"
238
+ totals << ", #{pending_count} pending" if pending_count > 0
239
+
240
+ # Total statistic
241
+ debug_log(totals)
242
+ log(totals)
243
+
244
+ # Time statistic from Spec Runner
245
+ status_message = "Finished in #{duration} seconds"
246
+ debug_log(status_message)
247
+ log(status_message)
248
+
249
+ #Really must be '@example_count == example_count', it is hack for spec trunk tests
250
+ if !@setup_failed && @example_count > example_count
251
+ msg = "#{RUNNER_ISNT_COMPATIBLE_MESSAGE}Error: Not all examples have been run! (#{count_notification} of #{@count_notification})\n#{gather_unfinished_examples_name}"
252
+
253
+ log_and_raise_internal_error msg
254
+ debug_log(msg)
255
+ end unless @groups_stack.empty?
256
+
257
+ unless @@RUNNING_EXAMPLES_STORAGE.empty?
258
+ # unfinished examples statistics
259
+ msg = RUNNER_ISNT_COMPATIBLE_MESSAGE + gather_unfinished_examples_name
260
+ log_and_raise_internal_error msg
261
+ end
262
+
263
+ # finishing
264
+ @@RUNNING_EXAMPLES_STORAGE.clear
265
+
266
+ debug_log("Summary finished.")
267
+ end
268
+
269
+ def close(notification)
270
+ tc_rspec_do_close
271
+ end
272
+
273
+ ###########################################################################
274
+ ###########################################################################
275
+ ###########################################################################
276
+ private
277
+
278
+ def gather_unfinished_examples_name
279
+ if @@RUNNING_EXAMPLES_STORAGE.empty?
280
+ return ""
281
+ end
282
+
283
+ msg = "Following examples weren't finished:"
284
+ count = 1
285
+ @@RUNNING_EXAMPLES_STORAGE.each { |key, value|
286
+ msg << "\n #{count}. Example : '#{value.full_name}'"
287
+ sout_str, serr_str = get_redirected_stdout_stderr_from_files(value.stdout_file_new, value.stderr_file_new)
288
+ unless sout_str.empty?
289
+ msg << "\n[Example Output]:\n#{sout_str}"
290
+ end
291
+ unless serr_str.empty?
292
+ msg << "\n[Example Error Output]:\n#{serr_str}"
293
+ end
294
+
295
+ count += 1
296
+ }
297
+ msg
298
+ end
299
+
300
+ def example_description(example)
301
+ example.description || '<noname>'
302
+ end
303
+
304
+
305
+ # Repairs SDOUT, STDERR from saved data
306
+ def repair_process_output
307
+ if !@sout.nil? && !@serr.nil?
308
+ @sout.flush
309
+ @serr.flush
310
+ reopen_stdout_stderr(@sout, @serr)
311
+ end
312
+ end
313
+
314
+ # Refactored initialize method. Is used for support rspec API < 1.1 and >= 1.1.
315
+ # spec_location_info : "$PATH:$LINE_NUM"
316
+ def my_add_example_group(group_desc, example_group = nil)
317
+ # New block starts.
318
+ @groups_stack << "#{group_desc}"
319
+
320
+ description = @groups_stack.last
321
+ debug_log("Adding example group(behaviour)...: [#{description}]...")
322
+ log(@message_factory.create_suite_started(description,
323
+ location_from_link(*extract_source_location_from_group(example_group))))
324
+ end
325
+
326
+ def close_test_block(example)
327
+ example_data = remove_data_from_storage(example)
328
+ finished_at_ms = get_time_in_ms(example.execution_result.finished_at)
329
+ duration = finished_at_ms - example_data.start_time_in_ms
330
+
331
+ additional_flowid_suffix = example_data.additional_flowid_suffix
332
+ running_example_full_name = example_data.full_name
333
+
334
+ debug_log("Example finishing... full example name = [#{running_example_full_name}], duration = #{duration} ms, additional flowid suffix=[#{additional_flowid_suffix}]")
335
+ diagnostic_info = "rspec [#{::RSpec::Core::Version::STRING}]" + ", f/s=(#{finished_at_ms}, #{example_data.start_time_in_ms}), duration=#{duration}, time.now=#{Time.now.to_s}, raw[:started_at]=#{example.execution_result.started_at.to_s}, raw[:finished_at]=#{example.execution_result.finished_at.to_s}, raw[:run_time]=#{example.execution_result.run_time.to_s}"
336
+
337
+ log(@message_factory.create_test_finished(running_example_full_name, duration, ::Rake::TeamCity.is_in_buildserver_mode ? nil : diagnostic_info))
338
+ end
339
+
340
+
341
+ def debug_log(string)
342
+ # Logs output.
343
+ SPEC_FORMATTER_LOG.log_msg(string)
344
+ end
345
+
346
+ def stop_capture_output_and_log_it(example)
347
+ example_data = get_data_from_storage(example)
348
+ additional_flowid_suffix = example_data.additional_flowid_suffix
349
+ running_example_full_name = example_data.full_name
350
+
351
+ stdout_string, stderr_string = capture_output_end_external(*example_data.get_std_files)
352
+ debug_log("Example capturing was stopped.")
353
+
354
+ debug_log("My stdOut: [#{stdout_string}] additional flow id=[#{additional_flowid_suffix}]")
355
+ if stdout_string && !stdout_string.empty?
356
+ log(@message_factory.create_test_output_message(running_example_full_name, true, stdout_string))
357
+ end
358
+ debug_log("My stdErr: [#{stderr_string}] additional flow id=[#{additional_flowid_suffix}]")
359
+ if stderr_string && !stderr_string.empty?
360
+ log(@message_factory.create_test_output_message(running_example_full_name, false, stderr_string))
361
+ end
362
+ end
363
+
364
+ ######################################################
365
+ ############# Assertions #############################
366
+ ######################################################
367
+ # def assert_example_valid(example_desc)
368
+ # if (example_desc != @my_running_example_desc)
369
+ # msg = "Example [#{example_desc}] doesn't correspond to current running example [#{@my_running_example_desc}]!"
370
+ # debug_log(msg)
371
+ # ... [send error to teamcity] ...
372
+ # close_test_block
373
+ #
374
+ # raise ::Rake::TeamCity::InnerException, msg, caller
375
+ # end
376
+ # end
377
+
378
+ # We doesn't support concurrent example groups executing
379
+ def assert_example_group_valid(group_description)
380
+ current_group_description = @groups_stack.last
381
+ if group_description != current_group_description
382
+ msg = "Example group(behaviour) [#{group_description}] doesn't correspond to current running example group [#{ current_group_description}]!"
383
+ debug_log(msg)
384
+
385
+ raise ::Rake::TeamCity::InnerException, msg, caller
386
+ end
387
+ end
388
+
389
+ ######################################################
390
+ def log_and_raise_internal_error(msg, raise_now = false)
391
+ debug_log(msg)
392
+
393
+ log(msg)
394
+ log(@message_factory.create_build_error_report("Failed to run RSpec.."))
395
+
396
+ excep_data = [msg, caller]
397
+ if raise_now
398
+ @@RUNNING_EXAMPLES_STORAGE.clear
399
+ raise ::Rake::TeamCity::InnerException, *excep_data
400
+ end
401
+ TEAMCITY_FORMATTER_INTERNAL_ERRORS << excep_data
402
+ end
403
+
404
+ def get_data_from_storage(example)
405
+ @@RUNNING_EXAMPLES_STORAGE[example.object_id]
406
+ end
407
+
408
+ def remove_data_from_storage(example)
409
+ @@RUNNING_EXAMPLES_STORAGE.delete(example.object_id)
410
+ end
411
+
412
+ def put_data_to_storage(example, data)
413
+ @@RUNNING_EXAMPLES_STORAGE[example.object_id] = data
414
+ end
415
+
416
+ ######################################################
417
+ ######################################################
418
+ #TODO remove flowid
419
+ class RunningExampleData
420
+ attr_reader :full_name # full task name, example name in build log
421
+ # TODO: Remove!
422
+ attr_reader :additional_flowid_suffix # to support concurrently running examples
423
+ attr_reader :start_time_in_ms # start time of example
424
+ attr_reader :stdout_file_old # before capture
425
+ attr_reader :stderr_file_old # before capture
426
+ attr_reader :stdout_file_new #current capturing storage
427
+ attr_reader :stderr_file_new # current capturing storage
428
+
429
+ def initialize(full_name, additional_flowid_suffix, start_time_in_ms, stdout_file_old, stderr_file_old, stdout_file_new, stderr_file_new)
430
+ @full_name = full_name
431
+ # TODO: Remove!
432
+ @additional_flowid_suffix = additional_flowid_suffix
433
+ @start_time_in_ms = start_time_in_ms
434
+ @stdout_file_old = stdout_file_old
435
+ @stderr_file_old = stderr_file_old
436
+ @stdout_file_new = stdout_file_new
437
+ @stderr_file_new = stderr_file_new
438
+ end
439
+
440
+ def get_std_files
441
+ return @stdout_file_old, @stderr_file_old, @stdout_file_new, @stderr_file_new
442
+ end
443
+ end
444
+ end
445
+ end
446
+ end
447
+ end
448
+