rspec-teamcity 0.0.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,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
+