rspec-buildkite-analytics 0.3.6 → 0.4.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 +1 -1
- data/lib/rspec/buildkite/analytics/ci.rb +4 -2
- data/lib/rspec/buildkite/analytics/reporter.rb +25 -1
- data/lib/rspec/buildkite/analytics/session.rb +53 -3
- data/lib/rspec/buildkite/analytics/socket_connection.rb +12 -1
- data/lib/rspec/buildkite/analytics/uploader.rb +0 -6
- data/lib/rspec/buildkite/analytics/version.rb +1 -1
- data/lib/rspec/buildkite/analytics.rb +5 -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: 44dd3ce4c2f47452f1eb52c96a2b86a7f49dd971d38350152b773dd343a85969
|
4
|
+
data.tar.gz: 81fad9923f72c5a4e733b9e93917323c1037a6a0fa9ba4c0f76e9c3d0820f06b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6eb38e960fa8c7c8e6b4ff29070d188db20f0868c316a7b2b51051cd733793c8c86ea736385ddefa3eab04afb585d20ebef5fcf3bb7d3a2ee62513d9dcf448d5
|
7
|
+
data.tar.gz: dec76a5f780c47f7f340b83501450545ccd810dbf9b0c43755d4fff300197da750b9411a49eb757f41a094d7058780a78f7a6d859fef34514105be6e97fd8765
|
data/Gemfile.lock
CHANGED
@@ -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,6 @@
|
|
1
1
|
module RSpec::Buildkite::Analytics
|
2
2
|
class Reporter
|
3
|
-
RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending
|
3
|
+
RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending, :dump_summary
|
4
4
|
|
5
5
|
attr_reader :output
|
6
6
|
|
@@ -20,6 +20,30 @@ module RSpec::Buildkite::Analytics
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
def dump_summary(notification)
|
24
|
+
if RSpec::Buildkite::Analytics.session.present?
|
25
|
+
examples_count = {
|
26
|
+
examples: notification.examples.count,
|
27
|
+
failed: notification.failed_examples.count,
|
28
|
+
pending: notification.pending_examples.count,
|
29
|
+
errors_outside_examples: notification.errors_outside_of_examples_count
|
30
|
+
}
|
31
|
+
|
32
|
+
RSpec::Buildkite::Analytics.session.close(examples_count)
|
33
|
+
|
34
|
+
# Write the debug file, if debug mode is enabled
|
35
|
+
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"
|
37
|
+
|
38
|
+
File.open(filename, "wb") do |f|
|
39
|
+
gz = Zlib::GzipWriter.new(f)
|
40
|
+
gz.puts(RSpec::Buildkite::Analytics.session.logger.to_array)
|
41
|
+
gz.close
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
23
47
|
alias_method :example_passed, :handle_example
|
24
48
|
alias_method :example_failed, :handle_example
|
25
49
|
alias_method :example_pending, :handle_example
|
@@ -12,6 +12,28 @@ 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
38
|
@queue = Queue.new
|
17
39
|
@channel = channel
|
@@ -25,6 +47,8 @@ module RSpec::Buildkite::Analytics
|
|
25
47
|
@url = url
|
26
48
|
@authorization_header = authorization_header
|
27
49
|
|
50
|
+
@logger = Logger.new
|
51
|
+
|
28
52
|
connect
|
29
53
|
rescue TimeoutError, InitialConnectionFailure => e
|
30
54
|
$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."
|
@@ -43,16 +67,19 @@ module RSpec::Buildkite::Analytics
|
|
43
67
|
# time the mutex is released, the value of @connection has been refreshed, and so
|
44
68
|
# the second thread returns early and does not reattempt the reconnection.
|
45
69
|
return unless connection == @connection
|
70
|
+
@logger.write("starting reconnection")
|
46
71
|
|
47
72
|
begin
|
48
73
|
reconnection_count += 1
|
49
74
|
connect
|
50
75
|
rescue SocketConnection::HandshakeError, RejectedSubscription, TimeoutError, SocketConnection::SocketError => e
|
76
|
+
@logger.write("failed reconnection attempt #{reconnection_count} due to #{e}")
|
51
77
|
if reconnection_count > MAX_RECONNECTION_ATTEMPTS
|
52
78
|
$stderr.puts "rspec-buildkite-analytics experienced a disconnection and could not reconnect to Buildkite due to #{e.message}. Please contact support."
|
53
79
|
raise e
|
54
80
|
else
|
55
81
|
sleep(WAIT_BETWEEN_RECONNECTIONS)
|
82
|
+
@logger.write("retrying reconnection")
|
56
83
|
retry
|
57
84
|
end
|
58
85
|
end
|
@@ -60,8 +87,10 @@ module RSpec::Buildkite::Analytics
|
|
60
87
|
retransmit
|
61
88
|
end
|
62
89
|
|
63
|
-
def close()
|
90
|
+
def close(examples_count)
|
64
91
|
@closing = true
|
92
|
+
@examples_count = examples_count
|
93
|
+
@logger.write("closing socket connection")
|
65
94
|
|
66
95
|
# Because the server only sends us confirmations after every 10mb of
|
67
96
|
# data it uploads to S3, we'll never get confirmation of the
|
@@ -70,6 +99,7 @@ module RSpec::Buildkite::Analytics
|
|
70
99
|
send_eot
|
71
100
|
|
72
101
|
@idents_mutex.synchronize do
|
102
|
+
@logger.write("waiting for last confirm")
|
73
103
|
# Here, we sleep for 75 seconds while waiting for the server to confirm the last idents.
|
74
104
|
# We are woken up when the unconfirmed_idents is empty, and given back the mutex to
|
75
105
|
# continue operation.
|
@@ -78,6 +108,7 @@ module RSpec::Buildkite::Analytics
|
|
78
108
|
|
79
109
|
# Then we always disconnect cos we can't wait forever? 🤷♀️
|
80
110
|
@connection.close
|
111
|
+
@logger.write("socket connection closed")
|
81
112
|
end
|
82
113
|
|
83
114
|
def handle(_connection, data)
|
@@ -86,11 +117,14 @@ module RSpec::Buildkite::Analytics
|
|
86
117
|
when "ping"
|
87
118
|
# In absence of other message, the server sends us a ping every 3 seconds
|
88
119
|
# We are currently not doing anything with these
|
120
|
+
@logger.write("received ping")
|
89
121
|
when "welcome", "confirm_subscription"
|
90
122
|
# Push these two messages onto the queue, so that we block on waiting for the
|
91
123
|
# initializing phase to complete
|
92
124
|
@queue.push(data)
|
125
|
+
@logger.write("received #{data['type']}")
|
93
126
|
when "reject_subscription"
|
127
|
+
@logger.write("received rejected_subscription")
|
94
128
|
raise RejectedSubscription
|
95
129
|
else
|
96
130
|
process_message(data)
|
@@ -103,6 +137,8 @@ module RSpec::Buildkite::Analytics
|
|
103
137
|
add_unconfirmed_idents(result.id, result_as_json)
|
104
138
|
|
105
139
|
transmit_results([result_as_json])
|
140
|
+
|
141
|
+
@logger.write("transmitted #{result.id}")
|
106
142
|
end
|
107
143
|
|
108
144
|
def unconfirmed_idents_count
|
@@ -125,6 +161,8 @@ module RSpec::Buildkite::Analytics
|
|
125
161
|
end
|
126
162
|
|
127
163
|
def connect
|
164
|
+
@logger.write("starting socket connection process")
|
165
|
+
|
128
166
|
@connection = SocketConnection.new(self, @url, {
|
129
167
|
"Authorization" => @authorization_header,
|
130
168
|
})
|
@@ -137,6 +175,8 @@ module RSpec::Buildkite::Analytics
|
|
137
175
|
})
|
138
176
|
|
139
177
|
wait_for_confirm
|
178
|
+
|
179
|
+
@logger.write("connected")
|
140
180
|
end
|
141
181
|
|
142
182
|
def pop_with_timeout(message_type)
|
@@ -174,6 +214,8 @@ module RSpec::Buildkite::Analytics
|
|
174
214
|
# Remove received idents from unconfirmed_idents
|
175
215
|
idents.each { |key| @unconfirmed_idents.delete(key) }
|
176
216
|
|
217
|
+
@logger.write("received confirm for indentifiers: #{idents.join(", ")}")
|
218
|
+
|
177
219
|
# This @empty ConditionVariable broadcasts every time that @unconfirmed_idents is
|
178
220
|
# empty, which will happen about every 10mb of data as that's when the server
|
179
221
|
# sends back confirmations.
|
@@ -191,9 +233,12 @@ module RSpec::Buildkite::Analytics
|
|
191
233
|
"identifier" => @channel,
|
192
234
|
"command" => "message",
|
193
235
|
"data" => {
|
194
|
-
"action" => "end_of_transmission"
|
236
|
+
"action" => "end_of_transmission",
|
237
|
+
"examples_count" => @examples_count.to_json
|
195
238
|
}.to_json
|
196
239
|
})
|
240
|
+
|
241
|
+
@logger.write("transmitted EOT")
|
197
242
|
end
|
198
243
|
|
199
244
|
def process_message(data)
|
@@ -205,6 +250,7 @@ module RSpec::Buildkite::Analytics
|
|
205
250
|
remove_unconfirmed_idents(data["message"]["confirm"])
|
206
251
|
else
|
207
252
|
# unhandled message
|
253
|
+
@logger.write("received unhandled message #{data["message"]}")
|
208
254
|
end
|
209
255
|
end
|
210
256
|
|
@@ -214,7 +260,11 @@ module RSpec::Buildkite::Analytics
|
|
214
260
|
end
|
215
261
|
|
216
262
|
# send the contents of the buffer, unless it's empty
|
217
|
-
|
263
|
+
unless data.empty?
|
264
|
+
@logger.write("retransmitting data")
|
265
|
+
transmit_results(data)
|
266
|
+
end
|
267
|
+
|
218
268
|
# if we were disconnected in the closing phase, then resend the EOT
|
219
269
|
# message so the server can persist the last upload part
|
220
270
|
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, Errno::ECONNRESET
|
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
|
@@ -104,6 +110,7 @@ module RSpec::Buildkite::Analytics
|
|
104
110
|
@socket.write(frame.to_s)
|
105
111
|
rescue Errno::EPIPE, Errno::ECONNRESET, OpenSSL::SSL::SSLError => e
|
106
112
|
return unless @socket
|
113
|
+
@session.logger.write("got #{e}, attempting disconnected flow")
|
107
114
|
@session.disconnected(self)
|
108
115
|
disconnect
|
109
116
|
rescue IndexError
|
@@ -112,13 +119,16 @@ module RSpec::Buildkite::Analytics
|
|
112
119
|
# This relates to this issue https://github.com/ruby/openssl/issues/452
|
113
120
|
# A fix for it has been released but the repercussions of overriding
|
114
121
|
# the OpenSSL version in the stdlib seem worse than catching this error here.
|
122
|
+
@session.logger.write("IndexError")
|
115
123
|
if @socket
|
124
|
+
@session.logger.write("attempting disconnected flow")
|
116
125
|
@session.disconnected(self)
|
117
126
|
disconnect
|
118
127
|
end
|
119
128
|
end
|
120
129
|
|
121
130
|
def close
|
131
|
+
@session.logger.write("socket close")
|
122
132
|
transmit(nil, type: :close)
|
123
133
|
disconnect
|
124
134
|
end
|
@@ -126,6 +136,7 @@ module RSpec::Buildkite::Analytics
|
|
126
136
|
private
|
127
137
|
|
128
138
|
def disconnect
|
139
|
+
@session.logger.write("socket disconnect")
|
129
140
|
socket = @socket
|
130
141
|
@socket = nil
|
131
142
|
socket&.close
|
@@ -162,12 +162,6 @@ module RSpec::Buildkite::Analytics
|
|
162
162
|
trace = RSpec::Buildkite::Analytics::Uploader::Trace.new(example, tracer.history)
|
163
163
|
RSpec::Buildkite::Analytics.uploader.traces << trace
|
164
164
|
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
165
|
end
|
172
166
|
|
173
167
|
RSpec::Buildkite::Analytics::Network.configure
|
@@ -15,11 +15,15 @@ module RSpec::Buildkite::Analytics
|
|
15
15
|
attr_accessor :url
|
16
16
|
attr_accessor :uploader
|
17
17
|
attr_accessor :session
|
18
|
+
attr_accessor :debug_enabled
|
19
|
+
attr_accessor :debug_filepath
|
18
20
|
end
|
19
21
|
|
20
|
-
def self.configure(token: nil, url: nil)
|
22
|
+
def self.configure(token: nil, url: nil, debug_enabled: false, debug_filepath: nil)
|
21
23
|
self.api_token = token || ENV["BUILDKITE_ANALYTICS_TOKEN"]
|
22
24
|
self.url = url || DEFAULT_URL
|
25
|
+
self.debug_enabled = debug_enabled || !!(ENV["BUILDKITE_ANALYTICS_DEBUG_ENABLED"])
|
26
|
+
self.debug_filepath = debug_filepath || ENV["BUILDKITE_ANALYTICS_DEBUG_FILEPATH"]
|
23
27
|
|
24
28
|
require_relative "analytics/uploader"
|
25
29
|
|
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.4.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
|
+
date: 2021-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|