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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33a7fd6d204f0b946f8bc4007e1e948473cfecdcc9b822790f9832f1f2f726f6
4
- data.tar.gz: 80c7105ad884094d737ddcf140e2a491f5d84af71c83856ad6d1382d7fea39b7
3
+ metadata.gz: c9c3ef16af71af28cb4ba0a1b1cc6333549b29c59be0b4a5d81d04820c2b9b09
4
+ data.tar.gz: 3b7f66435ec75625bb68af73917bea553815ac76f526efb0bb954252fac35bc3
5
5
  SHA512:
6
- metadata.gz: 567e793b6d5114a37570fe3834966562aae6f1ba23484c2dfdb02dcaa6689aa8c6b4b35d5c705936273809af4abfe6f236c821eb655f9aed9f5af599faeb0b72
7
- data.tar.gz: 62bd24aafeaf69fd5e308f9a253e66c602b2fca3e0760572afe467f989ab1a2397edc37f7bdf60df662d00dedda43f2004b20c8bec971b0cf48b92452b50a6a2
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', "~> 10.0"
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
- class CircuitBreaker
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
- def initialize(max_consecutive_failures:)
54
- @max = max_consecutive_failures
55
- @consecutive_failures = 0
56
- end
53
+ class MaxConsecutiveFailures
54
+ def initialize(max_consecutive_failures:)
55
+ @max = max_consecutive_failures
56
+ @consecutive_failures = 0
57
+ end
57
58
 
58
- def report_failure!
59
- @consecutive_failures += 1
60
- end
59
+ def report_failure!
60
+ @consecutive_failures += 1
61
+ end
61
62
 
62
- def report_success!
63
- @consecutive_failures = 0
64
- end
63
+ def report_success!
64
+ @consecutive_failures = 0
65
+ end
65
66
 
66
- def open?
67
- @consecutive_failures >= @max
68
- end
67
+ def open?
68
+ @consecutive_failures >= @max
69
+ end
69
70
 
70
- def message
71
- 'This worker is exiting early because it encountered too many consecutive test failures, probably because of some corrupted state.'
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, :build_id, :worker_id, :max_requeues, :grind_count, :failure_file
6
- attr_accessor :requeue_tolerance, :namespace, :seed, :failing_test, :statsd_endpoint
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
- self.max_duration = max_duration
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
 
@@ -8,8 +8,8 @@ module CI
8
8
 
9
9
  private
10
10
 
11
- def step(*args)
12
- ci_provider.step(*args)
11
+ def step(*args, **kwargs)
12
+ ci_provider.step(*args, **kwargs)
13
13
  end
14
14
 
15
15
  def reopen_previous_step
@@ -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
- stat_names.zip(counts.map { |values| values.map(&:to_f).inject(:+).to_f }).to_h
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)
@@ -22,7 +22,7 @@ module CI
22
22
  yield if block_given?
23
23
 
24
24
  time_left = config.timeout
25
- until exhausted? || time_left <= 0
25
+ until exhausted? || time_left <= 0 || max_test_failed?
26
26
  sleep 1
27
27
  time_left -= 1
28
28
 
@@ -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
@@ -50,7 +50,7 @@ module CI
50
50
  end
51
51
 
52
52
  def poll
53
- while !config.circuit_breakers.any?(&:open?) && test = @queue.shift
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CI
4
4
  module Queue
5
- VERSION = '0.17.2'
5
+ VERSION = '0.18.0'
6
6
  DEV_SCRIPTS_ROOT = ::File.expand_path('../../../../../redis', __FILE__)
7
7
  RELEASE_SCRIPTS_ROOT = ::File.expand_path('../redis', __FILE__)
8
8
  end
@@ -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
@@ -27,6 +27,7 @@ module Minitest
27
27
  test_line: test_line,
28
28
  test_and_module_name: "#{test.klass}##{test.name}",
29
29
  test_name: test.name,
30
+ error_class: test.failure.exception.class.name,
30
31
  output: to_s,
31
32
  }
32
33
  end
@@ -181,7 +181,13 @@ module Minitest
181
181
  end
182
182
 
183
183
  unless supervisor.exhausted?
184
- abort! "#{supervisor.size} tests weren't run."
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.
@@ -19,7 +19,7 @@ module Minitest
19
19
  @socket = UDPSocket.new
20
20
  @socket.connect(host, Integer(port))
21
21
  end
22
- rescue SocketError => e
22
+ rescue SocketError
23
23
  # No-op, we shouldn't fail CI because of statsd
24
24
  end
25
25
 
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.17.2
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-02-04 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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: