ci-queue 0.17.2 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|