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 +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
|