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 +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +5 -5
- data/lib/rspec/buildkite/analytics/ci.rb +79 -24
- data/lib/rspec/buildkite/analytics/session.rb +32 -26
- data/lib/rspec/buildkite/analytics/uploader.rb +15 -3
- data/lib/rspec/buildkite/analytics/version.rb +1 -1
- data/lib/rspec/buildkite/analytics.rb +6 -0
- data/rspec-buildkite-analytics.gemspec +1 -1
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ac283b3f333632c2909b0dd7b35dd1c4f9765d659489c00d8ae93a169433aa8
|
4
|
+
data.tar.gz: eb45c11b5da7a61597b915749f04bb77d46408b764f22cb84dba349ae3fba4f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
5
|
-
activesupport (>= 5.2,
|
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.
|
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.
|
18
|
+
concurrent-ruby (1.1.10)
|
19
19
|
diff-lcs (1.4.4)
|
20
|
-
i18n (1.
|
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
|
-
|
5
|
+
class RSpec::Buildkite::Analytics::CI
|
6
6
|
def self.env
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
@
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
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
|
268
|
-
|
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
|
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
|
-
|
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
|
-
|
295
|
-
|
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
|
-
|
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
|
-
|
326
|
-
|
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
|
-
|
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
|
@@ -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", "
|
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.
|
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-
|
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: '
|
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: '
|
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.
|
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
|