rspec-buildkite-analytics 0.3.3 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 852e10cf6b6c63174c82c06f3f5f249b9093fc666582041b01a5fa44d1bdc421
4
- data.tar.gz: f88e00ffe339bb34f9edd90d6503e61ec308af5387e19d85b9bc790c076edf49
3
+ metadata.gz: 44dd3ce4c2f47452f1eb52c96a2b86a7f49dd971d38350152b773dd343a85969
4
+ data.tar.gz: 81fad9923f72c5a4e733b9e93917323c1037a6a0fa9ba4c0f76e9c3d0820f06b
5
5
  SHA512:
6
- metadata.gz: 4a9c3b5d0c9b19cd2c8b5e5614ed38cc799f34852c6f6d551be4da788a009137e181015d67a27cd602c1c81b34e84d5ac108b076ff2365c4d153c66388b63f64
7
- data.tar.gz: d6ce7ee6ed3988f12fe93c92a1ee3280c39d353b7c741f43423e42acba05f4c52c7da6e3bae772bbc3f0dc3ceec3bb51d4c8e0c187255f8bda98541b6516400d
6
+ metadata.gz: 6eb38e960fa8c7c8e6b4ff29070d188db20f0868c316a7b2b51051cd733793c8c86ea736385ddefa3eab04afb585d20ebef5fcf3bb7d3a2ee62513d9dcf448d5
7
+ data.tar.gz: dec76a5f780c47f7f340b83501450545ccd810dbf9b0c43755d4fff300197da750b9411a49eb757f41a094d7058780a78f7a6d859fef34514105be6e97fd8765
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rspec-buildkite-analytics (0.3.3)
4
+ rspec-buildkite-analytics (0.4.0)
5
5
  activesupport (>= 5.2, <= 7.0)
6
6
  rspec-core (~> 3.10)
7
7
  rspec-expectations (~> 3.10)
@@ -38,7 +38,7 @@ GEM
38
38
  tzinfo (2.0.4)
39
39
  concurrent-ruby (~> 1.0)
40
40
  websocket (1.2.9)
41
- zeitwerk (2.4.2)
41
+ zeitwerk (2.5.1)
42
42
 
43
43
  PLATFORMS
44
44
  ruby
@@ -49,4 +49,4 @@ DEPENDENCIES
49
49
  rspec-buildkite-analytics!
50
50
 
51
51
  BUNDLED WITH
52
- 2.2.20
52
+ 2.2.22
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,8 @@
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
+
5
+ attr_reader :output
4
6
 
5
7
  def initialize(output)
6
8
  @output = output
@@ -18,6 +20,30 @@ module RSpec::Buildkite::Analytics
18
20
  end
19
21
  end
20
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
+
21
47
  alias_method :example_passed, :handle_example
22
48
  alias_method :example_failed, :handle_example
23
49
  alias_method :example_pending, :handle_example
@@ -10,6 +10,29 @@ module RSpec::Buildkite::Analytics
10
10
  WAIT_BETWEEN_RECONNECTIONS = 5
11
11
 
12
12
  class RejectedSubscription < StandardError; end
13
+ class InitialConnectionFailure < StandardError; end
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
13
36
 
14
37
  def initialize(url, authorization_header, channel)
15
38
  @queue = Queue.new
@@ -24,9 +47,11 @@ module RSpec::Buildkite::Analytics
24
47
  @url = url
25
48
  @authorization_header = authorization_header
26
49
 
50
+ @logger = Logger.new
51
+
27
52
  connect
28
- rescue TimeoutError => e
29
- $stderr.puts "rspec-buildkite-analytics could not establish an initial connection with Buildkite. Please contact support."
53
+ rescue TimeoutError, InitialConnectionFailure => e
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."
30
55
  end
31
56
 
32
57
  def disconnected(connection)
@@ -42,16 +67,19 @@ module RSpec::Buildkite::Analytics
42
67
  # time the mutex is released, the value of @connection has been refreshed, and so
43
68
  # the second thread returns early and does not reattempt the reconnection.
44
69
  return unless connection == @connection
70
+ @logger.write("starting reconnection")
45
71
 
46
72
  begin
47
73
  reconnection_count += 1
48
74
  connect
49
75
  rescue SocketConnection::HandshakeError, RejectedSubscription, TimeoutError, SocketConnection::SocketError => e
76
+ @logger.write("failed reconnection attempt #{reconnection_count} due to #{e}")
50
77
  if reconnection_count > MAX_RECONNECTION_ATTEMPTS
51
78
  $stderr.puts "rspec-buildkite-analytics experienced a disconnection and could not reconnect to Buildkite due to #{e.message}. Please contact support."
52
79
  raise e
53
80
  else
54
81
  sleep(WAIT_BETWEEN_RECONNECTIONS)
82
+ @logger.write("retrying reconnection")
55
83
  retry
56
84
  end
57
85
  end
@@ -59,8 +87,10 @@ module RSpec::Buildkite::Analytics
59
87
  retransmit
60
88
  end
61
89
 
62
- def close()
90
+ def close(examples_count)
63
91
  @closing = true
92
+ @examples_count = examples_count
93
+ @logger.write("closing socket connection")
64
94
 
65
95
  # Because the server only sends us confirmations after every 10mb of
66
96
  # data it uploads to S3, we'll never get confirmation of the
@@ -69,6 +99,7 @@ module RSpec::Buildkite::Analytics
69
99
  send_eot
70
100
 
71
101
  @idents_mutex.synchronize do
102
+ @logger.write("waiting for last confirm")
72
103
  # Here, we sleep for 75 seconds while waiting for the server to confirm the last idents.
73
104
  # We are woken up when the unconfirmed_idents is empty, and given back the mutex to
74
105
  # continue operation.
@@ -77,6 +108,7 @@ module RSpec::Buildkite::Analytics
77
108
 
78
109
  # Then we always disconnect cos we can't wait forever? 🤷‍♀️
79
110
  @connection.close
111
+ @logger.write("socket connection closed")
80
112
  end
81
113
 
82
114
  def handle(_connection, data)
@@ -85,11 +117,14 @@ module RSpec::Buildkite::Analytics
85
117
  when "ping"
86
118
  # In absence of other message, the server sends us a ping every 3 seconds
87
119
  # We are currently not doing anything with these
120
+ @logger.write("received ping")
88
121
  when "welcome", "confirm_subscription"
89
122
  # Push these two messages onto the queue, so that we block on waiting for the
90
123
  # initializing phase to complete
91
124
  @queue.push(data)
125
+ @logger.write("received #{data['type']}")
92
126
  when "reject_subscription"
127
+ @logger.write("received rejected_subscription")
93
128
  raise RejectedSubscription
94
129
  else
95
130
  process_message(data)
@@ -102,6 +137,8 @@ module RSpec::Buildkite::Analytics
102
137
  add_unconfirmed_idents(result.id, result_as_json)
103
138
 
104
139
  transmit_results([result_as_json])
140
+
141
+ @logger.write("transmitted #{result.id}")
105
142
  end
106
143
 
107
144
  def unconfirmed_idents_count
@@ -124,6 +161,8 @@ module RSpec::Buildkite::Analytics
124
161
  end
125
162
 
126
163
  def connect
164
+ @logger.write("starting socket connection process")
165
+
127
166
  @connection = SocketConnection.new(self, @url, {
128
167
  "Authorization" => @authorization_header,
129
168
  })
@@ -136,27 +175,29 @@ module RSpec::Buildkite::Analytics
136
175
  })
137
176
 
138
177
  wait_for_confirm
178
+
179
+ @logger.write("connected")
139
180
  end
140
181
 
141
- def pop_with_timeout
142
- Timeout.timeout(30, RSpec::Buildkite::Analytics::TimeoutError, "Waited 30 seconds") do
182
+ def pop_with_timeout(message_type)
183
+ Timeout.timeout(30, RSpec::Buildkite::Analytics::TimeoutError, "Timeout: Waited 30 seconds for #{message_type}") do
143
184
  @queue.pop
144
185
  end
145
186
  end
146
187
 
147
188
  def wait_for_welcome
148
- welcome = pop_with_timeout
189
+ welcome = pop_with_timeout("welcome")
149
190
 
150
191
  if welcome && welcome != { "type" => "welcome" }
151
- raise "Not a welcome: #{welcome.inspect}"
192
+ raise InitialConnectionFailure.new("Wrong message received, expected a welcome, but received: #{welcome.inspect}")
152
193
  end
153
194
  end
154
195
 
155
196
  def wait_for_confirm
156
- confirm = pop_with_timeout
197
+ confirm = pop_with_timeout("confirm")
157
198
 
158
199
  if confirm && confirm != { "type" => "confirm_subscription", "identifier" => @channel }
159
- raise "Not a confirm: #{confirm.inspect}"
200
+ raise InitialConnectionFailure.new("Wrong message received, expected a confirm, but received: #{confirm.inspect}")
160
201
  end
161
202
  end
162
203
 
@@ -173,6 +214,8 @@ module RSpec::Buildkite::Analytics
173
214
  # Remove received idents from unconfirmed_idents
174
215
  idents.each { |key| @unconfirmed_idents.delete(key) }
175
216
 
217
+ @logger.write("received confirm for indentifiers: #{idents.join(", ")}")
218
+
176
219
  # This @empty ConditionVariable broadcasts every time that @unconfirmed_idents is
177
220
  # empty, which will happen about every 10mb of data as that's when the server
178
221
  # sends back confirmations.
@@ -190,9 +233,12 @@ module RSpec::Buildkite::Analytics
190
233
  "identifier" => @channel,
191
234
  "command" => "message",
192
235
  "data" => {
193
- "action" => "end_of_transmission"
236
+ "action" => "end_of_transmission",
237
+ "examples_count" => @examples_count.to_json
194
238
  }.to_json
195
239
  })
240
+
241
+ @logger.write("transmitted EOT")
196
242
  end
197
243
 
198
244
  def process_message(data)
@@ -204,6 +250,7 @@ module RSpec::Buildkite::Analytics
204
250
  remove_unconfirmed_idents(data["message"]["confirm"])
205
251
  else
206
252
  # unhandled message
253
+ @logger.write("received unhandled message #{data["message"]}")
207
254
  end
208
255
  end
209
256
 
@@ -213,7 +260,11 @@ module RSpec::Buildkite::Analytics
213
260
  end
214
261
 
215
262
  # send the contents of the buffer, unless it's empty
216
- transmit_results(data) unless data.empty?
263
+ unless data.empty?
264
+ @logger.write("retransmitting data")
265
+ transmit_results(data)
266
+ end
267
+
217
268
  # if we were disconnected in the closing phase, then resend the EOT
218
269
  # message so the server can persist the last upload part
219
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,13 +76,28 @@ 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")
89
+ rescue IndexError
90
+ # I don't like that we're doing this but I think it's the best of the options
91
+ #
92
+ # This relates to this issue https://github.com/ruby/openssl/issues/452
93
+ # A fix for it has been released but the repercussions of overriding
94
+ # the OpenSSL version in the stdlib seem worse than catching this error here.
95
+ @session.logger.write("IndexError")
96
+ if @socket
97
+ @session.logger.write("attempting disconnected flow")
98
+ @session.disconnected(self)
99
+ disconnect
100
+ end
85
101
  end
86
102
  end
87
103
 
@@ -92,13 +108,27 @@ module RSpec::Buildkite::Analytics
92
108
  raw_data = data.to_json
93
109
  frame = WebSocket::Frame::Outgoing::Client.new(data: raw_data, type: :text, version: @version)
94
110
  @socket.write(frame.to_s)
95
- rescue Errno::EPIPE, OpenSSL::SSL::SSLError => e
111
+ rescue Errno::EPIPE, Errno::ECONNRESET, OpenSSL::SSL::SSLError => e
96
112
  return unless @socket
113
+ @session.logger.write("got #{e}, attempting disconnected flow")
97
114
  @session.disconnected(self)
98
115
  disconnect
116
+ rescue IndexError
117
+ # I don't like that we're doing this but I think it's the best of the options
118
+ #
119
+ # This relates to this issue https://github.com/ruby/openssl/issues/452
120
+ # A fix for it has been released but the repercussions of overriding
121
+ # the OpenSSL version in the stdlib seem worse than catching this error here.
122
+ @session.logger.write("IndexError")
123
+ if @socket
124
+ @session.logger.write("attempting disconnected flow")
125
+ @session.disconnected(self)
126
+ disconnect
127
+ end
99
128
  end
100
129
 
101
130
  def close
131
+ @session.logger.write("socket close")
102
132
  transmit(nil, type: :close)
103
133
  disconnect
104
134
  end
@@ -106,6 +136,7 @@ module RSpec::Buildkite::Analytics
106
136
  private
107
137
 
108
138
  def disconnect
139
+ @session.logger.write("socket disconnect")
109
140
  socket = @socket
110
141
  @socket = nil
111
142
  socket&.close
@@ -65,7 +65,7 @@ module RSpec::Buildkite::Analytics
65
65
  private
66
66
 
67
67
  def generate_file_name(example)
68
- file_path_regex = /^(.*?\.rb)/
68
+ file_path_regex = /^(.*?\.(rb|feature))/
69
69
  identifier_file_name = example.id[file_path_regex]
70
70
  location_file_name = example.location[file_path_regex]
71
71
 
@@ -127,12 +127,12 @@ module RSpec::Buildkite::Analytics
127
127
  response = begin
128
128
  http.request(contact)
129
129
  rescue *REQUEST_EXCEPTIONS => e
130
- puts "Error communicating with the server: #{e.message}"
130
+ puts "Buildkite Test Analytics: Error communicating with the server: #{e.message}"
131
131
  end
132
132
 
133
133
  case response.code
134
134
  when "401"
135
- puts "Invalid Suite API key. Please double check your Suite API key."
135
+ puts "Buildkite Test Analytics: Invalid Suite API key. Please double check your Suite API key."
136
136
  when "200"
137
137
  json = JSON.parse(response.body)
138
138
 
@@ -141,10 +141,10 @@ module RSpec::Buildkite::Analytics
141
141
  end
142
142
  else
143
143
  request_id = response.to_hash["x-request-id"]
144
- puts "Unknown error. If this error persists, please contact support+analytics@buildkite.com with this request ID `#{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}`."
145
145
  end
146
146
  else
147
- puts "No Suite API key provided. You can get the API key from your Suite settings page."
147
+ puts "Buildkite Test Analytics: No Suite API key provided. You can get the API key from your Suite settings page."
148
148
  end
149
149
  end
150
150
 
@@ -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
@@ -3,7 +3,7 @@
3
3
  module RSpec
4
4
  module Buildkite
5
5
  module Analytics
6
- VERSION = "0.3.3"
6
+ VERSION = "0.4.0"
7
7
  end
8
8
  end
9
9
  end
@@ -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.3.3
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-09-08 00:00:00.000000000 Z
11
+ date: 2021-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -121,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
121
  - !ruby/object:Gem::Version
122
122
  version: '0'
123
123
  requirements: []
124
- rubygems_version: 3.2.3
124
+ rubygems_version: 3.1.4
125
125
  signing_key:
126
126
  specification_version: 4
127
127
  summary: Track execution of specs and report to Buildkite Analytics