ci-queue 0.87.0 → 0.89.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 +4 -4
- data/Gemfile.lock +5 -1
- data/ci-queue.gemspec +1 -0
- data/lib/ci/queue/queue_entry.rb +3 -1
- data/lib/ci/queue/redis/base.rb +20 -0
- data/lib/ci/queue/redis/build_record.rb +14 -0
- data/lib/ci/queue/redis/retry.rb +15 -0
- data/lib/ci/queue/redis/worker.rb +9 -0
- data/lib/ci/queue/version.rb +1 -1
- data/lib/minitest/queue.rb +10 -1
- data/lib/rspec/queue/build_status_recorder.rb +2 -2
- data/lib/rspec/queue.rb +4 -1
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 87b11dfe365c8f57a79ef1d5b65e4f422a1fce2bd94d51b0d653d10bfb95120a
|
|
4
|
+
data.tar.gz: 1a530c84f9883b85f0784b33e67044f5a325a5815dc06d4b89872b76d1127a74
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 25e7f86d44fc3428e6f1d08430a3940348ae9d2d2561c190894d7a6e1b197d00b22a97fefcb5ade8462d2ccc0e14255ff9d4f10571e5f1f99dd8758b45d8a066
|
|
7
|
+
data.tar.gz: 0c676475b941684ad92d43088612e31adb597f2f2c22f383e55c8d2dbc2cfa68646e013a58000191f5fecb16aee8151b5ce8f14cd97fe2d9ca2f11c3fd57cf3f
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
ci-queue (0.
|
|
4
|
+
ci-queue (0.89.0)
|
|
5
5
|
logger
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -43,6 +43,8 @@ GEM
|
|
|
43
43
|
builder
|
|
44
44
|
minitest (>= 5.0)
|
|
45
45
|
ruby-progressbar
|
|
46
|
+
mocha (3.1.0)
|
|
47
|
+
ruby2_keywords (>= 0.0.5)
|
|
46
48
|
msgpack (1.8.0)
|
|
47
49
|
parallel (1.27.0)
|
|
48
50
|
parser (3.3.10.2)
|
|
@@ -86,6 +88,7 @@ GEM
|
|
|
86
88
|
parser (>= 3.3.7.2)
|
|
87
89
|
prism (~> 1.7)
|
|
88
90
|
ruby-progressbar (1.13.0)
|
|
91
|
+
ruby2_keywords (0.0.5)
|
|
89
92
|
securerandom (0.4.1)
|
|
90
93
|
simplecov (0.22.0)
|
|
91
94
|
docile (~> 1.1)
|
|
@@ -112,6 +115,7 @@ DEPENDENCIES
|
|
|
112
115
|
ci-queue!
|
|
113
116
|
minitest (~> 5.11)
|
|
114
117
|
minitest-reporters (~> 1.1)
|
|
118
|
+
mocha
|
|
115
119
|
msgpack
|
|
116
120
|
rake
|
|
117
121
|
redis
|
data/ci-queue.gemspec
CHANGED
|
@@ -40,6 +40,7 @@ Gem::Specification.new do |spec|
|
|
|
40
40
|
spec.add_development_dependency 'redis'
|
|
41
41
|
spec.add_development_dependency 'simplecov', '~> 0.12'
|
|
42
42
|
spec.add_development_dependency 'minitest-reporters', '~> 1.1'
|
|
43
|
+
spec.add_development_dependency 'mocha'
|
|
43
44
|
|
|
44
45
|
spec.add_development_dependency 'rexml'
|
|
45
46
|
spec.add_development_dependency 'snappy'
|
data/lib/ci/queue/queue_entry.rb
CHANGED
|
@@ -17,7 +17,9 @@ module CI
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def self.format(test_id, file_path)
|
|
20
|
-
|
|
20
|
+
raise ArgumentError, "file_path is required for '#{test_id}' — the test file path must be resolvable" if file_path.nil? || file_path.empty?
|
|
21
|
+
canonical = load_error_payload?(file_path) ? file_path : ::File.expand_path(file_path)
|
|
22
|
+
JSON.dump({ test_id: test_id, file_path: canonical })
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def self.load_error_payload?(file_path)
|
data/lib/ci/queue/redis/base.rb
CHANGED
|
@@ -264,6 +264,8 @@ module CI
|
|
|
264
264
|
end
|
|
265
265
|
|
|
266
266
|
class HeartbeatProcess
|
|
267
|
+
MAX_RESTART_ATTEMPTS = 3
|
|
268
|
+
|
|
267
269
|
def initialize(redis_url, zset_key, owners_key, leases_key)
|
|
268
270
|
@redis_url = redis_url
|
|
269
271
|
@zset_key = zset_key
|
|
@@ -313,10 +315,28 @@ module CI
|
|
|
313
315
|
|
|
314
316
|
def tick!(id, lease)
|
|
315
317
|
send_message(:tick!, id: id, lease: lease.to_s)
|
|
318
|
+
@restart_attempts = 0
|
|
319
|
+
rescue IOError, Errno::EPIPE => error
|
|
320
|
+
@restart_attempts = (@restart_attempts || 0) + 1
|
|
321
|
+
raise if @restart_attempts > MAX_RESTART_ATTEMPTS
|
|
322
|
+
|
|
323
|
+
restart!
|
|
324
|
+
retry
|
|
316
325
|
end
|
|
317
326
|
|
|
318
327
|
private
|
|
319
328
|
|
|
329
|
+
def restart!
|
|
330
|
+
@pipe.close rescue nil
|
|
331
|
+
begin
|
|
332
|
+
Process.kill(:TERM, @pid)
|
|
333
|
+
Process.waitpid2(@pid)
|
|
334
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
|
335
|
+
nil
|
|
336
|
+
end
|
|
337
|
+
boot!
|
|
338
|
+
end
|
|
339
|
+
|
|
320
340
|
def send_message(*message)
|
|
321
341
|
payload = message.to_json
|
|
322
342
|
@pipe.write([payload.bytesize].pack("L").b, payload)
|
|
@@ -145,6 +145,10 @@ module CI
|
|
|
145
145
|
redis.hgetall(key('error-reports')).transform_keys { |entry| CI::Queue::QueueEntry.test_id(entry) }
|
|
146
146
|
end
|
|
147
147
|
|
|
148
|
+
def failed_test_entries
|
|
149
|
+
redis.hkeys(key('error-reports'))
|
|
150
|
+
end
|
|
151
|
+
|
|
148
152
|
def flaky_reports
|
|
149
153
|
redis.smembers(key('flaky-reports')).map { |entry| CI::Queue::QueueEntry.test_id(entry) }
|
|
150
154
|
end
|
|
@@ -177,6 +181,16 @@ module CI
|
|
|
177
181
|
pipeline.hdel(key(stat_name), config.worker_id)
|
|
178
182
|
end
|
|
179
183
|
end
|
|
184
|
+
# Purge any error-report-deltas that reference this worker so that
|
|
185
|
+
# apply_error_report_delta_correction cannot double-subtract from
|
|
186
|
+
# the now-zeroed counters on a subsequent successful retry.
|
|
187
|
+
deltas = redis.hgetall(key('error-report-deltas'))
|
|
188
|
+
to_delete = deltas.filter_map do |entry, delta_json|
|
|
189
|
+
entry if JSON.parse(delta_json)['worker_id'].to_s == config.worker_id.to_s
|
|
190
|
+
rescue JSON::ParserError
|
|
191
|
+
nil
|
|
192
|
+
end
|
|
193
|
+
redis.hdel(key('error-report-deltas'), *to_delete) unless to_delete.empty?
|
|
180
194
|
end
|
|
181
195
|
|
|
182
196
|
private
|
data/lib/ci/queue/redis/retry.rb
CHANGED
|
@@ -28,9 +28,24 @@ module CI
|
|
|
28
28
|
self
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
# Queue a Redis SADD so that BuildRecord#record_success can include this
|
|
32
|
+
# in its multi-exec transaction. Without this, Static#acknowledge returns
|
|
33
|
+
# a Ruby value (not a Redis future), shifting the result indices and
|
|
34
|
+
# breaking the stats delta correction.
|
|
35
|
+
def acknowledge(entry, error: nil, pipeline: redis)
|
|
36
|
+
@progress += 1
|
|
37
|
+
return @progress unless pipeline
|
|
38
|
+
test_id = CI::Queue::QueueEntry.test_id(entry)
|
|
39
|
+
pipeline.sadd(key('processed'), test_id)
|
|
40
|
+
end
|
|
41
|
+
|
|
31
42
|
private
|
|
32
43
|
|
|
33
44
|
attr_reader :redis
|
|
45
|
+
|
|
46
|
+
def key(*args)
|
|
47
|
+
['build', config.build_id, *args].join(':')
|
|
48
|
+
end
|
|
34
49
|
end
|
|
35
50
|
end
|
|
36
51
|
end
|
|
@@ -158,6 +158,15 @@ module CI
|
|
|
158
158
|
log.select! { |entry| failures.include?(CI::Queue::QueueEntry.test_id(entry)) }
|
|
159
159
|
log.uniq! { |entry| CI::Queue::QueueEntry.test_id(entry) }
|
|
160
160
|
log.reverse!
|
|
161
|
+
|
|
162
|
+
if log.empty?
|
|
163
|
+
# Per-worker log has no matching failures — this worker didn't run
|
|
164
|
+
# the failing tests (e.g. Buildkite rebuild with new worker IDs,
|
|
165
|
+
# or a different parallel slot). Fall back to ALL unresolved
|
|
166
|
+
# failures from error-reports so any worker can retry them.
|
|
167
|
+
log = redis.hkeys(key('error-reports'))
|
|
168
|
+
end
|
|
169
|
+
|
|
161
170
|
Retry.new(log, config, redis: redis)
|
|
162
171
|
end
|
|
163
172
|
|
data/lib/ci/queue/version.rb
CHANGED
data/lib/minitest/queue.rb
CHANGED
|
@@ -330,7 +330,16 @@ module Minitest
|
|
|
330
330
|
end
|
|
331
331
|
|
|
332
332
|
def queue_entry
|
|
333
|
-
@queue_entry ||=
|
|
333
|
+
@queue_entry ||= begin
|
|
334
|
+
unless runnable.is_a?(Module)
|
|
335
|
+
raise ArgumentError, "runnable must be a Module (got #{runnable.class}). " \
|
|
336
|
+
"Do not create SingleExample with string class names."
|
|
337
|
+
end
|
|
338
|
+
file_path = runnable.instance_method(method_name).source_location&.first
|
|
339
|
+
raise ArgumentError, "Cannot resolve source file for #{id} — " \
|
|
340
|
+
"ensure the test method is defined in a Ruby source file" if file_path.nil?
|
|
341
|
+
CI::Queue::QueueEntry.format(id, file_path)
|
|
342
|
+
end
|
|
334
343
|
end
|
|
335
344
|
|
|
336
345
|
def <=>(other)
|
|
@@ -18,13 +18,13 @@ module RSpec
|
|
|
18
18
|
|
|
19
19
|
def example_passed(notification)
|
|
20
20
|
example = notification.example
|
|
21
|
-
entry = CI::Queue::QueueEntry.format(example.id,
|
|
21
|
+
entry = CI::Queue::QueueEntry.format(example.id, example.file_path)
|
|
22
22
|
build.record_success(entry)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def example_failed(notification)
|
|
26
26
|
example = notification.example
|
|
27
|
-
entry = CI::Queue::QueueEntry.format(example.id,
|
|
27
|
+
entry = CI::Queue::QueueEntry.format(example.id, example.file_path)
|
|
28
28
|
build.record_error(entry, dump(notification))
|
|
29
29
|
end
|
|
30
30
|
|
data/lib/rspec/queue.rb
CHANGED
|
@@ -254,7 +254,10 @@ module RSpec
|
|
|
254
254
|
end
|
|
255
255
|
|
|
256
256
|
def queue_entry
|
|
257
|
-
@queue_entry ||=
|
|
257
|
+
@queue_entry ||= begin
|
|
258
|
+
file_path = example.metadata[:absolute_file_path] || example.file_path
|
|
259
|
+
CI::Queue::QueueEntry.format(id, file_path)
|
|
260
|
+
end
|
|
258
261
|
end
|
|
259
262
|
|
|
260
263
|
def <=>(other)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ci-queue
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.89.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jean Boussier
|
|
@@ -121,6 +121,20 @@ dependencies:
|
|
|
121
121
|
- - "~>"
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
123
|
version: '1.1'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: mocha
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0'
|
|
124
138
|
- !ruby/object:Gem::Dependency
|
|
125
139
|
name: rexml
|
|
126
140
|
requirement: !ruby/object:Gem::Requirement
|