rspec-buildkite-analytics 0.4.0 → 0.5.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44dd3ce4c2f47452f1eb52c96a2b86a7f49dd971d38350152b773dd343a85969
4
- data.tar.gz: 81fad9923f72c5a4e733b9e93917323c1037a6a0fa9ba4c0f76e9c3d0820f06b
3
+ metadata.gz: a9fc407bacf4405e3d3e2ee4043a9c71f20d75a363d3ba33948b37403a6e8de4
4
+ data.tar.gz: 67f8b42a6ac98518d784eea442fb9efc5a1c8dd5ca0037cc5ce5664b180b75ec
5
5
  SHA512:
6
- metadata.gz: 6eb38e960fa8c7c8e6b4ff29070d188db20f0868c316a7b2b51051cd733793c8c86ea736385ddefa3eab04afb585d20ebef5fcf3bb7d3a2ee62513d9dcf448d5
7
- data.tar.gz: dec76a5f780c47f7f340b83501450545ccd810dbf9b0c43755d4fff300197da750b9411a49eb757f41a094d7058780a78f7a6d859fef34514105be6e97fd8765
6
+ metadata.gz: 6c368891ad4401ac9112a618953686c79b2e69bd33824be1477e3416f99f03233210d80ad3d3a1eccd9b251f595f2b879fd79de0eb1d80193544a6812e8ac0b9
7
+ data.tar.gz: 2cf265c336146324d21b06d8c3ec4827848dcb0580ac626d49cd82eaab10ef5101f9398bdad10c118c5dfd42276ed2e70441f8c42b6881a8d68bf6a05da73485
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rspec-buildkite-analytics (0.4.0)
4
+ rspec-buildkite-analytics (0.5.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)
@@ -1,3 +1,5 @@
1
+ require "time"
2
+
1
3
  module RSpec::Buildkite::Analytics
2
4
  class Reporter
3
5
  RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending, :dump_summary
@@ -33,7 +35,7 @@ module RSpec::Buildkite::Analytics
33
35
 
34
36
  # Write the debug file, if debug mode is enabled
35
37
  if RSpec::Buildkite::Analytics.debug_enabled
36
- filename = "#{RSpec::Buildkite::Analytics.debug_filepath}/bk-analytics-#{DateTime.current.strftime("%F-%R:%S")}-#{ENV["BUILDKITE_JOB_ID"]}.log.gz"
38
+ filename = "#{RSpec::Buildkite::Analytics.debug_filepath}/bk-analytics-#{Time.now.strftime("%F-%R:%S")}-#{ENV["BUILDKITE_JOB_ID"]}.log.gz"
37
39
 
38
40
  File.open(filename, "wb") do |f|
39
41
  gz = Zlib::GzipWriter.new(f)
@@ -35,13 +35,16 @@ module RSpec::Buildkite::Analytics
35
35
  attr_reader :logger
36
36
 
37
37
  def initialize(url, authorization_header, channel)
38
- @queue = Queue.new
38
+ @establish_subscription_queue = Queue.new
39
39
  @channel = channel
40
40
 
41
41
  @unconfirmed_idents = {}
42
42
  @idents_mutex = Mutex.new
43
+ @send_queue = Queue.new
43
44
  @empty = ConditionVariable.new
44
45
  @closing = false
46
+ @eot_queued = false
47
+ @eot_queued_mutex = Mutex.new
45
48
  @reconnection_mutex = Mutex.new
46
49
 
47
50
  @url = url
@@ -55,7 +58,6 @@ module RSpec::Buildkite::Analytics
55
58
  end
56
59
 
57
60
  def disconnected(connection)
58
- reconnection_count = 0
59
61
  @reconnection_mutex.synchronize do
60
62
  # When the first thread detects a disconnection, it calls the disconnect method
61
63
  # with the current connection. This thread grabs the reconnection mutex and does the
@@ -69,6 +71,8 @@ module RSpec::Buildkite::Analytics
69
71
  return unless connection == @connection
70
72
  @logger.write("starting reconnection")
71
73
 
74
+ reconnection_count = 0
75
+
72
76
  begin
73
77
  reconnection_count += 1
74
78
  connect
@@ -100,7 +104,9 @@ module RSpec::Buildkite::Analytics
100
104
 
101
105
  @idents_mutex.synchronize do
102
106
  @logger.write("waiting for last confirm")
103
- # Here, we sleep for 75 seconds while waiting for the server to confirm the last idents.
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.
104
110
  # We are woken up when the unconfirmed_idents is empty, and given back the mutex to
105
111
  # continue operation.
106
112
  @empty.wait(@idents_mutex, CONFIRMATION_TIMEOUT) unless @unconfirmed_idents.empty?
@@ -108,6 +114,8 @@ module RSpec::Buildkite::Analytics
108
114
 
109
115
  # Then we always disconnect cos we can't wait forever? 🤷‍♀️
110
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
111
119
  @logger.write("socket connection closed")
112
120
  end
113
121
 
@@ -121,7 +129,7 @@ module RSpec::Buildkite::Analytics
121
129
  when "welcome", "confirm_subscription"
122
130
  # Push these two messages onto the queue, so that we block on waiting for the
123
131
  # initializing phase to complete
124
- @queue.push(data)
132
+ @establish_subscription_queue.push(data)
125
133
  @logger.write("received #{data['type']}")
126
134
  when "reject_subscription"
127
135
  @logger.write("received rejected_subscription")
@@ -132,13 +140,9 @@ module RSpec::Buildkite::Analytics
132
140
  end
133
141
 
134
142
  def write_result(result)
135
- result_as_json = result.as_json
136
-
137
- add_unconfirmed_idents(result.id, result_as_json)
143
+ queue_and_track_result(result.id, result.as_hash)
138
144
 
139
- transmit_results([result_as_json])
140
-
141
- @logger.write("transmitted #{result.id}")
145
+ @logger.write("added #{result.id} to send queue")
142
146
  end
143
147
 
144
148
  def unconfirmed_idents_count
@@ -149,17 +153,6 @@ module RSpec::Buildkite::Analytics
149
153
 
150
154
  private
151
155
 
152
- def transmit_results(results_as_json)
153
- @connection.transmit({
154
- "identifier" => @channel,
155
- "command" => "message",
156
- "data" => {
157
- "action" => "record_results",
158
- "results" => results_as_json
159
- }.to_json
160
- })
161
- end
162
-
163
156
  def connect
164
157
  @logger.write("starting socket connection process")
165
158
 
@@ -177,11 +170,52 @@ module RSpec::Buildkite::Analytics
177
170
  wait_for_confirm
178
171
 
179
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
180
214
  end
181
215
 
182
216
  def pop_with_timeout(message_type)
183
217
  Timeout.timeout(30, RSpec::Buildkite::Analytics::TimeoutError, "Timeout: Waited 30 seconds for #{message_type}") do
184
- @queue.pop
218
+ @establish_subscription_queue.pop
185
219
  end
186
220
  end
187
221
 
@@ -201,9 +235,16 @@ module RSpec::Buildkite::Analytics
201
235
  end
202
236
  end
203
237
 
204
- def add_unconfirmed_idents(ident, data)
238
+ def queue_and_track_result(ident, result_as_hash)
205
239
  @idents_mutex.synchronize do
206
- @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
207
248
  end
208
249
  end
209
250
 
@@ -222,23 +263,29 @@ module RSpec::Buildkite::Analytics
222
263
  #
223
264
  # However, there aren't any threads waiting on this signal until after we
224
265
  # send the EOT message, so the prior broadcasts shouldn't do anything.
225
- @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
226
272
  end
227
273
  end
228
274
 
229
275
  def send_eot
230
- # Expect server to respond with data of indentifiers last upload part
231
-
232
- @connection.transmit({
233
- "identifier" => @channel,
234
- "command" => "message",
235
- "data" => {
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 = {
236
280
  "action" => "end_of_transmission",
237
281
  "examples_count" => @examples_count.to_json
238
- }.to_json
239
- })
282
+ }
240
283
 
241
- @logger.write("transmitted EOT")
284
+ @send_queue << data
285
+ @eot_queued = true
286
+
287
+ @logger.write("added EOT to send queue")
288
+ end
242
289
  end
243
290
 
244
291
  def process_message(data)
@@ -255,14 +302,19 @@ module RSpec::Buildkite::Analytics
255
302
  end
256
303
 
257
304
  def retransmit
258
- data = @idents_mutex.synchronize do
259
- @unconfirmed_idents.values
260
- end
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
+ }
261
314
 
262
- # send the contents of the buffer, unless it's empty
263
- unless data.empty?
264
- @logger.write("retransmitting data")
265
- transmit_results(data)
315
+ @send_queue << data
316
+ @logger.write("queueing up retransmitted results #{@unconfirmed_idents.keys}")
317
+ end
266
318
  end
267
319
 
268
320
  # if we were disconnected in the closing phase, then resend the EOT
@@ -110,6 +110,7 @@ module RSpec::Buildkite::Analytics
110
110
  @socket.write(frame.to_s)
111
111
  rescue Errno::EPIPE, Errno::ECONNRESET, OpenSSL::SSL::SSLError => e
112
112
  return unless @socket
113
+ return if type == :close
113
114
  @session.logger.write("got #{e}, attempting disconnected flow")
114
115
  @session.disconnected(self)
115
116
  disconnect
@@ -13,15 +13,15 @@ module RSpec::Buildkite::Analytics
13
13
  @children = []
14
14
  end
15
15
 
16
- def as_json
16
+ def as_hash
17
17
  {
18
18
  section: section,
19
19
  start_at: start_at,
20
20
  end_at: end_at,
21
21
  duration: end_at - start_at,
22
22
  detail: detail,
23
- children: children.map(&:as_json),
24
- }
23
+ children: children.map(&:as_hash),
24
+ }.with_indifferent_access
25
25
  end
26
26
  end
27
27
 
@@ -57,7 +57,7 @@ module RSpec::Buildkite::Analytics
57
57
  end
58
58
 
59
59
  def history
60
- @top.as_json
60
+ @top.as_hash
61
61
  end
62
62
  end
63
63
  end
@@ -48,7 +48,7 @@ module RSpec::Buildkite::Analytics
48
48
  end
49
49
  end
50
50
 
51
- def as_json
51
+ def as_hash
52
52
  {
53
53
  id: @id,
54
54
  scope: example.example_group.metadata[:full_description],
@@ -59,7 +59,7 @@ module RSpec::Buildkite::Analytics
59
59
  result: result_state,
60
60
  failure: failure_message,
61
61
  history: history,
62
- }
62
+ }.with_indifferent_access
63
63
  end
64
64
 
65
65
  private
@@ -141,10 +141,12 @@ module RSpec::Buildkite::Analytics
141
141
  end
142
142
  else
143
143
  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}`."
144
+ 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
145
  end
146
146
  else
147
- puts "Buildkite Test Analytics: No Suite API key provided. You can get the API key from your Suite settings page."
147
+ if !!ENV["BUILDKITE_BUILD_ID"]
148
+ puts "Buildkite Test Analytics: No Suite API key provided. You can get the API key from your Suite settings page."
149
+ end
148
150
  end
149
151
  end
150
152
 
@@ -3,7 +3,7 @@
3
3
  module RSpec
4
4
  module Buildkite
5
5
  module Analytics
6
- VERSION = "0.4.0"
6
+ VERSION = "0.5.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
 
@@ -23,7 +24,7 @@ module RSpec::Buildkite::Analytics
23
24
  self.api_token = token || ENV["BUILDKITE_ANALYTICS_TOKEN"]
24
25
  self.url = url || DEFAULT_URL
25
26
  self.debug_enabled = debug_enabled || !!(ENV["BUILDKITE_ANALYTICS_DEBUG_ENABLED"])
26
- self.debug_filepath = debug_filepath || ENV["BUILDKITE_ANALYTICS_DEBUG_FILEPATH"]
27
+ self.debug_filepath = debug_filepath || ENV["BUILDKITE_ANALYTICS_DEBUG_FILEPATH"] || Dir.tmpdir
27
28
 
28
29
  require_relative "analytics/uploader"
29
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.4.0
4
+ version: 0.5.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-11-04 00:00:00.000000000 Z
11
+ date: 2021-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport