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 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: