ci-queue 0.83.0 → 0.84.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: '08152aacde705d2472151b351e690db68a0c067c8930bf9574e1835825ee225b'
4
- data.tar.gz: 37fc5a7e9174d9188ff39568a0e4a5bf4f1ae0e3bce0f08e4d9d66e2a5a5264b
3
+ metadata.gz: c47a6b5450a21d7f4fb79a2b9a862ee53d6602d49e61a1e60d0fdaba92e9d0fd
4
+ data.tar.gz: 217ae043f06406663beff99e415dcf778a1a6cacd47ba6148d9e50504927dcf3
5
5
  SHA512:
6
- metadata.gz: 0ca31505b3ca58115b632d1a23e7884e2bbc25ae2360f4228b08192705200303ddecd03220ff86ebb6c6c82b7e580f83f7d86e5f79d10d59cd6949c4b6947d42
7
- data.tar.gz: f47440362656cc4ddf8ad52a9c49c9289b70f0a64312eb2c0c3f82e6bc9a980e6414a2c03ccb6613508975bf7b64e6b6232d1083fbbfc4c159b6eac0cb522e51
6
+ metadata.gz: ca7a1134775424386068df3e1b3c80738f90bbf5a353ed254715cd12194d6dfa39e1313c0fe4ea3e851f84a459ed0c61bbc52045164764fd4941b833ee6d71eb
7
+ data.tar.gz: 0ddca915e68afcfe1f6a99b41d34d9b46aa731499922ae1556de7db026b5e30dd48de08644df603c99426c430bebb782530825e626cc631f45dc94f0df2db200
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.3.0
1
+ 4.0
data/Gemfile.lock CHANGED
@@ -1,95 +1,105 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ci-queue (0.83.0)
4
+ ci-queue (0.84.0)
5
5
  logger
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activesupport (7.1.3.2)
10
+ activesupport (8.1.3)
11
11
  base64
12
12
  bigdecimal
13
- concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ concurrent-ruby (~> 1.0, >= 1.3.1)
14
14
  connection_pool (>= 2.2.5)
15
15
  drb
16
16
  i18n (>= 1.6, < 2)
17
+ json
18
+ logger (>= 1.4.2)
17
19
  minitest (>= 5.1)
18
- mutex_m
19
- tzinfo (~> 2.0)
20
+ securerandom (>= 0.3)
21
+ tzinfo (~> 2.0, >= 2.0.5)
22
+ uri (>= 0.13.1)
20
23
  ansi (1.5.0)
21
- ast (2.4.2)
22
- base64 (0.2.0)
23
- bigdecimal (3.1.7)
24
- builder (3.2.4)
25
- concurrent-ruby (1.2.3)
26
- connection_pool (2.4.1)
27
- diff-lcs (1.5.1)
28
- docile (1.4.0)
29
- drb (2.2.1)
30
- i18n (1.14.4)
24
+ ast (2.4.3)
25
+ base64 (0.3.0)
26
+ benchmark (0.5.0)
27
+ bigdecimal (4.0.1)
28
+ builder (3.3.0)
29
+ concurrent-ruby (1.3.6)
30
+ connection_pool (3.0.2)
31
+ diff-lcs (1.6.2)
32
+ docile (1.4.1)
33
+ drb (2.2.3)
34
+ i18n (1.14.8)
31
35
  concurrent-ruby (~> 1.0)
32
- json (2.7.1)
33
- language_server-protocol (3.17.0.3)
34
- logger (1.6.1)
35
- minitest (5.22.3)
36
- minitest-reporters (1.6.1)
36
+ json (2.19.3)
37
+ language_server-protocol (3.17.0.5)
38
+ lint_roller (1.1.0)
39
+ logger (1.7.0)
40
+ minitest (5.27.0)
41
+ minitest-reporters (1.7.1)
37
42
  ansi
38
43
  builder
39
44
  minitest (>= 5.0)
40
45
  ruby-progressbar
41
- msgpack (1.7.2)
42
- mutex_m (0.2.0)
43
- parallel (1.24.0)
44
- parser (3.3.0.5)
46
+ msgpack (1.8.0)
47
+ parallel (1.27.0)
48
+ parser (3.3.10.2)
45
49
  ast (~> 2.4.1)
46
50
  racc
47
- racc (1.7.3)
51
+ prism (1.9.0)
52
+ racc (1.8.1)
48
53
  rainbow (3.1.1)
49
- rake (13.1.0)
50
- redis (5.1.0)
51
- redis-client (>= 0.17.0)
52
- redis-client (0.21.1)
54
+ rake (13.3.1)
55
+ redis (5.4.1)
56
+ redis-client (>= 0.22.0)
57
+ redis-client (0.28.0)
53
58
  connection_pool
54
- regexp_parser (2.9.0)
55
- rexml (3.3.8)
56
- rspec (3.13.0)
59
+ regexp_parser (2.11.3)
60
+ rexml (3.4.4)
61
+ rspec (3.13.2)
57
62
  rspec-core (~> 3.13.0)
58
63
  rspec-expectations (~> 3.13.0)
59
64
  rspec-mocks (~> 3.13.0)
60
- rspec-core (3.13.0)
65
+ rspec-core (3.13.6)
61
66
  rspec-support (~> 3.13.0)
62
- rspec-expectations (3.13.0)
67
+ rspec-expectations (3.13.5)
63
68
  diff-lcs (>= 1.2.0, < 2.0)
64
69
  rspec-support (~> 3.13.0)
65
- rspec-mocks (3.13.0)
70
+ rspec-mocks (3.13.8)
66
71
  diff-lcs (>= 1.2.0, < 2.0)
67
72
  rspec-support (~> 3.13.0)
68
- rspec-support (3.13.1)
69
- rubocop (1.62.1)
73
+ rspec-support (3.13.7)
74
+ rubocop (1.86.0)
70
75
  json (~> 2.3)
71
- language_server-protocol (>= 3.17.0)
76
+ language_server-protocol (~> 3.17.0.2)
77
+ lint_roller (~> 1.1.0)
72
78
  parallel (~> 1.10)
73
79
  parser (>= 3.3.0.2)
74
80
  rainbow (>= 2.2.2, < 4.0)
75
- regexp_parser (>= 1.8, < 3.0)
76
- rexml (>= 3.2.5, < 4.0)
77
- rubocop-ast (>= 1.31.1, < 2.0)
81
+ regexp_parser (>= 2.9.3, < 3.0)
82
+ rubocop-ast (>= 1.49.0, < 2.0)
78
83
  ruby-progressbar (~> 1.7)
79
- unicode-display_width (>= 2.4.0, < 3.0)
80
- rubocop-ast (1.31.2)
81
- parser (>= 3.3.0.4)
84
+ unicode-display_width (>= 2.4.0, < 4.0)
85
+ rubocop-ast (1.49.1)
86
+ parser (>= 3.3.7.2)
87
+ prism (~> 1.7)
82
88
  ruby-progressbar (1.13.0)
89
+ securerandom (0.4.1)
83
90
  simplecov (0.22.0)
84
91
  docile (~> 1.1)
85
92
  simplecov-html (~> 0.11)
86
93
  simplecov_json_formatter (~> 0.1)
87
- simplecov-html (0.12.3)
94
+ simplecov-html (0.13.2)
88
95
  simplecov_json_formatter (0.1.4)
89
- snappy (0.4.0)
96
+ snappy (0.5.1)
90
97
  tzinfo (2.0.6)
91
98
  concurrent-ruby (~> 1.0)
92
- unicode-display_width (2.5.0)
99
+ unicode-display_width (3.2.0)
100
+ unicode-emoji (~> 4.1)
101
+ unicode-emoji (4.2.0)
102
+ uri (1.1.1)
93
103
 
94
104
  PLATFORMS
95
105
  arm64-darwin-23
@@ -97,6 +107,7 @@ PLATFORMS
97
107
 
98
108
  DEPENDENCIES
99
109
  activesupport
110
+ benchmark
100
111
  bundler
101
112
  ci-queue!
102
113
  minitest (~> 5.11)
@@ -104,6 +115,7 @@ DEPENDENCIES
104
115
  msgpack
105
116
  rake
106
117
  redis
118
+ rexml
107
119
  rspec (~> 3.10)
108
120
  rubocop
109
121
  simplecov (~> 0.12)
data/ci-queue.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.homepage = 'https://github.com/Shopify/ci-queue'
20
20
  spec.license = 'MIT'
21
21
 
22
- spec.required_ruby_version = '>= 2.7'
22
+ spec.required_ruby_version = '>= 3.1'
23
23
 
24
24
  spec.files = lua_scripts + `git ls-files -z`.split("\x0").reject do |f|
25
25
  f.match(%r{^(test|spec|features)/})
@@ -43,5 +43,7 @@ Gem::Specification.new do |spec|
43
43
 
44
44
  spec.add_development_dependency 'snappy'
45
45
  spec.add_development_dependency 'msgpack'
46
+ spec.add_development_dependency 'benchmark'
47
+ spec.add_development_dependency 'rexml'
46
48
  spec.add_development_dependency 'rubocop'
47
49
  end
@@ -18,17 +18,17 @@ module CI
18
18
  @queue.exhausted?
19
19
  end
20
20
 
21
- def record_error(id, payload, stat_delta: nil)
22
- error_reports[id] = payload
21
+ def record_error(entry, payload, stat_delta: nil)
22
+ error_reports[entry] = payload
23
23
  true
24
24
  end
25
25
 
26
- def record_success(id, skip_flaky_record: false, acknowledge: true)
27
- error_reports.delete(id)
26
+ def record_success(entry, skip_flaky_record: false, acknowledge: true)
27
+ error_reports.delete(entry)
28
28
  true
29
29
  end
30
30
 
31
- def record_requeue(id)
31
+ def record_requeue(entry)
32
32
  true
33
33
  end
34
34
 
@@ -6,26 +6,18 @@ require 'json'
6
6
  module CI
7
7
  module Queue
8
8
  module QueueEntry
9
- DELIMITER = "\t"
10
9
  LOAD_ERROR_PREFIX = '__ciq_load_error__:'.freeze
11
10
 
12
11
  def self.test_id(entry)
13
- pos = entry.index(DELIMITER)
14
- pos ? entry[0, pos] : entry
12
+ JSON.parse(entry, symbolize_names: true)[:test_id]
15
13
  end
16
14
 
17
15
  def self.parse(entry)
18
- return { test_id: entry, file_path: nil } unless entry.include?(DELIMITER)
19
-
20
- test_id, file_path = entry.split(DELIMITER, 2)
21
- file_path = nil if file_path == ""
22
- { test_id: test_id, file_path: file_path }
16
+ JSON.parse(entry, symbolize_names: true)
23
17
  end
24
18
 
25
19
  def self.format(test_id, file_path)
26
- return test_id if file_path.nil? || file_path == ""
27
-
28
- "#{test_id}#{DELIMITER}#{file_path}"
20
+ JSON.dump({ test_id: test_id, file_path: file_path })
29
21
  end
30
22
 
31
23
  def self.load_error_payload?(file_path)
@@ -6,16 +6,15 @@ local error_reports_key = KEYS[4]
6
6
  local requeued_by_key = KEYS[5]
7
7
 
8
8
  local entry = ARGV[1]
9
- local test_id = ARGV[2]
10
- local error = ARGV[3]
11
- local ttl = ARGV[4]
9
+ local error = ARGV[2]
10
+ local ttl = ARGV[3]
12
11
  redis.call('zrem', zset_key, entry)
13
12
  redis.call('hdel', owners_key, entry) -- Doesn't matter if it was reclaimed by another workers
14
13
  redis.call('hdel', requeued_by_key, entry)
15
- local acknowledged = redis.call('sadd', processed_key, test_id) == 1
14
+ local acknowledged = redis.call('sadd', processed_key, entry) == 1
16
15
 
17
16
  if acknowledged and error ~= "" then
18
- redis.call('hset', error_reports_key, test_id, error)
17
+ redis.call('hset', error_reports_key, entry, error)
19
18
  redis.call('expire', error_reports_key, ttl)
20
19
  end
21
20
 
@@ -264,13 +264,12 @@ module CI
264
264
  end
265
265
 
266
266
  class HeartbeatProcess
267
- def initialize(redis_url, zset_key, processed_key, owners_key, worker_queue_key, entry_delimiter:)
267
+ def initialize(redis_url, zset_key, processed_key, owners_key, worker_queue_key)
268
268
  @redis_url = redis_url
269
269
  @zset_key = zset_key
270
270
  @processed_key = processed_key
271
271
  @owners_key = owners_key
272
272
  @worker_queue_key = worker_queue_key
273
- @entry_delimiter = entry_delimiter
274
273
  end
275
274
 
276
275
  def boot!
@@ -285,7 +284,6 @@ module CI
285
284
  @processed_key,
286
285
  @owners_key,
287
286
  @worker_queue_key,
288
- @entry_delimiter,
289
287
  in: child_read,
290
288
  out: child_write,
291
289
  )
@@ -360,7 +358,6 @@ module CI
360
358
  key('processed'),
361
359
  key('owners'),
362
360
  key('worker', worker_id, 'queue'),
363
- entry_delimiter: CI::Queue::QueueEntry::DELIMITER,
364
361
  )
365
362
  end
366
363
 
@@ -33,14 +33,14 @@ module CI
33
33
  end
34
34
 
35
35
  def failed_tests
36
- redis.hkeys(key('error-reports'))
36
+ redis.hkeys(key('error-reports')).map { |entry| CI::Queue::QueueEntry.test_id(entry) }
37
37
  end
38
38
 
39
39
  TOTAL_KEY = "___total___"
40
40
  def requeued_tests
41
41
  requeues = redis.hgetall(key('requeues-count'))
42
42
  requeues.delete(TOTAL_KEY)
43
- requeues
43
+ requeues.transform_keys { |entry| CI::Queue::QueueEntry.test_id(entry) }
44
44
  end
45
45
 
46
46
  def pop_warnings
@@ -56,39 +56,39 @@ module CI
56
56
  redis.rpush(key('warnings'), Marshal.dump([type, attributes]))
57
57
  end
58
58
 
59
- def record_error(id, payload, stat_delta: nil)
59
+ def record_error(entry, payload, stat_delta: nil)
60
60
  # Run acknowledge first so we know whether we're the first to ack
61
- acknowledged = @queue.acknowledge(id, error: payload)
61
+ acknowledged = @queue.acknowledge(entry, error: payload)
62
62
 
63
63
  if acknowledged
64
64
  # We were the first to ack; another worker already ack'd would get falsy from SADD
65
65
  @queue.increment_test_failed
66
66
  # Only the acknowledging worker's stats include this failure (others skip increment when ack=false).
67
67
  # Store so we can subtract it if another worker records success later.
68
- store_error_report_delta(id, stat_delta) if stat_delta && stat_delta.any?
68
+ store_error_report_delta(entry, stat_delta) if stat_delta && stat_delta.any?
69
69
  end
70
70
  # Return so caller can roll back local counter when not acknowledged
71
71
  !!acknowledged
72
72
  end
73
73
 
74
- def record_success(id, skip_flaky_record: false)
74
+ def record_success(entry, skip_flaky_record: false)
75
75
  acknowledged, error_reports_deleted_count, requeued_count, delta_json = redis.multi do |transaction|
76
- @queue.acknowledge(id, pipeline: transaction)
77
- transaction.hdel(key('error-reports'), id)
78
- transaction.hget(key('requeues-count'), id)
79
- transaction.hget(key('error-report-deltas'), id)
76
+ @queue.acknowledge(entry, pipeline: transaction)
77
+ transaction.hdel(key('error-reports'), entry)
78
+ transaction.hget(key('requeues-count'), entry)
79
+ transaction.hget(key('error-report-deltas'), entry)
80
80
  end
81
81
  # When we're replacing a failure, subtract the (single) acknowledging worker's stat contribution
82
82
  if error_reports_deleted_count.to_i > 0 && delta_json
83
83
  apply_error_report_delta_correction(delta_json)
84
- redis.hdel(key('error-report-deltas'), id)
84
+ redis.hdel(key('error-report-deltas'), entry)
85
85
  end
86
- record_flaky(id) if !skip_flaky_record && (error_reports_deleted_count.to_i > 0 || requeued_count.to_i > 0)
86
+ record_flaky(entry) if !skip_flaky_record && (error_reports_deleted_count.to_i > 0 || requeued_count.to_i > 0)
87
87
  # Count this run when we ack'd or when we replaced a failure (so stats delta is applied)
88
88
  !!(acknowledged || error_reports_deleted_count.to_i > 0)
89
89
  end
90
90
 
91
- def record_requeue(id)
91
+ def record_requeue(entry)
92
92
  true
93
93
  end
94
94
 
@@ -142,11 +142,11 @@ module CI
142
142
  end
143
143
 
144
144
  def error_reports
145
- redis.hgetall(key('error-reports'))
145
+ redis.hgetall(key('error-reports')).transform_keys { |entry| CI::Queue::QueueEntry.test_id(entry) }
146
146
  end
147
147
 
148
148
  def flaky_reports
149
- redis.smembers(key('flaky-reports'))
149
+ redis.smembers(key('flaky-reports')).map { |entry| CI::Queue::QueueEntry.test_id(entry) }
150
150
  end
151
151
 
152
152
  def record_worker_profile(profile)
@@ -187,10 +187,10 @@ module CI
187
187
  ['build', config.build_id, *args].join(':')
188
188
  end
189
189
 
190
- def store_error_report_delta(test_id, stat_delta)
190
+ def store_error_report_delta(entry, stat_delta)
191
191
  # Only the acknowledging worker's stats include this test; store their delta for correction on success
192
192
  payload = { 'worker_id' => config.worker_id.to_s }.merge(stat_delta)
193
- redis.hset(key('error-report-deltas'), test_id, JSON.generate(payload))
193
+ redis.hset(key('error-report-deltas'), entry, JSON.generate(payload))
194
194
  redis.expire(key('error-report-deltas'), config.redis_ttl)
195
195
  end
196
196
 
@@ -1,6 +1,4 @@
1
1
  -- AUTOGENERATED FILE DO NOT EDIT DIRECTLY
2
- -- @include _entry_helpers
3
-
4
2
  local zset_key = KEYS[1]
5
3
  local processed_key = KEYS[2]
6
4
  local owners_key = KEYS[3]
@@ -8,12 +6,9 @@ local worker_queue_key = KEYS[4]
8
6
 
9
7
  local current_time = ARGV[1]
10
8
  local entry = ARGV[2]
11
- local entry_delimiter = ARGV[3]
12
-
13
- local test_id = test_id_from_entry(entry, entry_delimiter)
14
9
 
15
10
  -- already processed, we do not need to bump the timestamp
16
- if redis.call('sismember', processed_key, test_id) == 1 then
11
+ if redis.call('sismember', processed_key, entry) == 1 then
17
12
  return false
18
13
  end
19
14
 
@@ -13,12 +13,11 @@ module CI
13
13
  DEV_SCRIPTS_ROOT = ::File.expand_path('../../../../../../redis', __FILE__)
14
14
  RELEASE_SCRIPTS_ROOT = ::File.expand_path('../../redis', __FILE__)
15
15
 
16
- def initialize(pipe, logger, redis_url, zset_key, processed_key, owners_key, worker_queue_key, entry_delimiter)
16
+ def initialize(pipe, logger, redis_url, zset_key, processed_key, owners_key, worker_queue_key)
17
17
  @zset_key = zset_key
18
18
  @processed_key = processed_key
19
19
  @owners_key = owners_key
20
20
  @worker_queue_key = worker_queue_key
21
- @entry_delimiter = entry_delimiter
22
21
  @logger = logger
23
22
  @redis = ::Redis.new(url: redis_url, reconnect_attempts: [0, 0, 0.1, 0.5, 1, 3, 5])
24
23
  @shutdown = false
@@ -41,7 +40,7 @@ module CI
41
40
  eval_script(
42
41
  :heartbeat,
43
42
  keys: [@zset_key, @processed_key, @owners_key, @worker_queue_key],
44
- argv: [Time.now.to_f, id, @entry_delimiter]
43
+ argv: [Time.now.to_f, id]
45
44
  )
46
45
  rescue => error
47
46
  @logger.info(error)
@@ -155,10 +154,9 @@ zset_key = ARGV[1]
155
154
  processed_key = ARGV[2]
156
155
  owners_key = ARGV[3]
157
156
  worker_queue_key = ARGV[4]
158
- entry_delimiter = ARGV[5]
159
157
 
160
158
  logger.debug("Starting monitor: #{redis_url} #{zset_key} #{processed_key}")
161
- manager = CI::Queue::Redis::Monitor.new($stdin, logger, redis_url, zset_key, processed_key, owners_key, worker_queue_key, entry_delimiter)
159
+ manager = CI::Queue::Redis::Monitor.new($stdin, logger, redis_url, zset_key, processed_key, owners_key, worker_queue_key)
162
160
 
163
161
  # Notify the parent we're ready
164
162
  $stdout.puts(".")
@@ -11,15 +11,14 @@ local requeued_by_key = KEYS[8]
11
11
  local max_requeues = tonumber(ARGV[1])
12
12
  local global_max_requeues = tonumber(ARGV[2])
13
13
  local entry = ARGV[3]
14
- local test_id = ARGV[4]
15
- local offset = ARGV[5]
16
- local ttl = tonumber(ARGV[6])
14
+ local offset = ARGV[4]
15
+ local ttl = tonumber(ARGV[5])
17
16
 
18
17
  if redis.call('hget', owners_key, entry) == worker_queue_key then
19
18
  redis.call('hdel', owners_key, entry)
20
19
  end
21
20
 
22
- if redis.call('sismember', processed_key, test_id) == 1 then
21
+ if redis.call('sismember', processed_key, entry) == 1 then
23
22
  return false
24
23
  end
25
24
 
@@ -28,15 +27,15 @@ if global_requeues and global_requeues >= tonumber(global_max_requeues) then
28
27
  return false
29
28
  end
30
29
 
31
- local requeues = tonumber(redis.call('hget', requeues_count_key, test_id))
30
+ local requeues = tonumber(redis.call('hget', requeues_count_key, entry))
32
31
  if requeues and requeues >= max_requeues then
33
32
  return false
34
33
  end
35
34
 
36
35
  redis.call('hincrby', requeues_count_key, '___total___', 1)
37
- redis.call('hincrby', requeues_count_key, test_id, 1)
36
+ redis.call('hincrby', requeues_count_key, entry, 1)
38
37
 
39
- redis.call('hdel', error_reports_key, test_id)
38
+ redis.call('hdel', error_reports_key, entry)
40
39
 
41
40
  local pivot = redis.call('lrange', queue_key, -1 - offset, 0 - offset)[1]
42
41
  if pivot then
@@ -1,6 +1,4 @@
1
1
  -- AUTOGENERATED FILE DO NOT EDIT DIRECTLY
2
- -- @include _entry_helpers
3
-
4
2
  local zset_key = KEYS[1]
5
3
  local processed_key = KEYS[2]
6
4
  local worker_queue_key = KEYS[3]
@@ -8,12 +6,10 @@ local owners_key = KEYS[4]
8
6
 
9
7
  local current_time = ARGV[1]
10
8
  local timeout = ARGV[2]
11
- local entry_delimiter = ARGV[3]
12
9
 
13
10
  local lost_tests = redis.call('zrangebyscore', zset_key, 0, current_time - timeout)
14
11
  for _, test in ipairs(lost_tests) do
15
- local test_id = test_id_from_entry(test, entry_delimiter)
16
- if redis.call('sismember', processed_key, test_id) == 0 then
12
+ if redis.call('sismember', processed_key, test) == 0 then
17
13
  redis.call('zadd', zset_key, current_time, test)
18
14
  redis.call('lpush', worker_queue_key, test)
19
15
  redis.call('hset', owners_key, test, worker_queue_key) -- Take ownership
@@ -147,8 +147,8 @@ module CI
147
147
  def retry_queue
148
148
  failures = build.failed_tests.to_set
149
149
  log = redis.lrange(key('worker', worker_id, 'queue'), 0, -1)
150
- log = log.map { |entry| queue_entry_test_id(entry) }
151
- log.select! { |id| failures.include?(id) }
150
+ log = log.map { |entry| CI::Queue::QueueEntry.test_id(entry) }
151
+ log.select! { |test_id| failures.include?(test_id) }
152
152
  log.uniq!
153
153
  log.reverse!
154
154
  Retry.new(log, config, redis: redis)
@@ -176,23 +176,23 @@ module CI
176
176
  build.report_worker_error(error)
177
177
  end
178
178
 
179
- def acknowledge(test_key, error: nil, pipeline: redis)
180
- test_id = normalize_test_id(test_key)
179
+ def acknowledge(entry, error: nil, pipeline: redis)
180
+ test_id = CI::Queue::QueueEntry.test_id(entry)
181
181
  assert_reserved!(test_id)
182
- entry = reserved_entries.fetch(test_id, queue_entry_for(test_key))
182
+ entry = reserved_entries.fetch(test_id, entry)
183
183
  unreserve_entry(test_id)
184
184
  eval_script(
185
185
  :acknowledge,
186
186
  keys: [key('running'), key('processed'), key('owners'), key('error-reports'), key('requeued-by')],
187
- argv: [entry, test_id, error.to_s, config.redis_ttl],
187
+ argv: [entry, error.to_s, config.redis_ttl],
188
188
  pipeline: pipeline,
189
189
  ) == 1
190
190
  end
191
191
 
192
- def requeue(test, offset: Redis.requeue_offset)
193
- test_id = normalize_test_id(test)
192
+ def requeue(entry, offset: Redis.requeue_offset)
193
+ test_id = CI::Queue::QueueEntry.test_id(entry)
194
194
  assert_reserved!(test_id)
195
- entry = reserved_entries.fetch(test_id, queue_entry_for(test))
195
+ entry = reserved_entries.fetch(test_id, entry)
196
196
  unreserve_entry(test_id)
197
197
  global_max_requeues = config.global_max_requeues(total)
198
198
 
@@ -208,7 +208,7 @@ module CI
208
208
  key('error-reports'),
209
209
  key('requeued-by'),
210
210
  ],
211
- argv: [config.max_requeues, global_max_requeues, entry, test_id, offset, config.redis_ttl],
211
+ argv: [config.max_requeues, global_max_requeues, entry, offset, config.redis_ttl],
212
212
  ) == 1
213
213
 
214
214
  unless requeued
@@ -255,7 +255,7 @@ module CI
255
255
  end
256
256
 
257
257
  def reserve_entry(entry)
258
- test_id = queue_entry_test_id(entry)
258
+ test_id = CI::Queue::QueueEntry.test_id(entry)
259
259
  reserved_tests << test_id
260
260
  reserved_entries[test_id] = entry
261
261
  reserved_entry_ids[entry] = test_id
@@ -267,19 +267,6 @@ module CI
267
267
  reserved_entry_ids.delete(entry) if entry
268
268
  end
269
269
 
270
- def normalize_test_id(test_key)
271
- key = test_key.respond_to?(:id) ? test_key.id : test_key
272
- if key.is_a?(String)
273
- cached = reserved_entry_ids[key]
274
- return cached if cached
275
- end
276
- queue_entry_test_id(key)
277
- end
278
-
279
- def queue_entry_test_id(entry)
280
- CI::Queue::QueueEntry.test_id(entry)
281
- end
282
-
283
270
  def queue_entry_for(test)
284
271
  return test.queue_entry if test.respond_to?(:queue_entry)
285
272
  return test.id if test.respond_to?(:id)
@@ -288,7 +275,7 @@ module CI
288
275
  end
289
276
 
290
277
  def resolve_entry(entry)
291
- test_id = reserved_entry_ids[entry] || queue_entry_test_id(entry)
278
+ test_id = reserved_entry_ids[entry] || CI::Queue::QueueEntry.test_id(entry)
292
279
  if populated?
293
280
  return index[test_id] if index.key?(test_id)
294
281
  end
@@ -387,15 +374,18 @@ module CI
387
374
  :reserve_lost,
388
375
  keys: [
389
376
  key('running'),
390
- key('completed'),
377
+ key('processed'),
391
378
  key('worker', worker_id, 'queue'),
392
379
  key('owners'),
393
380
  ],
394
- argv: [CI::Queue.time_now.to_f, timeout, CI::Queue::QueueEntry::DELIMITER],
381
+ argv: [CI::Queue.time_now.to_f, timeout],
395
382
  )
396
383
 
397
384
  if lost_test
398
- build.record_warning(Warnings::RESERVED_LOST_TEST, test: lost_test, timeout: config.timeout)
385
+ build.record_warning(Warnings::RESERVED_LOST_TEST, test: CI::Queue::QueueEntry.test_id(lost_test), timeout: config.timeout)
386
+ if CI::Queue.debug?
387
+ $stderr.puts "[ci-queue][reserve_lost] worker=#{worker_id} test_id=#{CI::Queue::QueueEntry.test_id(lost_test)}"
388
+ end
399
389
  end
400
390
 
401
391
  lost_test
@@ -125,12 +125,12 @@ module CI
125
125
  test_failed >= config.max_test_failed
126
126
  end
127
127
 
128
- def requeue(test)
129
- test_key = test.id
130
- return false unless should_requeue?(test_key)
128
+ def requeue(entry)
129
+ test_id = CI::Queue::QueueEntry.test_id(entry)
130
+ return false unless should_requeue?(test_id)
131
131
 
132
- requeues[test_key] += 1
133
- @queue.unshift(test_key)
132
+ requeues[test_id] += 1
133
+ @queue.unshift(test_id)
134
134
  true
135
135
  end
136
136
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CI
4
4
  module Queue
5
- VERSION = '0.83.0'
5
+ VERSION = '0.84.0'
6
6
  DEV_SCRIPTS_ROOT = ::File.expand_path('../../../../../redis', __FILE__)
7
7
  RELEASE_SCRIPTS_ROOT = ::File.expand_path('../redis', __FILE__)
8
8
  end
@@ -40,15 +40,15 @@ module Minitest
40
40
  self.total_time = Minitest.clock_time - start_time
41
41
 
42
42
  # Determine what type of result this is and record it
43
- test_id = "#{test.klass}##{test.name}"
43
+ entry = test.queue_entry
44
44
  delta = delta_for(test)
45
45
 
46
46
  acknowledged = if (test.failure || test.error?) && !test.skipped?
47
- build.record_error(test_id, dump(test), stat_delta: delta)
47
+ build.record_error(entry, dump(test), stat_delta: delta)
48
48
  elsif test.requeued?
49
- build.record_requeue(test_id)
49
+ build.record_requeue(entry)
50
50
  else
51
- build.record_success(test_id, skip_flaky_record: test.skipped?)
51
+ build.record_success(entry, skip_flaky_record: test.skipped?)
52
52
  end
53
53
 
54
54
  if acknowledged
@@ -138,7 +138,7 @@ module Minitest
138
138
  @error_location ||= begin
139
139
  last_before_assertion = ''
140
140
  backtrace_for(exception).reverse_each do |s|
141
- break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/
141
+ break if s =~ /in [`'](?:[\w:]*[#.])?(assert|refute|flunk|pass|fail|raise|must|wont)/
142
142
 
143
143
  last_before_assertion = s
144
144
  end
@@ -195,15 +195,18 @@ module Minitest
195
195
  # When we do a bisect, we don't care about the result other than the test we're running the bisect on
196
196
  result.mark_as_flaked!
197
197
  failed = false
198
+ end
199
+
200
+ if failed && CI::Queue.requeueable?(result) && queue.requeue(example.queue_entry)
201
+ result.requeue!
202
+ if CI::Queue.debug?
203
+ $stderr.puts "[ci-queue][requeue] test_id=#{example.id} error_class=#{result.failures.first&.class} error=#{result.failures.first&.message&.lines&.first&.chomp}"
204
+ end
198
205
  elsif failed
199
206
  queue.report_failure!
200
207
  else
201
208
  queue.report_success!
202
209
  end
203
-
204
- if failed && CI::Queue.requeueable?(result) && queue.requeue(example)
205
- result.requeue!
206
- end
207
210
  reporter.record(result)
208
211
  end
209
212
 
@@ -327,7 +330,7 @@ module Minitest
327
330
  end
328
331
 
329
332
  def queue_entry
330
- id
333
+ @queue_entry ||= CI::Queue::QueueEntry.format(id, nil)
331
334
  end
332
335
 
333
336
  def <=>(other)
@@ -18,12 +18,14 @@ module RSpec
18
18
 
19
19
  def example_passed(notification)
20
20
  example = notification.example
21
- build.record_success(example.id)
21
+ entry = CI::Queue::QueueEntry.format(example.id, nil)
22
+ build.record_success(entry)
22
23
  end
23
24
 
24
25
  def example_failed(notification)
25
26
  example = notification.example
26
- build.record_error(example.id, dump(notification))
27
+ entry = CI::Queue::QueueEntry.format(example.id, nil)
28
+ build.record_error(entry, dump(notification))
27
29
  end
28
30
 
29
31
  private
data/lib/rspec/queue.rb CHANGED
@@ -253,6 +253,10 @@ module RSpec
253
253
  example.id
254
254
  end
255
255
 
256
+ def queue_entry
257
+ @queue_entry ||= CI::Queue::QueueEntry.format(id, nil)
258
+ end
259
+
256
260
  def <=>(other)
257
261
  id <=> other.id
258
262
  end
@@ -411,7 +415,7 @@ module RSpec
411
415
  end
412
416
 
413
417
  def requeue
414
- @queue.requeue(@example)
418
+ @queue.requeue(@example.queue_entry)
415
419
  end
416
420
 
417
421
  def cancel_run!
@@ -422,7 +426,7 @@ module RSpec
422
426
  end
423
427
 
424
428
  def acknowledge
425
- @queue.acknowledge(@example)
429
+ @queue.acknowledge(@example.queue_entry)
426
430
  end
427
431
  end
428
432
 
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.83.0
4
+ version: 0.84.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
@@ -149,6 +149,34 @@ dependencies:
149
149
  - - ">="
150
150
  - !ruby/object:Gem::Version
151
151
  version: '0'
152
+ - !ruby/object:Gem::Dependency
153
+ name: benchmark
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ - !ruby/object:Gem::Dependency
167
+ name: rexml
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ type: :development
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
152
180
  - !ruby/object:Gem::Dependency
153
181
  name: rubocop
154
182
  requirement: !ruby/object:Gem::Requirement
@@ -199,7 +227,6 @@ files:
199
227
  - lib/ci/queue/output_helpers.rb
200
228
  - lib/ci/queue/queue_entry.rb
201
229
  - lib/ci/queue/redis.rb
202
- - lib/ci/queue/redis/_entry_helpers.lua
203
230
  - lib/ci/queue/redis/acknowledge.lua
204
231
  - lib/ci/queue/redis/base.rb
205
232
  - lib/ci/queue/redis/build_record.rb
@@ -257,14 +284,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
257
284
  requirements:
258
285
  - - ">="
259
286
  - !ruby/object:Gem::Version
260
- version: '2.7'
287
+ version: '3.1'
261
288
  required_rubygems_version: !ruby/object:Gem::Requirement
262
289
  requirements:
263
290
  - - ">="
264
291
  - !ruby/object:Gem::Version
265
292
  version: '0'
266
293
  requirements: []
267
- rubygems_version: 4.0.6
294
+ rubygems_version: 4.0.8
268
295
  specification_version: 4
269
296
  summary: Distribute tests over many workers using a queue
270
297
  test_files: []
@@ -1,10 +0,0 @@
1
- -- AUTOGENERATED FILE DO NOT EDIT DIRECTLY
2
- local function test_id_from_entry(value, delimiter)
3
- if delimiter then
4
- local pos = string.find(value, delimiter, 1, true)
5
- if pos then
6
- return string.sub(value, 1, pos - 1)
7
- end
8
- end
9
- return value
10
- end