ci-queue 0.17.2 → 0.18.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/ci-queue.gemspec +3 -1
- data/lib/ci/queue/circuit_breaker.rb +18 -16
- data/lib/ci/queue/configuration.rb +12 -9
- data/lib/ci/queue/output_helpers.rb +2 -2
- data/lib/ci/queue/redis/base.rb +14 -0
- data/lib/ci/queue/redis/build_record.rb +10 -1
- data/lib/ci/queue/redis/supervisor.rb +1 -1
- data/lib/ci/queue/redis/worker.rb +2 -1
- data/lib/ci/queue/static.rb +16 -1
- data/lib/ci/queue/version.rb +1 -1
- data/lib/minitest/queue.rb +11 -0
- data/lib/minitest/queue/failure_formatter.rb +1 -0
- data/lib/minitest/queue/runner.rb +16 -1
- data/lib/minitest/queue/statsd.rb +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: c9c3ef16af71af28cb4ba0a1b1cc6333549b29c59be0b4a5d81d04820c2b9b09
|
4
|
+
data.tar.gz: 3b7f66435ec75625bb68af73917bea553815ac76f526efb0bb954252fac35bc3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 110d276a4d4a44cf98cba68be7e895991d510e14236fdad601fc9887ea13f6b1f67b3ec3df48b5210cac8b2185be2528cd39c1c9da67a0649660300594801878
|
7
|
+
data.tar.gz: 103c204787a1f3b29e9ebb676f5847eac789acd806f5eaabbce84a5e2f47e6af27ea48d7a6f6d725c25dbf78061d38e5cb10feb62586a0b8d0ea1d32f5230af5
|
data/ci-queue.gemspec
CHANGED
@@ -27,10 +27,12 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ['lib']
|
29
29
|
|
30
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
31
|
+
|
30
32
|
spec.add_dependency 'ansi'
|
31
33
|
|
32
34
|
spec.add_development_dependency 'bundler'
|
33
|
-
spec.add_development_dependency 'rake'
|
35
|
+
spec.add_development_dependency 'rake'
|
34
36
|
spec.add_development_dependency 'minitest', ENV.fetch('MINITEST_VERSION', '~> 5.11')
|
35
37
|
spec.add_development_dependency 'rspec', '~> 3.7.0'
|
36
38
|
spec.add_development_dependency 'redis', '~> 3.3'
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module CI
|
3
3
|
module Queue
|
4
|
-
|
4
|
+
module CircuitBreaker
|
5
5
|
module Disabled
|
6
6
|
extend self
|
7
7
|
|
@@ -50,25 +50,27 @@ module CI
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
class MaxConsecutiveFailures
|
54
|
+
def initialize(max_consecutive_failures:)
|
55
|
+
@max = max_consecutive_failures
|
56
|
+
@consecutive_failures = 0
|
57
|
+
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
59
|
+
def report_failure!
|
60
|
+
@consecutive_failures += 1
|
61
|
+
end
|
61
62
|
|
62
|
-
|
63
|
-
|
64
|
-
|
63
|
+
def report_success!
|
64
|
+
@consecutive_failures = 0
|
65
|
+
end
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
67
|
+
def open?
|
68
|
+
@consecutive_failures >= @max
|
69
|
+
end
|
69
70
|
|
70
|
-
|
71
|
-
|
71
|
+
def message
|
72
|
+
'This worker is exiting early because it encountered too many consecutive test failures, probably because of some corrupted state.'
|
73
|
+
end
|
72
74
|
end
|
73
75
|
end
|
74
76
|
end
|
@@ -2,10 +2,12 @@
|
|
2
2
|
module CI
|
3
3
|
module Queue
|
4
4
|
class Configuration
|
5
|
-
attr_accessor :timeout, :
|
6
|
-
attr_accessor :requeue_tolerance, :namespace, :
|
5
|
+
attr_accessor :timeout, :worker_id, :max_requeues, :grind_count, :failure_file
|
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
9
|
attr_reader :circuit_breakers
|
10
|
+
attr_writer :seed, :build_id
|
9
11
|
|
10
12
|
class << self
|
11
13
|
def from_env(env)
|
@@ -30,30 +32,31 @@ module CI
|
|
30
32
|
timeout: 30, build_id: nil, worker_id: nil, max_requeues: 0, requeue_tolerance: 0,
|
31
33
|
namespace: nil, seed: nil, flaky_tests: [], statsd_endpoint: nil, max_consecutive_failures: nil,
|
32
34
|
grind_count: nil, max_duration: nil, failure_file: nil, max_test_duration: nil,
|
33
|
-
max_test_duration_percentile: 0.5, track_test_duration: false
|
35
|
+
max_test_duration_percentile: 0.5, track_test_duration: false, max_test_failed: nil
|
34
36
|
)
|
35
|
-
@circuit_breakers = [CircuitBreaker::Disabled]
|
36
37
|
@build_id = build_id
|
38
|
+
@circuit_breakers = [CircuitBreaker::Disabled]
|
37
39
|
@failure_file = failure_file
|
38
40
|
@flaky_tests = flaky_tests
|
39
41
|
@grind_count = grind_count
|
40
42
|
@max_requeues = max_requeues
|
43
|
+
@max_test_duration = max_test_duration
|
44
|
+
@max_test_duration_percentile = max_test_duration_percentile
|
45
|
+
@max_test_failed = max_test_failed
|
41
46
|
@namespace = namespace
|
42
47
|
@requeue_tolerance = requeue_tolerance
|
43
48
|
@seed = seed
|
44
49
|
@statsd_endpoint = statsd_endpoint
|
45
50
|
@timeout = timeout
|
46
|
-
@worker_id = worker_id
|
47
|
-
@max_test_duration = max_test_duration
|
48
|
-
@max_test_duration_percentile = max_test_duration_percentile
|
49
51
|
@track_test_duration = track_test_duration
|
50
|
-
|
52
|
+
@worker_id = worker_id
|
51
53
|
self.max_consecutive_failures = max_consecutive_failures
|
54
|
+
self.max_duration = max_duration
|
52
55
|
end
|
53
56
|
|
54
57
|
def max_consecutive_failures=(max)
|
55
58
|
if max
|
56
|
-
@circuit_breakers << CircuitBreaker.new(max_consecutive_failures: max)
|
59
|
+
@circuit_breakers << CircuitBreaker::MaxConsecutiveFailures.new(max_consecutive_failures: max)
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|
data/lib/ci/queue/redis/base.rb
CHANGED
@@ -61,6 +61,20 @@ module CI
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
+
def increment_test_failed
|
65
|
+
redis.incr(key('test_failed_count'))
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_failed
|
69
|
+
redis.get(key('test_failed_count')).to_i
|
70
|
+
end
|
71
|
+
|
72
|
+
def max_test_failed?
|
73
|
+
return false if config.max_test_failed.nil?
|
74
|
+
|
75
|
+
test_failed >= config.max_test_failed
|
76
|
+
end
|
77
|
+
|
64
78
|
private
|
65
79
|
|
66
80
|
attr_reader :redis, :redis_url
|
@@ -54,6 +54,12 @@ module CI
|
|
54
54
|
nil
|
55
55
|
end
|
56
56
|
|
57
|
+
def max_test_failed?
|
58
|
+
return false if config.max_test_failed.nil?
|
59
|
+
|
60
|
+
@queue.test_failures >= config.max_test_failed
|
61
|
+
end
|
62
|
+
|
57
63
|
def error_reports
|
58
64
|
redis.hgetall(key('error-reports'))
|
59
65
|
end
|
@@ -62,7 +68,10 @@ module CI
|
|
62
68
|
counts = redis.pipelined do
|
63
69
|
stat_names.each { |c| redis.hvals(key(c)) }
|
64
70
|
end
|
65
|
-
|
71
|
+
sum_counts = counts.map do |values|
|
72
|
+
values.map(&:to_f).inject(:+).to_f
|
73
|
+
end
|
74
|
+
stat_names.zip(sum_counts).to_h
|
66
75
|
end
|
67
76
|
|
68
77
|
def reset_stats(stat_names)
|
@@ -15,6 +15,7 @@ module CI
|
|
15
15
|
attr_reader :total
|
16
16
|
|
17
17
|
def initialize(redis, config)
|
18
|
+
@last_warning = nil
|
18
19
|
@reserved_test = nil
|
19
20
|
@shutdown_required = false
|
20
21
|
super(redis, config)
|
@@ -45,7 +46,7 @@ module CI
|
|
45
46
|
|
46
47
|
def poll
|
47
48
|
wait_for_master
|
48
|
-
until shutdown_required? || config.circuit_breakers.any?(&:open?) || exhausted?
|
49
|
+
until shutdown_required? || config.circuit_breakers.any?(&:open?) || exhausted? || max_test_failed?
|
49
50
|
if test = reserve
|
50
51
|
yield index.fetch(test), @last_warning
|
51
52
|
else
|
data/lib/ci/queue/static.rb
CHANGED
@@ -50,7 +50,7 @@ module CI
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def poll
|
53
|
-
while
|
53
|
+
while config.circuit_breakers.none?(&:open?) && !max_test_failed? && test = @queue.shift
|
54
54
|
yield index.fetch(test)
|
55
55
|
end
|
56
56
|
end
|
@@ -64,9 +64,24 @@ module CI
|
|
64
64
|
true
|
65
65
|
end
|
66
66
|
|
67
|
+
def increment_test_failed
|
68
|
+
@test_failed = test_failed + 1
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_failed
|
72
|
+
@test_failed ||= 0
|
73
|
+
end
|
74
|
+
|
75
|
+
def max_test_failed?
|
76
|
+
return false if config.max_test_failed.nil?
|
77
|
+
|
78
|
+
test_failed >= config.max_test_failed
|
79
|
+
end
|
80
|
+
|
67
81
|
def requeue(test)
|
68
82
|
test_key = test.id
|
69
83
|
return false unless should_requeue?(test_key)
|
84
|
+
|
70
85
|
requeues[test_key] += 1
|
71
86
|
@queue.unshift(test_key)
|
72
87
|
true
|
data/lib/ci/queue/version.rb
CHANGED
data/lib/minitest/queue.rb
CHANGED
@@ -88,6 +88,7 @@ module Minitest
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def flaked?
|
91
|
+
@flaky ||= false
|
91
92
|
!!((Flaked === failure) || @flaky)
|
92
93
|
end
|
93
94
|
|
@@ -153,6 +154,10 @@ module Minitest
|
|
153
154
|
if queue.config.circuit_breakers.any?(&:open?)
|
154
155
|
STDERR.puts queue.config.circuit_breakers.map(&:message).join(' ').strip
|
155
156
|
end
|
157
|
+
|
158
|
+
if queue.max_test_failed?
|
159
|
+
STDERR.puts 'This worker is exiting early because too many failed tests were encountered.'
|
160
|
+
end
|
156
161
|
else
|
157
162
|
super
|
158
163
|
end
|
@@ -174,7 +179,9 @@ module Minitest
|
|
174
179
|
queue.report_success!
|
175
180
|
end
|
176
181
|
|
182
|
+
requeued = false
|
177
183
|
if failed && queue.requeue(example)
|
184
|
+
requeued = true
|
178
185
|
result.requeue!
|
179
186
|
reporter.record(result)
|
180
187
|
elsif queue.acknowledge(example) || !failed
|
@@ -182,6 +189,10 @@ module Minitest
|
|
182
189
|
# Then we only record it if it is successful.
|
183
190
|
reporter.record(result)
|
184
191
|
end
|
192
|
+
|
193
|
+
if !requeued && failed
|
194
|
+
queue.increment_test_failed
|
195
|
+
end
|
185
196
|
end
|
186
197
|
end
|
187
198
|
end
|
@@ -181,7 +181,13 @@ module Minitest
|
|
181
181
|
end
|
182
182
|
|
183
183
|
unless supervisor.exhausted?
|
184
|
-
|
184
|
+
msg = "#{supervisor.size} tests weren't run."
|
185
|
+
if supervisor.max_test_failed?
|
186
|
+
puts('Encountered too many failed tests. Test run was ended early.')
|
187
|
+
puts(msg)
|
188
|
+
else
|
189
|
+
abort!(msg)
|
190
|
+
end
|
185
191
|
end
|
186
192
|
end
|
187
193
|
|
@@ -399,6 +405,15 @@ module Minitest
|
|
399
405
|
queue_config.max_duration = max
|
400
406
|
end
|
401
407
|
|
408
|
+
help = <<~EOS
|
409
|
+
Defines how many user test tests can be fail.
|
410
|
+
Defaults to none.
|
411
|
+
EOS
|
412
|
+
opts.separator ""
|
413
|
+
opts.on('--max-test-failed MAX', Integer, help) do |max|
|
414
|
+
queue_config.max_test_failed = max
|
415
|
+
end
|
416
|
+
|
402
417
|
help = <<~EOS
|
403
418
|
Defines how many requeues can happen overall, based on the test suite size. e.g 0.05 for 5%.
|
404
419
|
Defaults to 0.
|
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.18.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: 2020-
|
11
|
+
date: 2020-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ansi
|
@@ -42,16 +42,16 @@ dependencies:
|
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: minitest
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -235,7 +235,8 @@ files:
|
|
235
235
|
homepage: https://github.com/Shopify/ci-queue
|
236
236
|
licenses:
|
237
237
|
- MIT
|
238
|
-
metadata:
|
238
|
+
metadata:
|
239
|
+
allowed_push_host: https://rubygems.org
|
239
240
|
post_install_message:
|
240
241
|
rdoc_options: []
|
241
242
|
require_paths:
|