rspec-buildkite-analytics 0.6.3 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
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