rspec-buildkite-analytics 0.6.3 → 0.8.1

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: 499229f993ea2f8556a6d908f011325bf989de60c9a69a266338356b9f89426f
4
- data.tar.gz: 901fec67b35b6ca85f61900a4cd9fa00293959892eb45e95339b6d4a6c27f527
3
+ metadata.gz: 4ac283b3f333632c2909b0dd7b35dd1c4f9765d659489c00d8ae93a169433aa8
4
+ data.tar.gz: eb45c11b5da7a61597b915749f04bb77d46408b764f22cb84dba349ae3fba4f8
5
5
  SHA512:
6
- metadata.gz: 0b7942d5c6b1abd45ff662cd52878d348bb1f9b3e2fa9fd1a6285eb7f521ac1d1f9dcc52007650dde979da9ddcbdd0506afe869708c785d033f9cf2e969eb009
7
- data.tar.gz: c5484d6a416ac0faf663390c64d86e6c24184ddadbaa286b66407afd169444b7af9a6b9e665ead3438e87e719b5563ea0bc184c963643e17b13cdb8d3517ad1c
6
+ metadata.gz: deb2c39374b9a86cea6ab81b8f81137eb919e78f7d5a5f4ad28fb1cb51dfdbda72f930e71dca67dcd4353860519109627852f52c07238523bc6aeb3b8b83513a
7
+ data.tar.gz: 64036687b205256d27f656066bdc182b936dcb29daa10f6149ce18972303b4b8140c44ab6db22028cce0a19fd84bab4b59d679f4e8f1471f87f86e0c31fe3a1f
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # CHANGELOG
2
+
3
+ ## v0.8.1
4
+
5
+ - Improve the EOT confirmation #93 — @blaknite
6
+
7
+ ## v0.8.0
8
+
9
+ - Support multiple CI platforms and generic env #80 — @blaknite
10
+ - Replace invalid UTF-8 characters in test names #85 — @mariovisic
11
+ - Relax Active Support constraint #87 — @ags
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rspec-buildkite-analytics (0.6.3)
5
- activesupport (>= 5.2, <= 7.0)
4
+ rspec-buildkite-analytics (0.8.1)
5
+ activesupport (>= 5.2, < 8)
6
6
  rspec-core (~> 3.10)
7
7
  rspec-expectations (~> 3.10)
8
8
  websocket (~> 1.2)
@@ -10,14 +10,14 @@ PATH
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
- activesupport (7.0.0)
13
+ activesupport (7.0.2.4)
14
14
  concurrent-ruby (~> 1.0, >= 1.0.2)
15
15
  i18n (>= 1.6, < 2)
16
16
  minitest (>= 5.1)
17
17
  tzinfo (~> 2.0)
18
- concurrent-ruby (1.1.9)
18
+ concurrent-ruby (1.1.10)
19
19
  diff-lcs (1.4.4)
20
- i18n (1.8.11)
20
+ i18n (1.10.0)
21
21
  concurrent-ruby (~> 1.0)
22
22
  minitest (5.15.0)
23
23
  rake (13.0.6)
@@ -2,30 +2,85 @@
2
2
 
3
3
  require "securerandom"
4
4
 
5
- module RSpec::Buildkite::Analytics::CI
5
+ class RSpec::Buildkite::Analytics::CI
6
6
  def self.env
7
- if ENV["BUILDKITE_BUILD_ID"]
8
- {
9
- "CI" => "buildkite",
10
- "key" => ENV["BUILDKITE_BUILD_ID"],
11
- "url" => ENV["BUILDKITE_BUILD_URL"],
12
- "branch" => ENV["BUILDKITE_BRANCH"],
13
- "commit_sha" => ENV["BUILDKITE_COMMIT"],
14
- "number" => ENV["BUILDKITE_BUILD_NUMBER"],
15
- "job_id" => ENV["BUILDKITE_JOB_ID"],
16
- "message" => ENV["BUILDKITE_MESSAGE"],
17
- "debug" => ENV["BUILDKITE_ANALYTICS_DEBUG_ENABLED"],
18
- "version" => RSpec::Buildkite::Analytics::VERSION,
19
- "collector" => RSpec::Buildkite::Analytics::NAME
20
- }
21
- else
22
- {
23
- "CI" => nil,
24
- "key" => SecureRandom.uuid,
25
- "debug" => ENV["BUILDKITE_ANALYTICS_DEBUG_ENABLED"],
26
- "version" => RSpec::Buildkite::Analytics::VERSION,
27
- "collector" => RSpec::Buildkite::Analytics::NAME
28
- }
29
- end
7
+ new.env
8
+ end
9
+
10
+ # The analytics env are more specific than the automatic ci platform env.
11
+ # If they've been specified we'll assume the user wants to use that value instead.
12
+ def env
13
+ ci_env.merge(analytics_env)
14
+ end
15
+
16
+ private
17
+
18
+ def ci_env
19
+ return buildkite if ENV["BUILDKITE_BUILD_ID"]
20
+ return github_actions if ENV["GITHUB_RUN_NUMBER"]
21
+ return circleci if ENV["CIRCLE_BUILD_NUM"]
22
+ return generic if ENV["CI"]
23
+
24
+ {
25
+ "CI" => nil,
26
+ "key" => SecureRandom.uuid,
27
+ }
28
+ end
29
+
30
+ def analytics_env
31
+ {
32
+ "key" => ENV["BUILDKITE_ANALYTICS_KEY"],
33
+ "url" => ENV["BUILDKITE_ANALYTICS_URL"],
34
+ "branch" => ENV["BUILDKITE_ANALYTICS_BRANCH"],
35
+ "commit_sha" => ENV["BUILDKITE_ANALYTICS_SHA"],
36
+ "number" => ENV["BUILDKITE_ANALYTICS_NUMBER"],
37
+ "job_id" => ENV["BUILDKITE_ANALYTICS_JOB_ID"],
38
+ "message" => ENV["BUILDKITE_ANANLYTICS_MESSAGE"],
39
+ "debug" => ENV["BUILDKITE_ANALYTICS_DEBUG_ENABLED"],
40
+ "version" => RSpec::Buildkite::Analytics::VERSION,
41
+ "collector" => RSpec::Buildkite::Analytics::NAME,
42
+ }.compact
43
+ end
44
+
45
+ def generic
46
+ {
47
+ "CI" => "generic",
48
+ "key" => SecureRandom.uuid,
49
+ }
50
+ end
51
+
52
+ def buildkite
53
+ {
54
+ "CI" => "buildkite",
55
+ "key" => ENV["BUILDKITE_BUILD_ID"],
56
+ "url" => ENV["BUILDKITE_BUILD_URL"],
57
+ "branch" => ENV["BUILDKITE_BRANCH"],
58
+ "commit_sha" => ENV["BUILDKITE_COMMIT"],
59
+ "number" => ENV["BUILDKITE_BUILD_NUMBER"],
60
+ "job_id" => ENV["BUILDKITE_JOB_ID"],
61
+ "message" => ENV["BUILDKITE_MESSAGE"],
62
+ }
63
+ end
64
+
65
+ def github_actions
66
+ {
67
+ "CI" => "github_actions",
68
+ "key" => "#{ENV["GITHUB_ACTION"]}-#{ENV["GITHUB_RUN_NUMBER"]}-#{ENV["GITHUB_RUN_ATTEMPT"]}",
69
+ "url" => File.join("https://github.com", ENV["GITHUB_REPOSITORY"], "actions/runs", ENV["GITHUB_RUN_ID"]),
70
+ "branch" => ENV["GITHUB_REF"],
71
+ "commit_sha" => ENV["GITHUB_SHA"],
72
+ "number" => ENV["GITHUB_RUN_NUMBER"],
73
+ }
74
+ end
75
+
76
+ def circleci
77
+ {
78
+ "CI" => "circleci",
79
+ "key" => "#{ENV["CIRCLE_WORKFLOW_ID"]}-#{ENV["CIRCLE_BUILD_NUM"]}",
80
+ "url" => ENV["CIRCLE_BUILD_URL"],
81
+ "branch" => ENV["CIRCLE_BRANCH"],
82
+ "commit_sha" => ENV["CIRCLE_SHA1"],
83
+ "number" => ENV["CIRCLE_BUILD_NUM"],
84
+ }
30
85
  end
31
86
  end
@@ -5,9 +5,9 @@ require_relative "socket_connection"
5
5
  module RSpec::Buildkite::Analytics
6
6
  class Session
7
7
  # Picked 75 as the magic timeout number as it's longer than the TCP timeout of 60s 🤷‍♀️
8
- CONFIRMATION_TIMEOUT = 75
9
- MAX_RECONNECTION_ATTEMPTS = 3
10
- WAIT_BETWEEN_RECONNECTIONS = 5
8
+ CONFIRMATION_TIMEOUT = ENV.fetch("BUILDKITE_ANALYTICS_CONFIRMATION_TIMEOUT") { 75 }.to_i
9
+ MAX_RECONNECTION_ATTEMPTS = ENV.fetch("BUILDKITE_ANALYTICS_RECONNECTION_ATTEMPTS") { 3 }.to_i
10
+ WAIT_BETWEEN_RECONNECTIONS = ENV.fetch("BUILDKITE_ANALYTICS_RECONNECTION_WAIT") { 5 }.to_i
11
11
 
12
12
  class RejectedSubscription < StandardError; end
13
13
  class InitialConnectionFailure < StandardError; end
@@ -116,20 +116,24 @@ module RSpec::Buildkite::Analytics
116
116
  # to which the server will respond with the last bits of data
117
117
  send_eot
118
118
 
119
+ # After EOT, we wait for 75 seconds for the send queue to be drained and for the
120
+ # server to confirm the last idents. If everything has already been confirmed we can
121
+ # proceed without waiting.
119
122
  @idents_mutex.synchronize do
120
- @logger.write("waiting for last confirm")
121
- # Here, we sleep for 75 seconds while waiting for the send
122
- # queue to be drained and for the server to confirm the last
123
- # idents.
124
- # We are woken up when the unconfirmed_idents is empty, and given back the mutex to
125
- # continue operation.
126
- @empty.wait(@idents_mutex, CONFIRMATION_TIMEOUT) unless @unconfirmed_idents.empty?
123
+ if @unconfirmed_idents.any?
124
+ puts "Waiting for Buildkite Test Analytics to send results..."
125
+ @logger.write("waiting for last confirm")
126
+
127
+ @empty.wait(@idents_mutex, CONFIRMATION_TIMEOUT)
128
+ end
127
129
  end
128
130
 
129
131
  # Then we always disconnect cos we can't wait forever? 🤷‍♀️
130
132
  @connection.close
131
133
  # We kill the write thread cos it's got a while loop in it, so it won't finish otherwise
132
134
  @write_thread&.kill
135
+
136
+ puts "Buildkite Test Analytics completed"
133
137
  @logger.write("socket connection closed")
134
138
  end
135
139
 
@@ -183,6 +187,7 @@ module RSpec::Buildkite::Analytics
183
187
 
184
188
  wait_for_confirm
185
189
 
190
+ puts "Connected to Buildkite Test Analytics!"
186
191
  @logger.write("connected")
187
192
  end
188
193
 
@@ -255,23 +260,21 @@ module RSpec::Buildkite::Analytics
255
260
  @idents_mutex.synchronize do
256
261
  @unconfirmed_idents[ident] = result_as_hash
257
262
 
258
- data = {
263
+ @send_queue << {
259
264
  "action" => "record_results",
260
265
  "results" => [result_as_hash]
261
266
  }
262
-
263
- @send_queue << data
264
267
  end
265
268
  end
266
269
 
267
- def remove_unconfirmed_idents(idents)
268
- return if idents.empty?
270
+ def confirm_idents(idents)
271
+ retransmit_required = @closing
269
272
 
270
273
  @idents_mutex.synchronize do
271
274
  # Remove received idents from unconfirmed_idents
272
275
  idents.each { |key| @unconfirmed_idents.delete(key) }
273
276
 
274
- @logger.write("received confirm for indentifiers: #{idents.join(", ")}")
277
+ @logger.write("received confirm for indentifiers: #{idents}")
275
278
 
276
279
  # This @empty ConditionVariable broadcasts every time that @unconfirmed_idents is
277
280
  # empty, which will happen about every 10mb of data as that's when the server
@@ -281,23 +284,27 @@ module RSpec::Buildkite::Analytics
281
284
  # send the EOT message, so the prior broadcasts shouldn't do anything.
282
285
  if @unconfirmed_idents.empty?
283
286
  @empty.broadcast
284
- @logger.write("broadcast empty")
287
+
288
+ retransmit_required = false
289
+
290
+ @logger.write("all identifiers have been confirmed")
285
291
  else
286
- @logger.write("still waiting on confirm for #{@unconfirmed_idents.keys}")
292
+ @logger.write("still waiting on confirm for identifiers: #{@unconfirmed_idents.keys}")
287
293
  end
288
294
  end
295
+
296
+ # If we're closing, any unconfirmed results need to be retransmitted.
297
+ retransmit if retransmit_required
289
298
  end
290
299
 
291
300
  def send_eot
292
301
  @eot_queued_mutex.synchronize do
293
302
  return if @eot_queued
294
- # Expect server to respond with data of indentifiers last upload part
295
- data = {
303
+
304
+ @send_queue << {
296
305
  "action" => "end_of_transmission",
297
306
  "examples_count" => @examples_count.to_json
298
307
  }
299
-
300
- @send_queue << data
301
308
  @eot_queued = true
302
309
 
303
310
  @logger.write("added EOT to send queue")
@@ -310,7 +317,7 @@ module RSpec::Buildkite::Analytics
310
317
 
311
318
  case
312
319
  when data["message"].key?("confirm")
313
- remove_unconfirmed_idents(data["message"]["confirm"])
320
+ confirm_idents(data["message"]["confirm"])
314
321
  else
315
322
  # unhandled message
316
323
  @logger.write("received unhandled message #{data["message"]}")
@@ -322,13 +329,12 @@ module RSpec::Buildkite::Analytics
322
329
  results = @unconfirmed_idents.values
323
330
 
324
331
  # queue the contents of the buffer, unless it's empty
325
- unless results.empty?
326
- data = {
332
+ if results.any?
333
+ @send_queue << {
327
334
  "action" => "record_results",
328
335
  "results" => results
329
336
  }
330
337
 
331
- @send_queue << data
332
338
  @logger.write("queueing up retransmitted results #{@unconfirmed_idents.keys}")
333
339
  end
334
340
  end
@@ -42,7 +42,7 @@ module RSpec::Buildkite::Analytics
42
42
  end
43
43
 
44
44
  def as_hash
45
- {
45
+ strip_invalid_utf8_chars(
46
46
  id: @id,
47
47
  scope: example.example_group.metadata[:full_description],
48
48
  name: example.description,
@@ -53,14 +53,14 @@ module RSpec::Buildkite::Analytics
53
53
  failure_reason: failure_reason,
54
54
  failure_expanded: failure_expanded,
55
55
  history: history,
56
- }.with_indifferent_access.compact
56
+ ).with_indifferent_access.compact
57
57
  end
58
58
 
59
59
  private
60
60
 
61
61
  def generate_file_name(example)
62
62
  file_path_regex = /^(.*?\.(rb|feature))/
63
- identifier_file_name = example.id[file_path_regex]
63
+ identifier_file_name = strip_invalid_utf8_chars(example.id)[file_path_regex]
64
64
  location_file_name = example.location[file_path_regex]
65
65
 
66
66
  if identifier_file_name != location_file_name
@@ -78,6 +78,18 @@ module RSpec::Buildkite::Analytics
78
78
  identifier_file_name
79
79
  end
80
80
  end
81
+
82
+ def strip_invalid_utf8_chars(object)
83
+ if object.is_a?(Hash)
84
+ Hash[object.map { |key, value| [key, strip_invalid_utf8_chars(value)] }]
85
+ elsif object.is_a?(Array)
86
+ object.map { |value| strip_invalid_utf8_chars(value) }
87
+ elsif object.is_a?(String)
88
+ object.encode('UTF-8', :invalid => :replace, :undef => :replace)
89
+ else
90
+ object
91
+ end
92
+ end
81
93
  end
82
94
 
83
95
  def self.traces
@@ -3,7 +3,7 @@
3
3
  module RSpec
4
4
  module Buildkite
5
5
  module Analytics
6
- VERSION = "0.6.3"
6
+ VERSION = "0.8.1"
7
7
  NAME = "rspec-buildkite"
8
8
  end
9
9
  end
@@ -30,4 +30,10 @@ module RSpec::Buildkite::Analytics
30
30
 
31
31
  self::Uploader.configure
32
32
  end
33
+
34
+ def self.annotate(content)
35
+ tracer = RSpec::Buildkite::Analytics::Uploader.tracer
36
+ tracer&.enter("annotation", **{ content: content })
37
+ tracer&.leave
38
+ end
33
39
  end
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
26
26
 
27
- spec.add_dependency "activesupport", ">= 5.2", "<= 7.0"
27
+ spec.add_dependency "activesupport", ">= 5.2", "< 8"
28
28
  spec.add_dependency "rspec-core", '~> 3.10'
29
29
  spec.add_dependency "rspec-expectations", '~> 3.10'
30
30
  spec.add_dependency "websocket", '~> 1.2'
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.6.3
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Buildkite
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-19 00:00:00.000000000 Z
11
+ date: 2022-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -17,9 +17,9 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '5.2'
20
- - - "<="
20
+ - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '7.0'
22
+ version: '8'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,9 +27,9 @@ dependencies:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
29
  version: '5.2'
30
- - - "<="
30
+ - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '7.0'
32
+ version: '8'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: rspec-core
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -81,6 +81,7 @@ extra_rdoc_files: []
81
81
  files:
82
82
  - ".gitignore"
83
83
  - ".rspec"
84
+ - CHANGELOG.md
84
85
  - Gemfile
85
86
  - Gemfile.lock
86
87
  - LICENSE.txt
@@ -121,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
122
  - !ruby/object:Gem::Version
122
123
  version: '0'
123
124
  requirements: []
124
- rubygems_version: 3.1.4
125
+ rubygems_version: 3.3.3
125
126
  signing_key:
126
127
  specification_version: 4
127
128
  summary: Track execution of specs and report to Buildkite Analytics