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 +4 -4
- data/Gemfile.lock +5 -5
- data/lib/rspec/buildkite/analytics/reporter.rb +3 -1
- data/lib/rspec/buildkite/analytics/session.rb +93 -41
- data/lib/rspec/buildkite/analytics/socket_connection.rb +1 -0
- data/lib/rspec/buildkite/analytics/tracer.rb +4 -4
- data/lib/rspec/buildkite/analytics/uploader.rb +6 -4
- data/lib/rspec/buildkite/analytics/version.rb +1 -1
- data/lib/rspec/buildkite/analytics.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9fc407bacf4405e3d3e2ee4043a9c71f20d75a363d3ba33948b37403a6e8de4
|
4
|
+
data.tar.gz: 67f8b42a6ac98518d784eea442fb9efc5a1c8dd5ca0037cc5ce5664b180b75ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
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.
|
21
|
+
i18n (1.8.11)
|
22
22
|
concurrent-ruby (~> 1.0)
|
23
23
|
minitest (5.14.4)
|
24
|
-
rake (13.0.
|
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.
|
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.
|
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-#{
|
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
|
-
@
|
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
|
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
|
-
@
|
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
|
-
|
136
|
-
|
137
|
-
add_unconfirmed_idents(result.id, result_as_json)
|
143
|
+
queue_and_track_result(result.id, result.as_hash)
|
138
144
|
|
139
|
-
|
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
|
-
@
|
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
|
238
|
+
def queue_and_track_result(ident, result_as_hash)
|
205
239
|
@idents_mutex.synchronize do
|
206
|
-
@unconfirmed_idents[ident] =
|
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
|
-
|
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
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
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
|
-
}
|
239
|
-
})
|
282
|
+
}
|
240
283
|
|
241
|
-
|
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
|
-
|
259
|
-
@unconfirmed_idents.values
|
260
|
-
|
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
|
-
|
263
|
-
|
264
|
-
|
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
|
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(&:
|
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.
|
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
|
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 "
|
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
|
-
|
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
|
|
@@ -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
|
+
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-
|
11
|
+
date: 2021-11-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|