rspec-buildkite-analytics 0.3.5 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d55751f8e88a6ac7ad250c0c1e2b382dcaeb2d23504aa6d110a22a4a61af8d06
4
- data.tar.gz: b21d3d7fe03e1d504870b2d1fba0f188ab94e1f5e1c5c3fa3f11b1536a9f99b1
3
+ metadata.gz: 1ca7801b3e7bc48f52a372df29c553c9e2dad5bffdab1bf8dff0b20f0173b987
4
+ data.tar.gz: 22d6197b37f465afa1a2a309ba4f3780bafef928d626768a5d4d3fa6d214a809
5
5
  SHA512:
6
- metadata.gz: 8bb6efbdc7f9b4f06ab3b0793c9ef5a1c2b41429a755040d0c6b558fbb24b308e59cd862a646b86717bb5bebf4464eec45e3551db043b8332ed37a94b642f789
7
- data.tar.gz: 97089293de5331c1329979fd853ff15297e4460aaec7631867e2031398a31603296e53174da6a398134fe7a54bfa9b1e7082817acb989e6f74978a574afae9fc
6
+ metadata.gz: '0380c462ae87c2dd2dbb830bbcdc93bef621b74f2f516c465b37e633679e9e0c887fe8557356aaf85abd8e7fe179110b86aabceb339057cd89e37c00afcac107'
7
+ data.tar.gz: 9b6810ff8529454f543b70f521b34906223e2ee02ad1f0d4cd261d4bfe6e2c4ed21eb76c8eddfb08818c9c8a07d07cb62c3c206c958c48743a47c1dfde83f9b3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rspec-buildkite-analytics (0.3.5)
4
+ rspec-buildkite-analytics (0.6.0)
5
5
  activesupport (>= 5.2, <= 7.0)
6
6
  rspec-core (~> 3.10)
7
7
  rspec-expectations (~> 3.10)
@@ -18,10 +18,10 @@ GEM
18
18
  zeitwerk (~> 2.3)
19
19
  concurrent-ruby (1.1.9)
20
20
  diff-lcs (1.4.4)
21
- i18n (1.8.10)
21
+ i18n (1.8.11)
22
22
  concurrent-ruby (~> 1.0)
23
23
  minitest (5.14.4)
24
- rake (13.0.3)
24
+ rake (13.0.6)
25
25
  rspec (3.10.0)
26
26
  rspec-core (~> 3.10.0)
27
27
  rspec-expectations (~> 3.10.0)
@@ -31,10 +31,10 @@ GEM
31
31
  rspec-expectations (3.10.1)
32
32
  diff-lcs (>= 1.2.0, < 2.0)
33
33
  rspec-support (~> 3.10.0)
34
- rspec-mocks (3.10.1)
34
+ rspec-mocks (3.10.2)
35
35
  diff-lcs (>= 1.2.0, < 2.0)
36
36
  rspec-support (~> 3.10.0)
37
- rspec-support (3.10.1)
37
+ rspec-support (3.10.3)
38
38
  tzinfo (2.0.4)
39
39
  concurrent-ruby (~> 1.0)
40
40
  websocket (1.2.9)
data/README.md CHANGED
@@ -15,10 +15,7 @@ end
15
15
 
16
16
  Configure your API key:
17
17
  ```ruby
18
- RSpec::Buildkite::Analytics.configure do |config|
19
- config.suite_key = "........"
20
- # other config
21
- end
18
+ RSpec::Buildkite::Analytics.configure(token: "........")
22
19
  ```
23
20
 
24
21
  Run bundler to install the gem and update your `Gemfile.lock`:
@@ -13,12 +13,14 @@ module RSpec::Buildkite::Analytics::CI
13
13
  "commit_sha" => ENV["BUILDKITE_COMMIT"],
14
14
  "number" => ENV["BUILDKITE_BUILD_NUMBER"],
15
15
  "job_id" => ENV["BUILDKITE_JOB_ID"],
16
- "message" => ENV["BUILDKITE_MESSAGE"]
16
+ "message" => ENV["BUILDKITE_MESSAGE"],
17
+ "debug" => ENV["BUILDKITE_ANALYTICS_DEBUG_ENABLED"]
17
18
  }
18
19
  else
19
20
  {
20
21
  "CI" => nil,
21
- "key" => SecureRandom.uuid
22
+ "key" => SecureRandom.uuid,
23
+ "debug" => ENV["BUILDKITE_ANALYTICS_DEBUG_ENABLED"]
22
24
  }
23
25
  end
24
26
  end
@@ -1,6 +1,10 @@
1
+ require "time"
2
+
1
3
  module RSpec::Buildkite::Analytics
2
4
  class Reporter
3
- RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending
5
+ RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending, :dump_summary
6
+
7
+ attr_reader :output
4
8
 
5
9
  def initialize(output)
6
10
  @output = output
@@ -14,12 +18,82 @@ module RSpec::Buildkite::Analytics
14
18
 
15
19
  if trace
16
20
  trace.example = example
21
+ trace.failure_reason, trace.failure_expanded = failure_info(notification) if example.execution_result.status == :failed
17
22
  RSpec::Buildkite::Analytics.session&.write_result(trace)
18
23
  end
19
24
  end
20
25
 
26
+ def dump_summary(notification)
27
+ if RSpec::Buildkite::Analytics.session.present?
28
+ examples_count = {
29
+ examples: notification.examples.count,
30
+ failed: notification.failed_examples.count,
31
+ pending: notification.pending_examples.count,
32
+ errors_outside_examples: notification.errors_outside_of_examples_count
33
+ }
34
+
35
+ RSpec::Buildkite::Analytics.session.close(examples_count)
36
+
37
+ # Write the debug file, if debug mode is enabled
38
+ if RSpec::Buildkite::Analytics.debug_enabled
39
+ filename = "#{RSpec::Buildkite::Analytics.debug_filepath}/bk-analytics-#{Time.now.strftime("%F-%R:%S")}-#{ENV["BUILDKITE_JOB_ID"]}.log.gz"
40
+
41
+ File.open(filename, "wb") do |f|
42
+ gz = Zlib::GzipWriter.new(f)
43
+ gz.puts(RSpec::Buildkite::Analytics.session.logger.to_array)
44
+ gz.close
45
+ end
46
+ end
47
+ end
48
+ end
49
+
21
50
  alias_method :example_passed, :handle_example
22
51
  alias_method :example_failed, :handle_example
23
52
  alias_method :example_pending, :handle_example
53
+
54
+ private
55
+
56
+ def failure_info(notification)
57
+ failure_expanded = []
58
+
59
+ if notification.exception.class == RSpec::Expectations::MultipleExpectationsNotMetError
60
+ failure_reason = notification.exception.summary
61
+ notification.exception.all_exceptions.each do |exception|
62
+ # an example with multiple failures doesn't give us a
63
+ # separate message lines and backtrace object to send, so
64
+ # I've reached into RSpec internals and duplicated the
65
+ # construction of these
66
+ message_lines = RSpec::Core::Formatters::ExceptionPresenter.new(exception, notification.example).colorized_message_lines
67
+
68
+ failure_expanded << {
69
+ expanded: format_message_lines(message_lines),
70
+ backtrace: RSpec.configuration.backtrace_formatter.format_backtrace(exception.backtrace)
71
+ }
72
+ end
73
+ else
74
+ failure_reason = strip_diff_colors(notification.colorized_message_lines[0])
75
+
76
+ # the second line is always whitespace padding
77
+ message_lines = notification.colorized_message_lines[2..]
78
+
79
+ failure_expanded << {
80
+ expanded: format_message_lines(message_lines),
81
+ backtrace: notification.formatted_backtrace
82
+ }
83
+ end
84
+
85
+ return failure_reason, failure_expanded
86
+ end
87
+
88
+ def format_message_lines(message_lines)
89
+ message_lines.map! { |l| strip_diff_colors(l) }
90
+ # the last line is sometimes blank, depending on the error reported
91
+ message_lines.pop if message_lines.last.blank?
92
+ message_lines
93
+ end
94
+
95
+ def strip_diff_colors(string)
96
+ string.gsub(/\e\[([;\d]+)?m/, '')
97
+ end
24
98
  end
25
99
  end
@@ -12,26 +12,52 @@ module RSpec::Buildkite::Analytics
12
12
  class RejectedSubscription < StandardError; end
13
13
  class InitialConnectionFailure < StandardError; end
14
14
 
15
+ class Logger
16
+ def initialize
17
+ @log = Queue.new
18
+ end
19
+
20
+ def write(str)
21
+ @log << "#{Time.now.strftime("%F-%R:%S.%9N")} #{Thread.current} #{str}"
22
+ end
23
+
24
+ def to_array
25
+ # This empty check is important cos calling pop on a Queue is blocking until
26
+ # it's not empty
27
+ if @log.empty?
28
+ []
29
+ else
30
+ Array.new(@log.size) { @log.pop }
31
+ end
32
+ end
33
+ end
34
+
35
+ attr_reader :logger
36
+
15
37
  def initialize(url, authorization_header, channel)
16
- @queue = Queue.new
38
+ @establish_subscription_queue = Queue.new
17
39
  @channel = channel
18
40
 
19
41
  @unconfirmed_idents = {}
20
42
  @idents_mutex = Mutex.new
43
+ @send_queue = Queue.new
21
44
  @empty = ConditionVariable.new
22
45
  @closing = false
46
+ @eot_queued = false
47
+ @eot_queued_mutex = Mutex.new
23
48
  @reconnection_mutex = Mutex.new
24
49
 
25
50
  @url = url
26
51
  @authorization_header = authorization_header
27
52
 
53
+ @logger = Logger.new
54
+
28
55
  connect
29
56
  rescue TimeoutError, InitialConnectionFailure => e
30
57
  $stderr.puts "rspec-buildkite-analytics could not establish an initial connection with Buildkite due to #{e.message}. You may be missing some data for this test suite, please contact support."
31
58
  end
32
59
 
33
60
  def disconnected(connection)
34
- reconnection_count = 0
35
61
  @reconnection_mutex.synchronize do
36
62
  # When the first thread detects a disconnection, it calls the disconnect method
37
63
  # with the current connection. This thread grabs the reconnection mutex and does the
@@ -43,16 +69,21 @@ module RSpec::Buildkite::Analytics
43
69
  # time the mutex is released, the value of @connection has been refreshed, and so
44
70
  # the second thread returns early and does not reattempt the reconnection.
45
71
  return unless connection == @connection
72
+ @logger.write("starting reconnection")
73
+
74
+ reconnection_count = 0
46
75
 
47
76
  begin
48
77
  reconnection_count += 1
49
78
  connect
50
79
  rescue SocketConnection::HandshakeError, RejectedSubscription, TimeoutError, SocketConnection::SocketError => e
80
+ @logger.write("failed reconnection attempt #{reconnection_count} due to #{e}")
51
81
  if reconnection_count > MAX_RECONNECTION_ATTEMPTS
52
82
  $stderr.puts "rspec-buildkite-analytics experienced a disconnection and could not reconnect to Buildkite due to #{e.message}. Please contact support."
53
83
  raise e
54
84
  else
55
85
  sleep(WAIT_BETWEEN_RECONNECTIONS)
86
+ @logger.write("retrying reconnection")
56
87
  retry
57
88
  end
58
89
  end
@@ -60,8 +91,10 @@ module RSpec::Buildkite::Analytics
60
91
  retransmit
61
92
  end
62
93
 
63
- def close()
94
+ def close(examples_count)
64
95
  @closing = true
96
+ @examples_count = examples_count
97
+ @logger.write("closing socket connection")
65
98
 
66
99
  # Because the server only sends us confirmations after every 10mb of
67
100
  # data it uploads to S3, we'll never get confirmation of the
@@ -70,7 +103,10 @@ module RSpec::Buildkite::Analytics
70
103
  send_eot
71
104
 
72
105
  @idents_mutex.synchronize do
73
- # Here, we sleep for 75 seconds while waiting for the server to confirm the last idents.
106
+ @logger.write("waiting for last confirm")
107
+ # Here, we sleep for 75 seconds while waiting for the send
108
+ # queue to be drained and for the server to confirm the last
109
+ # idents.
74
110
  # We are woken up when the unconfirmed_idents is empty, and given back the mutex to
75
111
  # continue operation.
76
112
  @empty.wait(@idents_mutex, CONFIRMATION_TIMEOUT) unless @unconfirmed_idents.empty?
@@ -78,6 +114,9 @@ module RSpec::Buildkite::Analytics
78
114
 
79
115
  # Then we always disconnect cos we can't wait forever? 🤷‍♀️
80
116
  @connection.close
117
+ # We kill the write thread cos it's got a while loop in it, so it won't finish otherwise
118
+ @write_thread&.kill
119
+ @logger.write("socket connection closed")
81
120
  end
82
121
 
83
122
  def handle(_connection, data)
@@ -86,11 +125,14 @@ module RSpec::Buildkite::Analytics
86
125
  when "ping"
87
126
  # In absence of other message, the server sends us a ping every 3 seconds
88
127
  # We are currently not doing anything with these
128
+ @logger.write("received ping")
89
129
  when "welcome", "confirm_subscription"
90
130
  # Push these two messages onto the queue, so that we block on waiting for the
91
131
  # initializing phase to complete
92
- @queue.push(data)
132
+ @establish_subscription_queue.push(data)
133
+ @logger.write("received #{data['type']}")
93
134
  when "reject_subscription"
135
+ @logger.write("received rejected_subscription")
94
136
  raise RejectedSubscription
95
137
  else
96
138
  process_message(data)
@@ -98,11 +140,9 @@ module RSpec::Buildkite::Analytics
98
140
  end
99
141
 
100
142
  def write_result(result)
101
- result_as_json = result.as_json
102
-
103
- add_unconfirmed_idents(result.id, result_as_json)
143
+ queue_and_track_result(result.id, result.as_hash)
104
144
 
105
- transmit_results([result_as_json])
145
+ @logger.write("added #{result.id} to send queue")
106
146
  end
107
147
 
108
148
  def unconfirmed_idents_count
@@ -113,18 +153,9 @@ module RSpec::Buildkite::Analytics
113
153
 
114
154
  private
115
155
 
116
- def transmit_results(results_as_json)
117
- @connection.transmit({
118
- "identifier" => @channel,
119
- "command" => "message",
120
- "data" => {
121
- "action" => "record_results",
122
- "results" => results_as_json
123
- }.to_json
124
- })
125
- end
126
-
127
156
  def connect
157
+ @logger.write("starting socket connection process")
158
+
128
159
  @connection = SocketConnection.new(self, @url, {
129
160
  "Authorization" => @authorization_header,
130
161
  })
@@ -137,11 +168,54 @@ module RSpec::Buildkite::Analytics
137
168
  })
138
169
 
139
170
  wait_for_confirm
171
+
172
+ @logger.write("connected")
173
+
174
+ # As this connect method can be called multiple times in the
175
+ # reconnection process, kill prev write threads (if any) before
176
+ # setting up the new one
177
+ @write_thread&.kill
178
+
179
+ @write_thread = Thread.new do
180
+ @logger.write("hello from write thread")
181
+ # Pretty sure this eternal loop is fine cos the call to queue.pop is blocking
182
+ loop do
183
+ data = @send_queue.pop
184
+ message_type = data["action"]
185
+
186
+ if message_type == "end_of_transmission"
187
+ # Because of the unpredictable sequencing between the test suite finishing
188
+ # (EOT gets queued) and disconnections happening (retransmit results gets
189
+ # queued), we don't want to send an EOT before any retransmits are sent.
190
+ if @send_queue.length > 0
191
+ @send_queue << data
192
+ @logger.write("putting eot at back of queue")
193
+ next
194
+ end
195
+ @eot_queued_mutex.synchronize do
196
+ @eot_queued = false
197
+ end
198
+ end
199
+
200
+ @connection.transmit({
201
+ "identifier" => @channel,
202
+ "command" => "message",
203
+ "data" => data.to_json
204
+ })
205
+
206
+ if RSpec::Buildkite::Analytics.debug_enabled
207
+ ids = if message_type == "record_results"
208
+ data["results"].map { |result| result["id"] }
209
+ end
210
+ @logger.write("transmitted #{message_type} #{ids}")
211
+ end
212
+ end
213
+ end
140
214
  end
141
215
 
142
216
  def pop_with_timeout(message_type)
143
217
  Timeout.timeout(30, RSpec::Buildkite::Analytics::TimeoutError, "Timeout: Waited 30 seconds for #{message_type}") do
144
- @queue.pop
218
+ @establish_subscription_queue.pop
145
219
  end
146
220
  end
147
221
 
@@ -161,9 +235,16 @@ module RSpec::Buildkite::Analytics
161
235
  end
162
236
  end
163
237
 
164
- def add_unconfirmed_idents(ident, data)
238
+ def queue_and_track_result(ident, result_as_hash)
165
239
  @idents_mutex.synchronize do
166
- @unconfirmed_idents[ident] = data
240
+ @unconfirmed_idents[ident] = result_as_hash
241
+
242
+ data = {
243
+ "action" => "record_results",
244
+ "results" => [result_as_hash]
245
+ }
246
+
247
+ @send_queue << data
167
248
  end
168
249
  end
169
250
 
@@ -174,26 +255,37 @@ module RSpec::Buildkite::Analytics
174
255
  # Remove received idents from unconfirmed_idents
175
256
  idents.each { |key| @unconfirmed_idents.delete(key) }
176
257
 
258
+ @logger.write("received confirm for indentifiers: #{idents.join(", ")}")
259
+
177
260
  # This @empty ConditionVariable broadcasts every time that @unconfirmed_idents is
178
261
  # empty, which will happen about every 10mb of data as that's when the server
179
262
  # sends back confirmations.
180
263
  #
181
264
  # However, there aren't any threads waiting on this signal until after we
182
265
  # send the EOT message, so the prior broadcasts shouldn't do anything.
183
- @empty.broadcast if @unconfirmed_idents.empty?
266
+ if @unconfirmed_idents.empty?
267
+ @empty.broadcast
268
+ @logger.write("broadcast empty")
269
+ else
270
+ @logger.write("still waiting on confirm for #{@unconfirmed_idents.keys}")
271
+ end
184
272
  end
185
273
  end
186
274
 
187
275
  def send_eot
188
- # Expect server to respond with data of indentifiers last upload part
189
-
190
- @connection.transmit({
191
- "identifier" => @channel,
192
- "command" => "message",
193
- "data" => {
194
- "action" => "end_of_transmission"
195
- }.to_json
196
- })
276
+ @eot_queued_mutex.synchronize do
277
+ return if @eot_queued
278
+ # Expect server to respond with data of indentifiers last upload part
279
+ data = {
280
+ "action" => "end_of_transmission",
281
+ "examples_count" => @examples_count.to_json
282
+ }
283
+
284
+ @send_queue << data
285
+ @eot_queued = true
286
+
287
+ @logger.write("added EOT to send queue")
288
+ end
197
289
  end
198
290
 
199
291
  def process_message(data)
@@ -205,16 +297,26 @@ module RSpec::Buildkite::Analytics
205
297
  remove_unconfirmed_idents(data["message"]["confirm"])
206
298
  else
207
299
  # unhandled message
300
+ @logger.write("received unhandled message #{data["message"]}")
208
301
  end
209
302
  end
210
303
 
211
304
  def retransmit
212
- data = @idents_mutex.synchronize do
213
- @unconfirmed_idents.values
305
+ @idents_mutex.synchronize do
306
+ results = @unconfirmed_idents.values
307
+
308
+ # queue the contents of the buffer, unless it's empty
309
+ unless results.empty?
310
+ data = {
311
+ "action" => "record_results",
312
+ "results" => results
313
+ }
314
+
315
+ @send_queue << data
316
+ @logger.write("queueing up retransmitted results #{@unconfirmed_idents.keys}")
317
+ end
214
318
  end
215
319
 
216
- # send the contents of the buffer, unless it's empty
217
- transmit_results(data) unless data.empty?
218
320
  # if we were disconnected in the closing phase, then resend the EOT
219
321
  # message so the server can persist the last upload part
220
322
  send_eot if @closing
@@ -66,6 +66,7 @@ module RSpec::Buildkite::Analytics
66
66
  # Setting up a new thread that listens on the socket, and processes incoming
67
67
  # comms from the server
68
68
  @thread = Thread.new do
69
+ @session.logger.write("listening in on socket")
69
70
  frame = WebSocket::Frame::Incoming::Client.new
70
71
 
71
72
  while @socket
@@ -75,20 +76,25 @@ module RSpec::Buildkite::Analytics
75
76
  @session.handle(self, data.data)
76
77
  end
77
78
  end
78
- rescue EOFError
79
+ rescue EOFError, Errno::ECONNRESET => e
80
+ @session.logger.write("#{e}")
79
81
  if @socket
82
+ @session.logger.write("attempting disconnected flow")
80
83
  @session.disconnected(self)
81
84
  disconnect
82
85
  end
83
86
  rescue IOError
84
87
  # This is fine to ignore
88
+ @session.logger.write("IOError")
85
89
  rescue IndexError
86
90
  # I don't like that we're doing this but I think it's the best of the options
87
91
  #
88
92
  # This relates to this issue https://github.com/ruby/openssl/issues/452
89
93
  # A fix for it has been released but the repercussions of overriding
90
94
  # the OpenSSL version in the stdlib seem worse than catching this error here.
95
+ @session.logger.write("IndexError")
91
96
  if @socket
97
+ @session.logger.write("attempting disconnected flow")
92
98
  @session.disconnected(self)
93
99
  disconnect
94
100
  end
@@ -102,8 +108,10 @@ module RSpec::Buildkite::Analytics
102
108
  raw_data = data.to_json
103
109
  frame = WebSocket::Frame::Outgoing::Client.new(data: raw_data, type: :text, version: @version)
104
110
  @socket.write(frame.to_s)
105
- rescue Errno::EPIPE, OpenSSL::SSL::SSLError => e
111
+ rescue Errno::EPIPE, Errno::ECONNRESET, OpenSSL::SSL::SSLError => e
106
112
  return unless @socket
113
+ return if type == :close
114
+ @session.logger.write("got #{e}, attempting disconnected flow")
107
115
  @session.disconnected(self)
108
116
  disconnect
109
117
  rescue IndexError
@@ -112,13 +120,16 @@ module RSpec::Buildkite::Analytics
112
120
  # This relates to this issue https://github.com/ruby/openssl/issues/452
113
121
  # A fix for it has been released but the repercussions of overriding
114
122
  # the OpenSSL version in the stdlib seem worse than catching this error here.
123
+ @session.logger.write("IndexError")
115
124
  if @socket
125
+ @session.logger.write("attempting disconnected flow")
116
126
  @session.disconnected(self)
117
127
  disconnect
118
128
  end
119
129
  end
120
130
 
121
131
  def close
132
+ @session.logger.write("socket close")
122
133
  transmit(nil, type: :close)
123
134
  disconnect
124
135
  end
@@ -126,6 +137,7 @@ module RSpec::Buildkite::Analytics
126
137
  private
127
138
 
128
139
  def disconnect
140
+ @session.logger.write("socket disconnect")
129
141
  socket = @socket
130
142
  @socket = nil
131
143
  socket&.close
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/hash/indifferent_access"
4
+
3
5
  module RSpec::Buildkite::Analytics
4
6
  class Tracer
5
7
  class Span
@@ -13,15 +15,15 @@ module RSpec::Buildkite::Analytics
13
15
  @children = []
14
16
  end
15
17
 
16
- def as_json
18
+ def as_hash
17
19
  {
18
20
  section: section,
19
21
  start_at: start_at,
20
22
  end_at: end_at,
21
23
  duration: end_at - start_at,
22
24
  detail: detail,
23
- children: children.map(&:as_json),
24
- }
25
+ children: children.map(&:as_hash),
26
+ }.with_indifferent_access
25
27
  end
26
28
  end
27
29
 
@@ -57,7 +59,7 @@ module RSpec::Buildkite::Analytics
57
59
  end
58
60
 
59
61
  def history
60
- @top.as_json
62
+ @top.as_hash
61
63
  end
62
64
  end
63
65
  end
@@ -22,22 +22,15 @@ require "securerandom"
22
22
  module RSpec::Buildkite::Analytics
23
23
  class Uploader
24
24
  class Trace
25
- attr_accessor :example
25
+ attr_accessor :example, :failure_reason, :failure_expanded
26
26
  attr_reader :id, :history
27
27
 
28
28
  def initialize(example, history)
29
29
  @id = SecureRandom.uuid
30
30
  @example = example
31
31
  @history = history
32
- end
33
-
34
- def failure_message
35
- case example.exception
36
- when RSpec::Expectations::ExpectationNotMetError
37
- example.exception.message
38
- when Exception
39
- "#{example.exception.class}: #{example.exception.message}"
40
- end
32
+ @failure_reason = nil
33
+ @failure_expanded = []
41
34
  end
42
35
 
43
36
  def result_state
@@ -48,7 +41,7 @@ module RSpec::Buildkite::Analytics
48
41
  end
49
42
  end
50
43
 
51
- def as_json
44
+ def as_hash
52
45
  {
53
46
  id: @id,
54
47
  scope: example.example_group.metadata[:full_description],
@@ -57,15 +50,16 @@ module RSpec::Buildkite::Analytics
57
50
  location: example.location,
58
51
  file_name: generate_file_name(example),
59
52
  result: result_state,
60
- failure: failure_message,
53
+ failure_reason: failure_reason,
54
+ failure_expanded: failure_expanded,
61
55
  history: history,
62
- }
56
+ }.with_indifferent_access.compact
63
57
  end
64
58
 
65
59
  private
66
60
 
67
61
  def generate_file_name(example)
68
- file_path_regex = /^(.*?\.rb)/
62
+ file_path_regex = /^(.*?\.(rb|feature))/
69
63
  identifier_file_name = example.id[file_path_regex]
70
64
  location_file_name = example.location[file_path_regex]
71
65
 
@@ -121,7 +115,8 @@ module RSpec::Buildkite::Analytics
121
115
  "Content-Type" => "application/json",
122
116
  })
123
117
  contact.body = {
124
- run_env: CI.env
118
+ run_env: CI.env,
119
+ format: "websocket"
125
120
  }.to_json
126
121
 
127
122
  response = begin
@@ -141,10 +136,12 @@ module RSpec::Buildkite::Analytics
141
136
  end
142
137
  else
143
138
  request_id = response.to_hash["x-request-id"]
144
- puts "Buildkite Test Analytics: Unknown error. If this error persists, please contact support+analytics@buildkite.com with this request ID `#{request_id}`."
139
+ puts "rspec-buildkite-analytics could not establish an initial connection with Buildkite. You may be missing some data for this test suite, please contact support."
145
140
  end
146
141
  else
147
- puts "Buildkite Test Analytics: No Suite API key provided. You can get the API key from your Suite settings page."
142
+ if !!ENV["BUILDKITE_BUILD_ID"]
143
+ puts "Buildkite Test Analytics: No Suite API key provided. You can get the API key from your Suite settings page."
144
+ end
148
145
  end
149
146
  end
150
147
 
@@ -162,12 +159,6 @@ module RSpec::Buildkite::Analytics
162
159
  trace = RSpec::Buildkite::Analytics::Uploader::Trace.new(example, tracer.history)
163
160
  RSpec::Buildkite::Analytics.uploader.traces << trace
164
161
  end
165
-
166
- config.after(:suite) do
167
- # This needs the lonely operater as the session will be nil
168
- # if auth against the API token fails
169
- RSpec::Buildkite::Analytics.session&.close
170
- end
171
162
  end
172
163
 
173
164
  RSpec::Buildkite::Analytics::Network.configure
@@ -3,7 +3,7 @@
3
3
  module RSpec
4
4
  module Buildkite
5
5
  module Analytics
6
- VERSION = "0.3.5"
6
+ VERSION = "0.6.0"
7
7
  end
8
8
  end
9
9
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "timeout"
4
+ require "tmpdir"
4
5
 
5
6
  require_relative "analytics/version"
6
7
 
@@ -15,11 +16,15 @@ module RSpec::Buildkite::Analytics
15
16
  attr_accessor :url
16
17
  attr_accessor :uploader
17
18
  attr_accessor :session
19
+ attr_accessor :debug_enabled
20
+ attr_accessor :debug_filepath
18
21
  end
19
22
 
20
- def self.configure(token: nil, url: nil)
23
+ def self.configure(token: nil, url: nil, debug_enabled: false, debug_filepath: nil)
21
24
  self.api_token = token || ENV["BUILDKITE_ANALYTICS_TOKEN"]
22
25
  self.url = url || DEFAULT_URL
26
+ self.debug_enabled = debug_enabled || !!(ENV["BUILDKITE_ANALYTICS_DEBUG_ENABLED"])
27
+ self.debug_filepath = debug_filepath || ENV["BUILDKITE_ANALYTICS_DEBUG_FILEPATH"] || Dir.tmpdir
23
28
 
24
29
  require_relative "analytics/uploader"
25
30
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-buildkite-analytics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Buildkite
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-21 00:00:00.000000000 Z
11
+ date: 2021-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport