ci-queue 0.87.0 → 0.89.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: 8255493f3c842cc8d7a269e8ad22a3aab8928d4d2f98034f09f5effffd9e5ebb
4
- data.tar.gz: d43ce59b7114d41afbf758973e6bdab5a676ddae8e1a1c56665597c60e6a6e72
3
+ metadata.gz: 87b11dfe365c8f57a79ef1d5b65e4f422a1fce2bd94d51b0d653d10bfb95120a
4
+ data.tar.gz: 1a530c84f9883b85f0784b33e67044f5a325a5815dc06d4b89872b76d1127a74
5
5
  SHA512:
6
- metadata.gz: c11eaad88240e00781014d7c8d4e3390e995e412d3ab09ad1f3375a853aca77d278f49c4d8563a3872cdd66e399ef1c107f1acd57344ad5b815202fc9ee8e17b
7
- data.tar.gz: 8e0c09faf71e486b5096b9a13f377842e4b59dde5ed59f5607b42733525612c810a57a178743d2cb7fcd1bdb83860a2331ef5a09ddbd73b0ec0aa18ee43829c9
6
+ metadata.gz: 25e7f86d44fc3428e6f1d08430a3940348ae9d2d2561c190894d7a6e1b197d00b22a97fefcb5ade8462d2ccc0e14255ff9d4f10571e5f1f99dd8758b45d8a066
7
+ data.tar.gz: 0c676475b941684ad92d43088612e31adb597f2f2c22f383e55c8d2dbc2cfa68646e013a58000191f5fecb16aee8151b5ce8f14cd97fe2d9ca2f11c3fd57cf3f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ci-queue (0.87.0)
4
+ ci-queue (0.89.0)
5
5
  logger
6
6
 
7
7
  GEM
@@ -43,6 +43,8 @@ GEM
43
43
  builder
44
44
  minitest (>= 5.0)
45
45
  ruby-progressbar
46
+ mocha (3.1.0)
47
+ ruby2_keywords (>= 0.0.5)
46
48
  msgpack (1.8.0)
47
49
  parallel (1.27.0)
48
50
  parser (3.3.10.2)
@@ -86,6 +88,7 @@ GEM
86
88
  parser (>= 3.3.7.2)
87
89
  prism (~> 1.7)
88
90
  ruby-progressbar (1.13.0)
91
+ ruby2_keywords (0.0.5)
89
92
  securerandom (0.4.1)
90
93
  simplecov (0.22.0)
91
94
  docile (~> 1.1)
@@ -112,6 +115,7 @@ DEPENDENCIES
112
115
  ci-queue!
113
116
  minitest (~> 5.11)
114
117
  minitest-reporters (~> 1.1)
118
+ mocha
115
119
  msgpack
116
120
  rake
117
121
  redis
data/ci-queue.gemspec CHANGED
@@ -40,6 +40,7 @@ Gem::Specification.new do |spec|
40
40
  spec.add_development_dependency 'redis'
41
41
  spec.add_development_dependency 'simplecov', '~> 0.12'
42
42
  spec.add_development_dependency 'minitest-reporters', '~> 1.1'
43
+ spec.add_development_dependency 'mocha'
43
44
 
44
45
  spec.add_development_dependency 'rexml'
45
46
  spec.add_development_dependency 'snappy'
@@ -17,7 +17,9 @@ module CI
17
17
  end
18
18
 
19
19
  def self.format(test_id, file_path)
20
- JSON.dump({ test_id: test_id, file_path: file_path })
20
+ raise ArgumentError, "file_path is required for '#{test_id}' — the test file path must be resolvable" if file_path.nil? || file_path.empty?
21
+ canonical = load_error_payload?(file_path) ? file_path : ::File.expand_path(file_path)
22
+ JSON.dump({ test_id: test_id, file_path: canonical })
21
23
  end
22
24
 
23
25
  def self.load_error_payload?(file_path)
@@ -264,6 +264,8 @@ module CI
264
264
  end
265
265
 
266
266
  class HeartbeatProcess
267
+ MAX_RESTART_ATTEMPTS = 3
268
+
267
269
  def initialize(redis_url, zset_key, owners_key, leases_key)
268
270
  @redis_url = redis_url
269
271
  @zset_key = zset_key
@@ -313,10 +315,28 @@ module CI
313
315
 
314
316
  def tick!(id, lease)
315
317
  send_message(:tick!, id: id, lease: lease.to_s)
318
+ @restart_attempts = 0
319
+ rescue IOError, Errno::EPIPE => error
320
+ @restart_attempts = (@restart_attempts || 0) + 1
321
+ raise if @restart_attempts > MAX_RESTART_ATTEMPTS
322
+
323
+ restart!
324
+ retry
316
325
  end
317
326
 
318
327
  private
319
328
 
329
+ def restart!
330
+ @pipe.close rescue nil
331
+ begin
332
+ Process.kill(:TERM, @pid)
333
+ Process.waitpid2(@pid)
334
+ rescue Errno::ESRCH, Errno::ECHILD
335
+ nil
336
+ end
337
+ boot!
338
+ end
339
+
320
340
  def send_message(*message)
321
341
  payload = message.to_json
322
342
  @pipe.write([payload.bytesize].pack("L").b, payload)
@@ -145,6 +145,10 @@ module CI
145
145
  redis.hgetall(key('error-reports')).transform_keys { |entry| CI::Queue::QueueEntry.test_id(entry) }
146
146
  end
147
147
 
148
+ def failed_test_entries
149
+ redis.hkeys(key('error-reports'))
150
+ end
151
+
148
152
  def flaky_reports
149
153
  redis.smembers(key('flaky-reports')).map { |entry| CI::Queue::QueueEntry.test_id(entry) }
150
154
  end
@@ -177,6 +181,16 @@ module CI
177
181
  pipeline.hdel(key(stat_name), config.worker_id)
178
182
  end
179
183
  end
184
+ # Purge any error-report-deltas that reference this worker so that
185
+ # apply_error_report_delta_correction cannot double-subtract from
186
+ # the now-zeroed counters on a subsequent successful retry.
187
+ deltas = redis.hgetall(key('error-report-deltas'))
188
+ to_delete = deltas.filter_map do |entry, delta_json|
189
+ entry if JSON.parse(delta_json)['worker_id'].to_s == config.worker_id.to_s
190
+ rescue JSON::ParserError
191
+ nil
192
+ end
193
+ redis.hdel(key('error-report-deltas'), *to_delete) unless to_delete.empty?
180
194
  end
181
195
 
182
196
  private
@@ -28,9 +28,24 @@ module CI
28
28
  self
29
29
  end
30
30
 
31
+ # Queue a Redis SADD so that BuildRecord#record_success can include this
32
+ # in its multi-exec transaction. Without this, Static#acknowledge returns
33
+ # a Ruby value (not a Redis future), shifting the result indices and
34
+ # breaking the stats delta correction.
35
+ def acknowledge(entry, error: nil, pipeline: redis)
36
+ @progress += 1
37
+ return @progress unless pipeline
38
+ test_id = CI::Queue::QueueEntry.test_id(entry)
39
+ pipeline.sadd(key('processed'), test_id)
40
+ end
41
+
31
42
  private
32
43
 
33
44
  attr_reader :redis
45
+
46
+ def key(*args)
47
+ ['build', config.build_id, *args].join(':')
48
+ end
34
49
  end
35
50
  end
36
51
  end
@@ -158,6 +158,15 @@ module CI
158
158
  log.select! { |entry| failures.include?(CI::Queue::QueueEntry.test_id(entry)) }
159
159
  log.uniq! { |entry| CI::Queue::QueueEntry.test_id(entry) }
160
160
  log.reverse!
161
+
162
+ if log.empty?
163
+ # Per-worker log has no matching failures — this worker didn't run
164
+ # the failing tests (e.g. Buildkite rebuild with new worker IDs,
165
+ # or a different parallel slot). Fall back to ALL unresolved
166
+ # failures from error-reports so any worker can retry them.
167
+ log = redis.hkeys(key('error-reports'))
168
+ end
169
+
161
170
  Retry.new(log, config, redis: redis)
162
171
  end
163
172
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CI
4
4
  module Queue
5
- VERSION = '0.87.0'
5
+ VERSION = '0.89.0'
6
6
  DEV_SCRIPTS_ROOT = ::File.expand_path('../../../../../redis', __FILE__)
7
7
  RELEASE_SCRIPTS_ROOT = ::File.expand_path('../redis', __FILE__)
8
8
  end
@@ -330,7 +330,16 @@ module Minitest
330
330
  end
331
331
 
332
332
  def queue_entry
333
- @queue_entry ||= CI::Queue::QueueEntry.format(id, nil)
333
+ @queue_entry ||= begin
334
+ unless runnable.is_a?(Module)
335
+ raise ArgumentError, "runnable must be a Module (got #{runnable.class}). " \
336
+ "Do not create SingleExample with string class names."
337
+ end
338
+ file_path = runnable.instance_method(method_name).source_location&.first
339
+ raise ArgumentError, "Cannot resolve source file for #{id} — " \
340
+ "ensure the test method is defined in a Ruby source file" if file_path.nil?
341
+ CI::Queue::QueueEntry.format(id, file_path)
342
+ end
334
343
  end
335
344
 
336
345
  def <=>(other)
@@ -18,13 +18,13 @@ module RSpec
18
18
 
19
19
  def example_passed(notification)
20
20
  example = notification.example
21
- entry = CI::Queue::QueueEntry.format(example.id, nil)
21
+ entry = CI::Queue::QueueEntry.format(example.id, example.file_path)
22
22
  build.record_success(entry)
23
23
  end
24
24
 
25
25
  def example_failed(notification)
26
26
  example = notification.example
27
- entry = CI::Queue::QueueEntry.format(example.id, nil)
27
+ entry = CI::Queue::QueueEntry.format(example.id, example.file_path)
28
28
  build.record_error(entry, dump(notification))
29
29
  end
30
30
 
data/lib/rspec/queue.rb CHANGED
@@ -254,7 +254,10 @@ module RSpec
254
254
  end
255
255
 
256
256
  def queue_entry
257
- @queue_entry ||= CI::Queue::QueueEntry.format(id, nil)
257
+ @queue_entry ||= begin
258
+ file_path = example.metadata[:absolute_file_path] || example.file_path
259
+ CI::Queue::QueueEntry.format(id, file_path)
260
+ end
258
261
  end
259
262
 
260
263
  def <=>(other)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ci-queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.87.0
4
+ version: 0.89.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
@@ -121,6 +121,20 @@ dependencies:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
123
  version: '1.1'
124
+ - !ruby/object:Gem::Dependency
125
+ name: mocha
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
124
138
  - !ruby/object:Gem::Dependency
125
139
  name: rexml
126
140
  requirement: !ruby/object:Gem::Requirement