ci-queue 0.20.9 → 0.22.1

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: ce180525dfe099a326292c007d5654789406e1a4af4ed74455ea036891fd5e3b
4
- data.tar.gz: 570f697ebc2d320e3a556b0b7323d8a2cc9920e3263e07ebef8fa2fc3517500e
3
+ metadata.gz: 2847d2cc56c64030f51a65ef6b9262d04b647b15b6d6ef846cf76adb90715e44
4
+ data.tar.gz: bf4f65911c9f39c2e6049abc6006f853f8de1aa444dfe8d5c7237cd698d0a59f
5
5
  SHA512:
6
- metadata.gz: abe9a5431c9e61435f8f93ede21eae622c000b27d2359d24171e3e0bc10562d6b9c87f10138ccbd3d12e149aec09601ba2007510e1e629b1564df43a05675ce2
7
- data.tar.gz: d504aa4e467eba720a2aaa35bb448dbbf13d52502d6e16a491afcfbfa8413face59eefc5b260c5412889b3d7dc0e65c194569e676e6f656147a163e67f622394
6
+ metadata.gz: b7c62dd52f53069dd2aad2b51724334e7ea2e8fc2cdc8f19f022b6dfd926bb7323437c3f6d1c4ffe7071316c07cde6b924671be07a2e7b402e2d528becd16acf
7
+ data.tar.gz: 056b3a585d95b88479f79448dd204eef72e16635c0c2df5c619ad78be2caf3b97a6e52c15edf09f2a2dcccf80afa3ea0018cfe9361114f6d03914383ab080efb
data/ci-queue.gemspec CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
32
32
  spec.add_development_dependency 'bundler'
33
33
  spec.add_development_dependency 'rake'
34
34
  spec.add_development_dependency 'minitest', ENV.fetch('MINITEST_VERSION', '~> 5.11')
35
- spec.add_development_dependency 'rspec', '~> 3.7.0'
35
+ spec.add_development_dependency 'rspec', '~> 3.10'
36
36
  spec.add_development_dependency 'redis'
37
37
  spec.add_development_dependency 'simplecov', '~> 0.12'
38
38
  spec.add_development_dependency 'minitest-reporters', '~> 1.1'
@@ -31,6 +31,10 @@ module CI
31
31
  Static.new(first_half + [config.failing_test], config).populate(@all_tests)
32
32
  end
33
33
 
34
+ def release!
35
+ # noop
36
+ end
37
+
34
38
  def failed!
35
39
  @tests = first_half
36
40
  end
@@ -11,6 +11,10 @@ module CI
11
11
  false
12
12
  end
13
13
 
14
+ def release!
15
+ # noop
16
+ end
17
+
14
18
  def flaky?(test)
15
19
  @config.flaky?(test)
16
20
  end
@@ -1,8 +1,10 @@
1
1
  -- AUTOGENERATED FILE DO NOT EDIT DIRECTLY
2
2
  local zset_key = KEYS[1]
3
3
  local processed_key = KEYS[2]
4
+ local owners_key = KEYS[3]
4
5
 
5
6
  local test = ARGV[1]
6
7
 
7
8
  redis.call('zrem', zset_key, test)
9
+ redis.call('hdel', owners_key, test) -- Doesn't matter if it was reclaimed by another workers
8
10
  return redis.call('sadd', processed_key, test)
@@ -21,16 +21,16 @@ module CI
21
21
  end
22
22
 
23
23
  def size
24
- redis.multi do
25
- redis.llen(key('queue'))
26
- redis.zcard(key('running'))
24
+ redis.multi do |transaction|
25
+ transaction.llen(key('queue'))
26
+ transaction.zcard(key('running'))
27
27
  end.inject(:+)
28
28
  end
29
29
 
30
30
  def to_a
31
- redis.multi do
32
- redis.lrange(key('queue'), 0, -1)
33
- redis.zrange(key('running'), 0, -1)
31
+ redis.multi do |transaction|
32
+ transaction.lrange(key('queue'), 0, -1)
33
+ transaction.zrange(key('running'), 0, -1)
34
34
  end.flatten.reverse.map { |k| index.fetch(k) }
35
35
  end
36
36
 
@@ -22,9 +22,9 @@ module CI
22
22
  end
23
23
 
24
24
  def pop_warnings
25
- warnings = redis.multi do
26
- redis.lrange(key('warnings'), 0, -1)
27
- redis.del(key('warnings'))
25
+ warnings = redis.multi do |transaction|
26
+ transaction.lrange(key('warnings'), 0, -1)
27
+ transaction.del(key('warnings'))
28
28
  end.first
29
29
 
30
30
  warnings.map { |p| Marshal.load(p) }
@@ -35,21 +35,21 @@ module CI
35
35
  end
36
36
 
37
37
  def record_error(id, payload, stats: nil)
38
- redis.pipelined do
39
- redis.hset(
38
+ redis.pipelined do |pipeline|
39
+ pipeline.hset(
40
40
  key('error-reports'),
41
41
  id.dup.force_encoding(Encoding::BINARY),
42
42
  payload.dup.force_encoding(Encoding::BINARY),
43
43
  )
44
- record_stats(stats)
44
+ record_stats(stats, pipeline: pipeline)
45
45
  end
46
46
  nil
47
47
  end
48
48
 
49
49
  def record_success(id, stats: nil)
50
- redis.pipelined do
51
- redis.hdel(key('error-reports'), id.dup.force_encoding(Encoding::BINARY))
52
- record_stats(stats)
50
+ redis.pipelined do |pipeline|
51
+ pipeline.hdel(key('error-reports'), id.dup.force_encoding(Encoding::BINARY))
52
+ record_stats(stats, pipeline: pipeline)
53
53
  end
54
54
  nil
55
55
  end
@@ -65,8 +65,8 @@ module CI
65
65
  end
66
66
 
67
67
  def fetch_stats(stat_names)
68
- counts = redis.pipelined do
69
- stat_names.each { |c| redis.hvals(key(c)) }
68
+ counts = redis.pipelined do |pipeline|
69
+ stat_names.each { |c| pipeline.hvals(key(c)) }
70
70
  end
71
71
  sum_counts = counts.map do |values|
72
72
  values.map(&:to_f).inject(:+).to_f
@@ -75,9 +75,9 @@ module CI
75
75
  end
76
76
 
77
77
  def reset_stats(stat_names)
78
- redis.pipelined do
78
+ redis.pipelined do |pipeline|
79
79
  stat_names.each do |stat_name|
80
- redis.hdel(key(stat_name), config.worker_id)
80
+ pipeline.hdel(key(stat_name), config.worker_id)
81
81
  end
82
82
  end
83
83
  end
@@ -86,10 +86,10 @@ module CI
86
86
 
87
87
  attr_reader :config, :redis
88
88
 
89
- def record_stats(stats)
89
+ def record_stats(stats, pipeline: redis)
90
90
  return unless stats
91
91
  stats.each do |stat_name, stat_value|
92
- redis.hset(key(stat_name), config.worker_id, stat_value)
92
+ pipeline.hset(key(stat_name), config.worker_id, stat_value)
93
93
  end
94
94
  end
95
95
 
@@ -11,12 +11,12 @@ module CI
11
11
  end
12
12
 
13
13
  def record_error(payload, stats: nil)
14
- redis.pipelined do
15
- redis.lpush(
14
+ redis.pipelined do |pipeline|
15
+ pipeline.lpush(
16
16
  key('error-reports'),
17
17
  payload.force_encoding(Encoding::BINARY),
18
18
  )
19
- record_stats(stats)
19
+ record_stats(stats, pipeline: pipeline)
20
20
  end
21
21
  nil
22
22
  end
@@ -34,8 +34,8 @@ module CI
34
34
  end
35
35
 
36
36
  def fetch_stats(stat_names)
37
- counts = redis.pipelined do
38
- stat_names.each { |c| redis.hvals(key(c)) }
37
+ counts = redis.pipelined do |pipeline|
38
+ stat_names.each { |c| pipeline.hvals(key(c)) }
39
39
  end
40
40
  stat_names.zip(counts.map { |values| values.map(&:to_f).inject(:+).to_f }).to_h
41
41
  end
@@ -54,10 +54,10 @@ module CI
54
54
  ['build', config.build_id, *args].join(':')
55
55
  end
56
56
 
57
- def record_stats(stats)
57
+ def record_stats(stats, pipeline: redis)
58
58
  return unless stats
59
59
  stats.each do |stat_name, stat_value|
60
- redis.hset(key(stat_name), config.worker_id, stat_value)
60
+ pipeline.hset(key(stat_name), config.worker_id, stat_value)
61
61
  end
62
62
  end
63
63
  end
@@ -0,0 +1,16 @@
1
+ -- AUTOGENERATED FILE DO NOT EDIT DIRECTLY
2
+ local zset_key = KEYS[1]
3
+ local worker_queue_key = KEYS[2]
4
+ local owners_key = KEYS[3]
5
+
6
+ -- owned_tests = {"SomeTest", "worker:1", "SomeOtherTest", "worker:2", ...}
7
+ local owned_tests = redis.call('hgetall', owners_key)
8
+ for index, owner_or_test in ipairs(owned_tests) do
9
+ if owner_or_test == worker_queue_key then -- If we owned a test
10
+ local test = owned_tests[index - 1]
11
+ redis.call('zadd', zset_key, "0", test) -- We expire the lease immediately
12
+ return nil
13
+ end
14
+ end
15
+
16
+ return nil
@@ -3,12 +3,18 @@ local processed_key = KEYS[1]
3
3
  local requeues_count_key = KEYS[2]
4
4
  local queue_key = KEYS[3]
5
5
  local zset_key = KEYS[4]
6
+ local worker_queue_key = KEYS[5]
7
+ local owners_key = KEYS[6]
6
8
 
7
9
  local max_requeues = tonumber(ARGV[1])
8
10
  local global_max_requeues = tonumber(ARGV[2])
9
11
  local test = ARGV[3]
10
12
  local offset = ARGV[4]
11
13
 
14
+ if redis.call('hget', owners_key, test) == worker_queue_key then
15
+ redis.call('hdel', owners_key, test)
16
+ end
17
+
12
18
  if redis.call('sismember', processed_key, test) == 1 then
13
19
  return false
14
20
  end
@@ -3,6 +3,7 @@ local queue_key = KEYS[1]
3
3
  local zset_key = KEYS[2]
4
4
  local processed_key = KEYS[3]
5
5
  local worker_queue_key = KEYS[4]
6
+ local owners_key = KEYS[5]
6
7
 
7
8
  local current_time = ARGV[1]
8
9
 
@@ -10,6 +11,7 @@ local test = redis.call('rpop', queue_key)
10
11
  if test then
11
12
  redis.call('zadd', zset_key, current_time, test)
12
13
  redis.call('lpush', worker_queue_key, test)
14
+ redis.call('hset', owners_key, test, worker_queue_key)
13
15
  return test
14
16
  else
15
17
  return nil
@@ -2,6 +2,7 @@
2
2
  local zset_key = KEYS[1]
3
3
  local processed_key = KEYS[2]
4
4
  local worker_queue_key = KEYS[3]
5
+ local owners_key = KEYS[4]
5
6
 
6
7
  local current_time = ARGV[1]
7
8
  local timeout = ARGV[2]
@@ -11,6 +12,7 @@ for _, test in ipairs(lost_tests) do
11
12
  if redis.call('sismember', processed_key, test) == 0 then
12
13
  redis.call('zadd', zset_key, current_time, test)
13
14
  redis.call('lpush', worker_queue_key, test)
15
+ redis.call('hset', owners_key, test, worker_queue_key) -- Take ownership
14
16
  return test
15
17
  end
16
18
  end
@@ -19,8 +19,8 @@ module CI
19
19
  attr_reader :redis
20
20
 
21
21
  def record_test_time(test_name, duration)
22
- redis.pipelined do
23
- redis.lpush(
22
+ redis.pipelined do |pipeline|
23
+ pipeline.lpush(
24
24
  test_time_key(test_name),
25
25
  duration.to_s.force_encoding(Encoding::BINARY),
26
26
  )
@@ -29,8 +29,8 @@ module CI
29
29
  end
30
30
 
31
31
  def record_test_name(test_name)
32
- redis.pipelined do
33
- redis.lpush(
32
+ redis.pipelined do |pipeline|
33
+ pipeline.lpush(
34
34
  all_test_names_key,
35
35
  test_name.dup.force_encoding(Encoding::BINARY),
36
36
  )
@@ -39,18 +39,15 @@ module CI
39
39
  end
40
40
 
41
41
  def fetch_all_test_names
42
- values = redis.pipelined do
43
- redis.lrange(all_test_names_key, 0, -1)
42
+ values = redis.pipelined do |pipeline|
43
+ pipeline.lrange(all_test_names_key, 0, -1)
44
44
  end
45
45
  values.flatten.map(&:to_s)
46
46
  end
47
47
 
48
48
  def fetch_test_time(test_name)
49
- values = redis.pipelined do
50
- key = test_time_key(test_name)
51
- redis.lrange(key, 0, -1)
52
- end
53
- values.flatten.map(&:to_f)
49
+ key = test_time_key(test_name)
50
+ redis.lrange(key, 0, -1).map(&:to_f)
54
51
  end
55
52
 
56
53
  def all_test_names_key
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require 'ci/queue/static'
3
+ require 'set'
3
4
 
4
5
  module CI
5
6
  module Queue
@@ -15,7 +16,6 @@ module CI
15
16
  attr_reader :total
16
17
 
17
18
  def initialize(redis, config)
18
- @last_warning = nil
19
19
  @reserved_test = nil
20
20
  @shutdown_required = false
21
21
  super(redis, config)
@@ -48,7 +48,7 @@ module CI
48
48
  wait_for_master
49
49
  until shutdown_required? || config.circuit_breakers.any?(&:open?) || exhausted? || max_test_failed?
50
50
  if test = reserve
51
- yield index.fetch(test), @last_warning
51
+ yield index.fetch(test)
52
52
  else
53
53
  sleep 0.05
54
54
  end
@@ -92,7 +92,7 @@ module CI
92
92
  raise_on_mismatching_test(test_key)
93
93
  eval_script(
94
94
  :acknowledge,
95
- keys: [key('running'), key('processed')],
95
+ keys: [key('running'), key('processed'), key('owners')],
96
96
  argv: [test_key],
97
97
  ) == 1
98
98
  end
@@ -104,7 +104,14 @@ module CI
104
104
 
105
105
  requeued = config.max_requeues > 0 && global_max_requeues > 0 && eval_script(
106
106
  :requeue,
107
- keys: [key('processed'), key('requeues-count'), key('queue'), key('running')],
107
+ keys: [
108
+ key('processed'),
109
+ key('requeues-count'),
110
+ key('queue'),
111
+ key('running'),
112
+ key('worker', worker_id, 'queue'),
113
+ key('owners'),
114
+ ],
108
115
  argv: [config.max_requeues, global_max_requeues, test_key, offset],
109
116
  ) == 1
110
117
 
@@ -112,6 +119,15 @@ module CI
112
119
  requeued
113
120
  end
114
121
 
122
+ def release!
123
+ eval_script(
124
+ :release,
125
+ keys: [key('running'), key('worker', worker_id, 'queue'), key('owners')],
126
+ argv: [],
127
+ )
128
+ nil
129
+ end
130
+
115
131
  private
116
132
 
117
133
  attr_reader :index
@@ -144,7 +160,13 @@ module CI
144
160
  def try_to_reserve_test
145
161
  eval_script(
146
162
  :reserve,
147
- keys: [key('queue'), key('running'), key('processed'), key('worker', worker_id, 'queue')],
163
+ keys: [
164
+ key('queue'),
165
+ key('running'),
166
+ key('processed'),
167
+ key('worker', worker_id, 'queue'),
168
+ key('owners'),
169
+ ],
148
170
  argv: [Time.now.to_f],
149
171
  )
150
172
  end
@@ -152,7 +174,12 @@ module CI
152
174
  def try_to_reserve_lost_test
153
175
  lost_test = eval_script(
154
176
  :reserve_lost,
155
- keys: [key('running'), key('completed'), key('worker', worker_id, 'queue')],
177
+ keys: [
178
+ key('running'),
179
+ key('completed'),
180
+ key('worker', worker_id, 'queue'),
181
+ key('owners'),
182
+ ],
156
183
  argv: [Time.now.to_f, timeout],
157
184
  )
158
185
 
@@ -167,10 +194,10 @@ module CI
167
194
  @total = tests.size
168
195
 
169
196
  if @master = redis.setnx(key('master-status'), 'setup')
170
- redis.multi do
171
- redis.lpush(key('queue'), tests) unless tests.empty?
172
- redis.set(key('total'), @total)
173
- redis.set(key('master-status'), 'ready')
197
+ redis.multi do |transaction|
198
+ transaction.lpush(key('queue'), tests) unless tests.empty?
199
+ transaction.set(key('total'), @total)
200
+ transaction.set(key('master-status'), 'ready')
174
201
  end
175
202
  end
176
203
  register
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CI
4
4
  module Queue
5
- VERSION = '0.20.9'
5
+ VERSION = '0.22.1'
6
6
  DEV_SCRIPTS_ROOT = ::File.expand_path('../../../../../redis', __FILE__)
7
7
  RELEASE_SCRIPTS_ROOT = ::File.expand_path('../redis', __FILE__)
8
8
  end
@@ -81,6 +81,10 @@ module Minitest
81
81
  # Let minitest's at_exit hook trigger
82
82
  end
83
83
 
84
+ def release_command
85
+ queue.release!
86
+ end
87
+
84
88
  def grind_command
85
89
  invalid_usage!('No list to grind provided') if grind_list.nil?
86
90
  invalid_usage!('No grind count provided') if grind_count.nil?
@@ -52,6 +52,14 @@ module Minitest
52
52
  @test.time
53
53
  end
54
54
 
55
+ def test_start_timestamp
56
+ @test.start_timestamp
57
+ end
58
+
59
+ def test_finish_timestamp
60
+ @test.finish_timestamp
61
+ end
62
+
55
63
  def test_file_path
56
64
  path = @test.source_location.first
57
65
  begin
@@ -107,6 +115,8 @@ module Minitest
107
115
  test_retried: test_retried,
108
116
  test_assertions: test_assertions,
109
117
  test_duration: test_duration,
118
+ test_start_timestamp: test_start_timestamp,
119
+ test_finish_timestamp: test_finish_timestamp,
110
120
  test_file_path: test_file_path,
111
121
  test_file_line_number: test_file_line_number,
112
122
  error_class: error_class,
@@ -102,6 +102,10 @@ module Minitest
102
102
  end
103
103
  end
104
104
 
105
+ module WithTimestamps
106
+ attr_accessor :start_timestamp, :finish_timestamp
107
+ end
108
+
105
109
  module Queue
106
110
  attr_writer :run_command_formatter, :project_root
107
111
 
@@ -159,21 +163,36 @@ module Minitest
159
163
  id <=> other.id
160
164
  end
161
165
 
166
+ def with_timestamps
167
+ start_timestamp = current_timestamp
168
+ result = yield
169
+ result
170
+ ensure
171
+ if result
172
+ result.start_timestamp = start_timestamp
173
+ result.finish_timestamp = current_timestamp
174
+ end
175
+ end
176
+
162
177
  def run
163
- Minitest.run_one_method(@runnable, @method_name)
178
+ with_timestamps do
179
+ Minitest.run_one_method(@runnable, @method_name)
180
+ end
164
181
  end
165
182
 
166
183
  def flaky?
167
184
  Minitest.queue.flaky?(self)
168
185
  end
169
- end
170
186
 
171
- attr_reader :queue
187
+ private
172
188
 
173
- def queue=(queue)
174
- @queue = queue
189
+ def current_timestamp
190
+ Time.now.to_i
191
+ end
175
192
  end
176
193
 
194
+ attr_accessor :queue
195
+
177
196
  def queue_reporters=(reporters)
178
197
  @queue_reporters ||= []
179
198
  Reporters.use!(((Reporters.reporters || []) - @queue_reporters) + reporters)
@@ -244,9 +263,11 @@ MiniTest.singleton_class.prepend(MiniTest::Queue)
244
263
  if defined? MiniTest::Result
245
264
  MiniTest::Result.prepend(MiniTest::Requeueing)
246
265
  MiniTest::Result.prepend(MiniTest::Flakiness)
266
+ MiniTest::Result.prepend(MiniTest::WithTimestamps)
247
267
  else
248
268
  MiniTest::Test.prepend(MiniTest::Requeueing)
249
269
  MiniTest::Test.prepend(MiniTest::Flakiness)
270
+ MiniTest::Test.prepend(MiniTest::WithTimestamps)
250
271
 
251
272
  module MinitestBackwardCompatibility
252
273
  def source_location
data/lib/rspec/queue.rb CHANGED
@@ -112,7 +112,7 @@ module RSpec
112
112
 
113
113
  help = <<~EOS
114
114
  Specify a timeout after which if a test haven't completed, it will be picked up by another worker.
115
- It is very important to set this vlaue higher than the slowest test in the suite, otherwise performance will be impacted.
115
+ It is very important to set this value higher than the slowest test in the suite, otherwise performance will be impacted.
116
116
  Defaults to 30 seconds.
117
117
  EOS
118
118
  parser.separator ""
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.20.9
4
+ version: 0.22.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-14 00:00:00.000000000 Z
11
+ date: 2022-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 3.7.0
61
+ version: '3.10'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 3.7.0
68
+ version: '3.10'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: redis
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -187,6 +187,7 @@ files:
187
187
  - lib/ci/queue/redis/grind.rb
188
188
  - lib/ci/queue/redis/grind_record.rb
189
189
  - lib/ci/queue/redis/grind_supervisor.rb
190
+ - lib/ci/queue/redis/release.lua
190
191
  - lib/ci/queue/redis/requeue.lua
191
192
  - lib/ci/queue/redis/reserve.lua
192
193
  - lib/ci/queue/redis/reserve_lost.lua