ci-queue 0.22.1 → 0.24.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/README.md +6 -0
- data/dev.yml +1 -1
- data/{railgun.yml → isogun.yml} +0 -3
- data/lib/ci/queue/configuration.rb +15 -3
- data/lib/ci/queue/redis/base.rb +10 -0
- data/lib/ci/queue/redis/build_record.rb +2 -0
- data/lib/ci/queue/redis/grind_record.rb +2 -0
- data/lib/ci/queue/redis/supervisor.rb +19 -3
- data/lib/ci/queue/redis/test_time_record.rb +2 -0
- data/lib/ci/queue/redis/worker.rb +11 -0
- data/lib/ci/queue/version.rb +1 -1
- data/lib/minitest/queue/runner.rb +38 -1
- data/lib/rspec/queue.rb +9 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f51b9eaf62e347a724e6e1fc533c26d504cca7922a4296b750ae2599c7cfa6e
|
4
|
+
data.tar.gz: 0b9833d46d95e90d67663a2ff5bea2a8ad17c781b812a9f8b4ec7485bf0789d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b627008782774ad37ae5dbd5a2db0ae24df1f3fcda1dae9e0c0064f7c8df7890b926734855fd0f35d440e5e03d25441b6b9dc60c48bcaa19b8c5537c5d33c98b
|
7
|
+
data.tar.gz: e3d1c41abfe02341deb545fda1e6eebb94826801ca104788d2aa48fab1886e6093acd402ef097f58c6b2bc4f403568386e45d1b8032f1323d4f7cfbf6f7f381b
|
data/README.md
CHANGED
@@ -70,3 +70,9 @@ rspec-queue --queue redis://example.com --timeout 600 --report
|
|
70
70
|
#### Limitations
|
71
71
|
|
72
72
|
Because of how `ci-queue` executes the examples, `before(:all)` and `after(:all)` hooks are not supported. `rspec-queue` will explicitly reject them.
|
73
|
+
|
74
|
+
## Custom Redis Expiry
|
75
|
+
|
76
|
+
`ci-queue` expects the Redis server to have an [eviction policy](https://redis.io/docs/manual/eviction/#eviction-policies) of `allkeys-lru`.
|
77
|
+
|
78
|
+
You can also use `--redis-ttl` to set a custom expiration time for all CI Queue keys, this defaults to 8 hours (28,800 seconds)
|
data/dev.yml
CHANGED
data/{railgun.yml → isogun.yml}
RENAMED
@@ -5,10 +5,10 @@ module CI
|
|
5
5
|
attr_accessor :timeout, :worker_id, :max_requeues, :grind_count, :failure_file
|
6
6
|
attr_accessor :requeue_tolerance, :namespace, :failing_test, :statsd_endpoint
|
7
7
|
attr_accessor :max_test_duration, :max_test_duration_percentile, :track_test_duration
|
8
|
-
attr_accessor :max_test_failed
|
8
|
+
attr_accessor :max_test_failed, :redis_ttl
|
9
9
|
attr_reader :circuit_breakers
|
10
10
|
attr_writer :seed, :build_id
|
11
|
-
attr_writer :queue_init_timeout
|
11
|
+
attr_writer :queue_init_timeout, :report_timeout, :inactive_workers_timeout
|
12
12
|
|
13
13
|
class << self
|
14
14
|
def from_env(env)
|
@@ -18,6 +18,7 @@ module CI
|
|
18
18
|
seed: env['CIRCLE_SHA1'] || env['BUILDKITE_COMMIT'] || env['TRAVIS_COMMIT'] || env['HEROKU_TEST_RUN_COMMIT_VERSION'] || env['SEMAPHORE_GIT_SHA'],
|
19
19
|
flaky_tests: load_flaky_tests(env['CI_QUEUE_FLAKY_TESTS']),
|
20
20
|
statsd_endpoint: env['CI_QUEUE_STATSD_ADDR'],
|
21
|
+
redis_ttl: env['CI_QUEUE_REDIS_TTL']&.to_i || 8 * 60 * 60,
|
21
22
|
)
|
22
23
|
end
|
23
24
|
|
@@ -34,7 +35,7 @@ module CI
|
|
34
35
|
namespace: nil, seed: nil, flaky_tests: [], statsd_endpoint: nil, max_consecutive_failures: nil,
|
35
36
|
grind_count: nil, max_duration: nil, failure_file: nil, max_test_duration: nil,
|
36
37
|
max_test_duration_percentile: 0.5, track_test_duration: false, max_test_failed: nil,
|
37
|
-
queue_init_timeout: nil
|
38
|
+
queue_init_timeout: nil, redis_ttl: 8 * 60 * 60, report_timeout: nil, inactive_workers_timeout: nil
|
38
39
|
)
|
39
40
|
@build_id = build_id
|
40
41
|
@circuit_breakers = [CircuitBreaker::Disabled]
|
@@ -55,12 +56,23 @@ module CI
|
|
55
56
|
@worker_id = worker_id
|
56
57
|
self.max_consecutive_failures = max_consecutive_failures
|
57
58
|
self.max_duration = max_duration
|
59
|
+
@redis_ttl = redis_ttl
|
60
|
+
@report_timeout = report_timeout
|
61
|
+
@inactive_workers_timeout = inactive_workers_timeout
|
58
62
|
end
|
59
63
|
|
60
64
|
def queue_init_timeout
|
61
65
|
@queue_init_timeout || timeout
|
62
66
|
end
|
63
67
|
|
68
|
+
def report_timeout
|
69
|
+
@report_timeout || timeout
|
70
|
+
end
|
71
|
+
|
72
|
+
def inactive_workers_timeout
|
73
|
+
@inactive_workers_timeout || timeout
|
74
|
+
end
|
75
|
+
|
64
76
|
def max_consecutive_failures=(max)
|
65
77
|
if max
|
66
78
|
@circuit_breakers << CircuitBreaker::MaxConsecutiveFailures.new(max_consecutive_failures: max)
|
data/lib/ci/queue/redis/base.rb
CHANGED
@@ -5,6 +5,7 @@ module CI
|
|
5
5
|
class Base
|
6
6
|
include Common
|
7
7
|
|
8
|
+
TEN_MINUTES = 60 * 10
|
8
9
|
CONNECTION_ERRORS = [
|
9
10
|
::Redis::BaseConnectionError,
|
10
11
|
::SocketError, # https://github.com/redis/redis-rb/pull/631
|
@@ -20,6 +21,15 @@ module CI
|
|
20
21
|
queue_initialized? && size == 0
|
21
22
|
end
|
22
23
|
|
24
|
+
def expired?
|
25
|
+
if (created_at = redis.get(key('master-created-at')))
|
26
|
+
(created_at.to_f + config.redis_ttl + TEN_MINUTES) < Time.now.to_f
|
27
|
+
else
|
28
|
+
# if there is no created at set anymore we assume queue is expired
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
23
33
|
def size
|
24
34
|
redis.multi do |transaction|
|
25
35
|
transaction.llen(key('queue'))
|
@@ -41,6 +41,7 @@ module CI
|
|
41
41
|
id.dup.force_encoding(Encoding::BINARY),
|
42
42
|
payload.dup.force_encoding(Encoding::BINARY),
|
43
43
|
)
|
44
|
+
pipeline.expire(key('error-reports'), config.redis_ttl)
|
44
45
|
record_stats(stats, pipeline: pipeline)
|
45
46
|
end
|
46
47
|
nil
|
@@ -90,6 +91,7 @@ module CI
|
|
90
91
|
return unless stats
|
91
92
|
stats.each do |stat_name, stat_value|
|
92
93
|
pipeline.hset(key(stat_name), config.worker_id, stat_value)
|
94
|
+
pipeline.expire(key(stat_name), config.redis_ttl)
|
93
95
|
end
|
94
96
|
end
|
95
97
|
|
@@ -16,6 +16,7 @@ module CI
|
|
16
16
|
key('error-reports'),
|
17
17
|
payload.force_encoding(Encoding::BINARY),
|
18
18
|
)
|
19
|
+
pipeline.expire(key('error-reports'), config.redis_ttl)
|
19
20
|
record_stats(stats, pipeline: pipeline)
|
20
21
|
end
|
21
22
|
nil
|
@@ -58,6 +59,7 @@ module CI
|
|
58
59
|
return unless stats
|
59
60
|
stats.each do |stat_name, stat_value|
|
60
61
|
pipeline.hset(key(stat_name), config.worker_id, stat_value)
|
62
|
+
pipeline.expire(key(stat_name), config.redis_ttl)
|
61
63
|
end
|
62
64
|
end
|
63
65
|
end
|
@@ -21,17 +21,33 @@ module CI
|
|
21
21
|
|
22
22
|
yield if block_given?
|
23
23
|
|
24
|
-
time_left = config.
|
25
|
-
|
26
|
-
|
24
|
+
time_left = config.report_timeout
|
25
|
+
time_left_with_no_workers = config.inactive_workers_timeout
|
26
|
+
until exhausted? || time_left <= 0 || max_test_failed? || time_left_with_no_workers <= 0
|
27
27
|
time_left -= 1
|
28
|
+
sleep 1
|
29
|
+
|
30
|
+
if active_workers?
|
31
|
+
time_left_with_no_workers = config.inactive_workers_timeout
|
32
|
+
else
|
33
|
+
time_left_with_no_workers -= 1
|
34
|
+
end
|
28
35
|
|
29
36
|
yield if block_given?
|
30
37
|
end
|
38
|
+
|
39
|
+
puts "Aborting, it seems all workers died." if time_left_with_no_workers <= 0
|
31
40
|
exhausted?
|
32
41
|
rescue CI::Queue::Redis::LostMaster
|
33
42
|
false
|
34
43
|
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def active_workers?
|
48
|
+
# if there are running jobs we assume there are still agents active
|
49
|
+
redis.zrangebyscore(key('running'), Time.now.to_f - config.timeout, "+inf", limit: [0,1]).count > 0
|
50
|
+
end
|
35
51
|
end
|
36
52
|
end
|
37
53
|
end
|
@@ -24,6 +24,7 @@ module CI
|
|
24
24
|
test_time_key(test_name),
|
25
25
|
duration.to_s.force_encoding(Encoding::BINARY),
|
26
26
|
)
|
27
|
+
pipeline.expire(test_time_key(test_name), config.redis_ttl)
|
27
28
|
end
|
28
29
|
nil
|
29
30
|
end
|
@@ -34,6 +35,7 @@ module CI
|
|
34
35
|
all_test_names_key,
|
35
36
|
test_name.dup.force_encoding(Encoding::BINARY),
|
36
37
|
)
|
38
|
+
pipeline.expire(all_test_names_key, config.redis_ttl)
|
37
39
|
end
|
38
40
|
nil
|
39
41
|
end
|
@@ -53,6 +53,10 @@ module CI
|
|
53
53
|
sleep 0.05
|
54
54
|
end
|
55
55
|
end
|
56
|
+
redis.pipelined do |pipeline|
|
57
|
+
pipeline.expire(key('worker', worker_id, 'queue'), config.redis_ttl)
|
58
|
+
pipeline.expire(key('processed'), config.redis_ttl)
|
59
|
+
end
|
56
60
|
rescue *CONNECTION_ERRORS
|
57
61
|
end
|
58
62
|
|
@@ -198,9 +202,16 @@ module CI
|
|
198
202
|
transaction.lpush(key('queue'), tests) unless tests.empty?
|
199
203
|
transaction.set(key('total'), @total)
|
200
204
|
transaction.set(key('master-status'), 'ready')
|
205
|
+
transaction.set(key('master-created-at'), Time.now.to_f)
|
206
|
+
|
207
|
+
transaction.expire(key('queue'), config.redis_ttl)
|
208
|
+
transaction.expire(key('total'), config.redis_ttl)
|
209
|
+
transaction.expire(key('master-status'), config.redis_ttl)
|
210
|
+
transaction.expire(key('master-created-at'), config.redis_ttl)
|
201
211
|
end
|
202
212
|
end
|
203
213
|
register
|
214
|
+
redis.expire(key('workers'), config.redis_ttl)
|
204
215
|
rescue *CONNECTION_ERRORS
|
205
216
|
raise if @master
|
206
217
|
end
|
data/lib/ci/queue/version.rb
CHANGED
@@ -22,6 +22,9 @@ module Minitest
|
|
22
22
|
def initialize(argv)
|
23
23
|
@queue_config = CI::Queue::Configuration.from_env(ENV)
|
24
24
|
@command, @argv = parse(argv)
|
25
|
+
if Minitest.respond_to?(:seed=)
|
26
|
+
Minitest.seed = @queue_config.seed.to_i
|
27
|
+
end
|
25
28
|
end
|
26
29
|
|
27
30
|
def run!
|
@@ -44,7 +47,10 @@ module Minitest
|
|
44
47
|
end
|
45
48
|
|
46
49
|
def run_command
|
47
|
-
if queue.retrying?
|
50
|
+
if queue.retrying? || retry?
|
51
|
+
if queue.expired?
|
52
|
+
abort! "The test run is too old and can't be retried"
|
53
|
+
end
|
48
54
|
reset_counters
|
49
55
|
retry_queue = queue.retry_queue
|
50
56
|
if retry_queue.exhausted?
|
@@ -364,6 +370,24 @@ module Minitest
|
|
364
370
|
queue_config.timeout = timeout
|
365
371
|
end
|
366
372
|
|
373
|
+
help = <<~EOS
|
374
|
+
Specify a timeout after which the report command will fail if not all tests have been processed.
|
375
|
+
Defaults to the value set for --timeout.
|
376
|
+
EOS
|
377
|
+
opts.separator ""
|
378
|
+
opts.on('--report-timeout TIMEOUT', Float, help) do |timeout|
|
379
|
+
queue_config.report_timeout = timeout
|
380
|
+
end
|
381
|
+
|
382
|
+
help = <<~EOS
|
383
|
+
Specify a timeout after the report will fail if all workers are inactive (e.g. died).
|
384
|
+
Defaults to the value set for --timeout.
|
385
|
+
EOS
|
386
|
+
opts.separator ""
|
387
|
+
opts.on('--inactive-workers-timeout TIMEOUT', Float, help) do |timeout|
|
388
|
+
queue_config.inactive_workers_timeout = timeout
|
389
|
+
end
|
390
|
+
|
367
391
|
help = <<~EOS
|
368
392
|
Specify a timeout to elect the leader and populate the queue.
|
369
393
|
Defaults to the value set for --timeout.
|
@@ -487,6 +511,14 @@ module Minitest
|
|
487
511
|
end
|
488
512
|
end
|
489
513
|
|
514
|
+
help = <<~EOS
|
515
|
+
Defines how long the test report remain after the test run, in seconds.
|
516
|
+
Defaults to 28,800 (8 hours)
|
517
|
+
EOS
|
518
|
+
opts.on("--redis-ttl SECONDS", Integer, help) do |time|
|
519
|
+
queue.config.redis_ttl = time
|
520
|
+
end
|
521
|
+
|
490
522
|
opts.separator ""
|
491
523
|
opts.separator " retry: Replays a previous run in the same order."
|
492
524
|
|
@@ -535,6 +567,11 @@ module Minitest
|
|
535
567
|
puts red(message)
|
536
568
|
exit! 1 # exit! is required to avoid minitest at_exit callback
|
537
569
|
end
|
570
|
+
|
571
|
+
def retry?
|
572
|
+
ENV["BUILDKITE_RETRY_COUNT"].to_i > 0 ||
|
573
|
+
ENV["SEMAPHORE_PIPELINE_RERUN"] == "true"
|
574
|
+
end
|
538
575
|
end
|
539
576
|
end
|
540
577
|
end
|
data/lib/rspec/queue.rb
CHANGED
@@ -157,6 +157,15 @@ module RSpec
|
|
157
157
|
queue_config.max_consecutive_failures = Integer(max)
|
158
158
|
end
|
159
159
|
|
160
|
+
help = <<~EOS
|
161
|
+
Defines how long the test report remain after the test run, in seconds.
|
162
|
+
Defaults to 28,800 (8 hours)
|
163
|
+
EOS
|
164
|
+
parser.separator ""
|
165
|
+
parser.on("--redis-ttl SECONDS", Integer, help) do |time|
|
166
|
+
queue.config.redis_ttl = time
|
167
|
+
end
|
168
|
+
|
160
169
|
parser
|
161
170
|
end
|
162
171
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ci-queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.24.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-07-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -171,6 +171,7 @@ files:
|
|
171
171
|
- dev.yml
|
172
172
|
- exe/minitest-queue
|
173
173
|
- exe/rspec-queue
|
174
|
+
- isogun.yml
|
174
175
|
- lib/ci/queue.rb
|
175
176
|
- lib/ci/queue/bisect.rb
|
176
177
|
- lib/ci/queue/build_record.rb
|
@@ -218,7 +219,6 @@ files:
|
|
218
219
|
- lib/rspec/queue.rb
|
219
220
|
- lib/rspec/queue/build_status_recorder.rb
|
220
221
|
- lib/rspec/queue/order_recorder.rb
|
221
|
-
- railgun.yml
|
222
222
|
homepage: https://github.com/Shopify/ci-queue
|
223
223
|
licenses:
|
224
224
|
- MIT
|
@@ -239,7 +239,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
239
239
|
- !ruby/object:Gem::Version
|
240
240
|
version: '0'
|
241
241
|
requirements: []
|
242
|
-
rubygems_version: 3.
|
242
|
+
rubygems_version: 3.3.3
|
243
243
|
signing_key:
|
244
244
|
specification_version: 4
|
245
245
|
summary: Distribute tests over many workers using a queue
|