ci-queue 0.1.0 → 0.2.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
  SHA1:
3
- metadata.gz: 11fda6e3d6ac119631a90d63086ebb9e06d628bd
4
- data.tar.gz: 9ecad3efc2ec99b01e5a58f9defc55b772ffa65b
3
+ metadata.gz: 61155b4429b71670e473e39cf41dbac081c24727
4
+ data.tar.gz: 60d3c11057a5d7412554f51f10fe8beeefb4a483
5
5
  SHA512:
6
- metadata.gz: 121adc5f59a3c95c62121e6bad8ebeb321890be2a67e3f3ae14ae5b6fd5febab786b8d4a4bd4e74e54e14bc29968dc07cc3a2c54d30c7230e83a1b52cfdfb58f
7
- data.tar.gz: 658a17f26818a69bb7a60c9e563a5b2e16dc4dded98637455e49313d438b3d72fa323b1a10c0179d223c8f32b95009238cddc6e145c7db46754a7114ec596ec8
6
+ metadata.gz: d59e89e8a27f00177f32ee55d7115b93617e21a9cfa2161a211da558831aeddadd4739242a93e9f12e183458839e6766903084b03814730848127a5febd9e21b
7
+ data.tar.gz: 377c2123f5a51743a78abd13b5fd5c72f3bada7a1f230ea6712cef43275365e03a166f54c01097d111f2fd390b8f71d756732768fb70cc711d40c087867ee804
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ /.byebug_history
1
2
  /.bundle/
2
3
  /.yardoc
3
4
  /Gemfile.lock
data/.travis.yml CHANGED
@@ -1,5 +1,7 @@
1
1
  sudo: false
2
+ services:
3
+ - redis-server
2
4
  language: ruby
3
5
  rvm:
4
6
  - 2.3.1
5
- before_install: gem install bundler -v 1.13.3
7
+ before_install: gem install bundler -v 1.13.6
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # CI::Queue
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/ci-queue.svg)](https://rubygems.org/gems/ci-queue)
4
+ [![Build Status](https://travis-ci.org/Shopify/ci-queue.svg?branch=master)](https://travis-ci.org/Shopify/ci-queue)
5
+
3
6
  Distribute tests over many workers using a queue.
4
7
 
5
8
  ## Why a queue?
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require 'rake/testtask'
4
4
  Rake::TestTask.new(:test) do |t|
5
5
  t.libs << 'test'
6
6
  t.libs << 'lib'
7
- t.test_files = FileList['test/**/*_test.rb']
7
+ t.test_files = FileList['test/**/*_test.rb'] - FileList['test/fixtures/*_test.rb']
8
8
  end
9
9
 
10
10
  task :default => :test
data/ci-queue.gemspec CHANGED
@@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency 'minitest', '~> 5.0'
27
27
  spec.add_development_dependency 'redis', '~> 3.3'
28
28
  spec.add_development_dependency 'simplecov', '~> 0.12'
29
+ spec.add_development_dependency 'minitest-reporters', '~> 1.1'
29
30
  end
data/lib/ci/queue/file.rb CHANGED
@@ -3,8 +3,8 @@ require 'ci/queue/static'
3
3
  module CI
4
4
  module Queue
5
5
  class File < Static
6
- def initialize(path)
7
- super(::File.readlines(path).map(&:strip).reject(&:empty?))
6
+ def initialize(path, **args)
7
+ super(::File.readlines(path).map(&:strip).reject(&:empty?), **args)
8
8
  end
9
9
  end
10
10
  end
@@ -1,6 +1,7 @@
1
1
  require 'redis'
2
2
  require 'ci/queue/redis/base'
3
3
  require 'ci/queue/redis/worker'
4
+ require 'ci/queue/redis/retry'
4
5
  require 'ci/queue/redis/supervisor'
5
6
 
6
7
  module CI
@@ -4,7 +4,7 @@ module CI
4
4
  class Base
5
5
  def initialize(redis:, build_id:)
6
6
  @redis = redis
7
- @key = "build:#{build_id}"
7
+ @build_id = build_id
8
8
  end
9
9
 
10
10
  def empty?
@@ -44,10 +44,10 @@ module CI
44
44
 
45
45
  private
46
46
 
47
- attr_reader :redis
47
+ attr_reader :redis, :build_id
48
48
 
49
49
  def key(*args)
50
- [@key, *args].join(':')
50
+ ['build', build_id, *args].join(':')
51
51
  end
52
52
 
53
53
  def master_status
@@ -55,9 +55,12 @@ module CI
55
55
  end
56
56
 
57
57
  def eval_script(script, *args)
58
+ redis.evalsha(load_script(script), *args)
59
+ end
60
+
61
+ def load_script(script)
58
62
  @scripts_cache ||= {}
59
- sha = (@scripts_cache[script] ||= redis.script(:load, script))
60
- redis.evalsha(sha, *args)
63
+ @scripts_cache[script] ||= redis.script(:load, script)
61
64
  end
62
65
  end
63
66
  end
@@ -0,0 +1,29 @@
1
+ module CI
2
+ module Queue
3
+ module Redis
4
+ class Retry < Static
5
+ def initialize(tests, redis:, build_id:, worker_id:, **args)
6
+ @redis = redis
7
+ @build_id = build_id
8
+ @worker_id = worker_id
9
+ super(tests, **args)
10
+ end
11
+
12
+ def minitest_reporters
13
+ require 'minitest/reporters/redis_reporter'
14
+ @minitest_reporters ||= [
15
+ Minitest::Reporters::RedisReporter::Worker.new(
16
+ redis: redis,
17
+ build_id: build_id,
18
+ worker_id: worker_id,
19
+ )
20
+ ]
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :redis, :build_id, :worker_id
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,10 +1,17 @@
1
+ require 'ci/queue/static'
2
+
1
3
  module CI
2
4
  module Queue
3
5
  module Redis
6
+ ReservationError = Class.new(StandardError)
7
+
4
8
  class Worker < Base
5
9
  attr_reader :total
6
10
 
7
- def initialize(tests, redis:, build_id:, worker_id:, timeout:)
11
+ def initialize(tests, redis:, build_id:, worker_id:, timeout:, max_requeues: 0, requeue_tolerance: 0.0)
12
+ @reserved_test = nil
13
+ @max_requeues = max_requeues
14
+ @global_max_requeues = (tests.size * requeue_tolerance).ceil
8
15
  @shutdown_required = false
9
16
  super(redis: redis, build_id: build_id)
10
17
  @worker_id = worker_id
@@ -26,19 +33,90 @@ module CI
26
33
 
27
34
  def poll
28
35
  wait_for_master
29
- while test = reserve
30
- yield test
31
- acknowledge(test)
36
+ until shutdown_required? || empty?
37
+ if test = reserve
38
+ yield test
39
+ else
40
+ sleep 0.05
41
+ end
32
42
  end
33
43
  end
34
44
 
35
- def retry_queue
36
- Static.new(redis.lrange(key("worker:#{worker_id}:queue"), 0, -1).reverse)
45
+ def retry_queue(**args)
46
+ Retry.new(
47
+ redis.lrange(key('worker', worker_id, 'queue'), 0, -1).reverse.uniq,
48
+ redis: redis,
49
+ build_id: build_id,
50
+ worker_id: worker_id,
51
+ **args
52
+ )
53
+ end
54
+
55
+ def minitest_reporters
56
+ require 'minitest/reporters/redis_reporter'
57
+ @minitest_reporters ||= [
58
+ Minitest::Reporters::RedisReporter::Worker.new(
59
+ redis: redis,
60
+ build_id: build_id,
61
+ worker_id: worker_id,
62
+ )
63
+ ]
64
+ end
65
+
66
+ def acknowledge(test, success)
67
+ if @reserved_test == test
68
+ @reserved_test = nil
69
+ else
70
+ raise ReservationError, "Acknowledged #{test.inspect} but #{@reserved_test.inspect} was reserved"
71
+ end
72
+
73
+ if !success && should_requeue?(test)
74
+ requeue(test)
75
+ false
76
+ else
77
+ ack(test)
78
+ true
79
+ end
37
80
  end
38
81
 
39
82
  private
40
83
 
41
- attr_reader :worker_id, :timeout
84
+ attr_reader :worker_id, :timeout, :max_requeues, :global_max_requeues
85
+
86
+ def should_requeue?(test)
87
+ individual_requeues, global_requeues = redis.multi do
88
+ redis.hincrby(key('requeues-count'), test, 1)
89
+ redis.hincrby(key('requeues-count'), '___total___'.freeze, 1)
90
+ end
91
+
92
+ if individual_requeues.to_i > max_requeues || global_requeues.to_i > global_max_requeues
93
+ redis.multi do
94
+ redis.hincrby(key('requeues-count'), test, -1)
95
+ redis.hincrby(key('requeues-count'), '___total___'.freeze, -1)
96
+ end
97
+ return false
98
+ end
99
+
100
+ true
101
+ end
102
+
103
+ def requeue(test)
104
+ load_script(ACKNOWLEDGE)
105
+ redis.multi do
106
+ redis.decr(key('processed'))
107
+ redis.rpush(key('queue'), test)
108
+ ack(test)
109
+ end
110
+ end
111
+
112
+ def reserve
113
+ if @reserved_test
114
+ raise ReservationError, "#{@reserved_test.inspect} is already reserved. " \
115
+ "You have to acknowledge it before you can reserve another one"
116
+ end
117
+
118
+ @reserved_test = (try_to_reserve_lost_test || try_to_reserve_test)
119
+ end
42
120
 
43
121
  RESERVE_TEST = %{
44
122
  local queue_key = KEYS[1]
@@ -53,14 +131,8 @@ module CI
53
131
  return nil
54
132
  end
55
133
  }
56
- def reserve
57
- return if shutdown_required?
58
-
59
- if test = eval_script(RESERVE_TEST, keys: [key('queue'), key('running')], argv: [Time.now.to_f])
60
- return test
61
- else
62
- reserve_lost_test
63
- end
134
+ def try_to_reserve_test
135
+ eval_script(RESERVE_TEST, keys: [key('queue'), key('running')], argv: [Time.now.to_f])
64
136
  end
65
137
 
66
138
  RESERVE_LOST_TEST = %{
@@ -76,14 +148,8 @@ module CI
76
148
  return nil
77
149
  end
78
150
  }
79
- def reserve_lost_test
80
- until redis.zcard(key('running')) == 0
81
- if test = eval_script(RESERVE_LOST_TEST, keys: [key('running')], argv: [Time.now.to_f, timeout])
82
- return test
83
- end
84
- sleep 0.1
85
- end
86
- nil
151
+ def try_to_reserve_lost_test
152
+ eval_script(RESERVE_LOST_TEST, keys: [key('running')], argv: [Time.now.to_f, timeout])
87
153
  end
88
154
 
89
155
  ACKNOWLEDGE = %{
@@ -95,9 +161,9 @@ module CI
95
161
  redis.call('incr', processed_count_key)
96
162
  end
97
163
  }
98
- def acknowledge(test)
164
+ def ack(test)
99
165
  eval_script(ACKNOWLEDGE, keys: [key('running'), key('processed')], argv: [test])
100
- redis.lpush(key("worker:#{worker_id}:queue"), test)
166
+ redis.lpush(key('worker', worker_id, 'queue'), test)
101
167
  end
102
168
 
103
169
  def push(tests)
@@ -3,10 +3,12 @@ module CI
3
3
  class Static
4
4
  attr_reader :progress, :total
5
5
 
6
- def initialize(tests)
6
+ def initialize(tests, max_requeues: 0, requeue_tolerance: 0.0)
7
7
  @queue = tests
8
8
  @progress = 0
9
9
  @total = tests.size
10
+ @max_requeues = max_requeues
11
+ @global_max_requeues = (tests.size * requeue_tolerance).ceil
10
12
  end
11
13
 
12
14
  def to_a
@@ -27,6 +29,32 @@ module CI
27
29
  def empty?
28
30
  @queue.empty?
29
31
  end
32
+
33
+ def acknowledge(test, success)
34
+ if !success && should_requeue?(test)
35
+ requeue(test)
36
+ return false
37
+ end
38
+
39
+ true
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :max_requeues, :global_max_requeues
45
+
46
+ def should_requeue?(test)
47
+ requeues[test] < max_requeues && requeues.values.inject(0, :+) < global_max_requeues
48
+ end
49
+
50
+ def requeue(test)
51
+ requeues[test] += 1
52
+ @queue.unshift(test)
53
+ end
54
+
55
+ def requeues
56
+ @requeues ||= Hash.new(0)
57
+ end
30
58
  end
31
59
  end
32
60
  end
@@ -1,5 +1,5 @@
1
1
  module CI
2
2
  module Queue
3
- VERSION = '0.1.0'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
@@ -1,11 +1,77 @@
1
1
  require 'minitest'
2
2
 
3
+ gem 'minitest-reporters', '~> 1.1'
4
+ require 'minitest/reporters'
5
+
3
6
  module Minitest
7
+ class Requeue < Skip
8
+ attr_reader :failure
9
+
10
+ def initialize(failure)
11
+ super()
12
+ @failure = failure
13
+ end
14
+
15
+ def result_label
16
+ "Requeued"
17
+ end
18
+
19
+ def backtrace
20
+ failure.backtrace
21
+ end
22
+
23
+ def error
24
+ failure.error
25
+ end
26
+
27
+ def message
28
+ failure.message
29
+ end
30
+ end
31
+
32
+ module Requeueing
33
+ # Make requeues acts as skips for reporters not aware of the difference.
34
+ def skipped?
35
+ super || requeued?
36
+ end
37
+
38
+ def requeued?
39
+ Requeue === failure
40
+ end
41
+
42
+ def requeue!
43
+ self.failures.unshift(Requeue.new(self.failures.shift))
44
+ end
45
+ end
46
+
4
47
  module Queue
5
- attr_accessor :queue
48
+ attr_reader :queue
49
+
50
+ def queue=(queue)
51
+ @queue = queue
52
+ if queue.respond_to?(:minitest_reporters)
53
+ self.queue_reporters = queue.minitest_reporters
54
+ else
55
+ self.queue_reporters = []
56
+ end
57
+ end
58
+
59
+ def queue_reporters=(reporters)
60
+ @queue_reporters ||= []
61
+ Reporters.reporters = ((Reporters.reporters || []) - @queue_reporters) + reporters
62
+ @queue_reporters = reporters
63
+ end
6
64
 
7
65
  SuiteNotFound = Class.new(StandardError)
8
66
 
67
+ def loaded_tests
68
+ MiniTest::Test.runnables.flat_map do |suite|
69
+ suite.runnable_methods.map do |method|
70
+ "#{suite}##{method}"
71
+ end
72
+ end
73
+ end
74
+
9
75
  def __run(*args)
10
76
  if queue
11
77
  run_from_queue(*args)
@@ -17,11 +83,15 @@ module Minitest
17
83
  def run_from_queue(reporter, *)
18
84
  runnable_classes = Minitest::Runnable.runnables.map { |s| [s.name, s] }.to_h
19
85
 
20
- queue.poll do |msg|
21
- class_name, method = msg.split("#".freeze, 2)
86
+ queue.poll do |test_name|
87
+ class_name, method_name = test_name.split("#".freeze, 2)
22
88
 
23
- if suite = runnable_classes[class_name]
24
- Minitest::Runnable.run_one_method(suite, method, reporter)
89
+ if klass = runnable_classes[class_name]
90
+ result = Minitest.run_one_method(klass, method_name)
91
+ unless queue.acknowledge(test_name, result.passed? || result.skipped?)
92
+ result.requeue!
93
+ end
94
+ reporter.record(result)
25
95
  else
26
96
  raise SuiteNotFound, "Couldn't find suite matching: #{msg.inspect}"
27
97
  end
@@ -31,3 +101,4 @@ module Minitest
31
101
  end
32
102
 
33
103
  MiniTest.singleton_class.prepend(MiniTest::Queue)
104
+ MiniTest::Test.prepend(MiniTest::Requeueing)
@@ -0,0 +1,65 @@
1
+ require 'ansi'
2
+ require 'delegate'
3
+
4
+ module Minitest
5
+ module Reporters
6
+ class FailureFormatter < SimpleDelegator
7
+ include ANSI::Code
8
+
9
+ def initialize(test)
10
+ @test = test
11
+ super
12
+ end
13
+
14
+ def to_s
15
+ [
16
+ header,
17
+ body,
18
+ "\n"
19
+ ].flatten.compact.join("\n")
20
+ end
21
+
22
+ def to_h
23
+ {
24
+ test_and_module_name: "#{test.class}##{test.name}",
25
+ test_name: test.name,
26
+ output: to_s,
27
+ }
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :test
33
+
34
+ def header
35
+ "#{red(status)} #{test.class}##{test.name}"
36
+ end
37
+
38
+ def status
39
+ if test.error?
40
+ 'ERROR'
41
+ elsif test.failure
42
+ 'FAIL'
43
+ else
44
+ raise ArgumentError, "Couldn't infer test status"
45
+ end
46
+ end
47
+
48
+ def body
49
+ error = test.failure
50
+ message = if error.is_a?(MiniTest::UnexpectedError)
51
+ "#{error.exception.class}: #{error.exception.message}"
52
+ else
53
+ error.exception.message
54
+ end
55
+
56
+ backtrace = Minitest.filter_backtrace(error.backtrace).map { |line| ' ' + relativize(line) }
57
+ [yellow(message), *backtrace].join("\n")
58
+ end
59
+
60
+ def relativize(trace_line)
61
+ trace_line.sub(/\A#{Regexp.escape("#{Dir.pwd}/")}/, '')
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,48 @@
1
+ require 'minitest/reporters'
2
+
3
+ module Minitest
4
+ module Reporters
5
+ class QueueReporter < BaseReporter
6
+ include ANSI::Code
7
+ attr_accessor :requeues
8
+
9
+ def initialize(*)
10
+ self.requeues = 0
11
+ super
12
+ end
13
+
14
+ def report
15
+ self.requeues = results.count(&:requeued?)
16
+ super
17
+ print_report
18
+ end
19
+
20
+ private
21
+
22
+ def print_report
23
+ success = failures.zero? && errors.zero?
24
+ failures_count = "#{failures} failures, #{errors} errors,"
25
+ puts [
26
+ 'Ran %d tests, %d assertions,' % [count, assertions],
27
+ success ? green(failures_count) : red(failures_count),
28
+ yellow("#{skips} skips, #{requeues} requeues"),
29
+ 'in %.2fs' % total_time,
30
+ ].join(' ')
31
+ end
32
+
33
+ def message_for(test)
34
+ e = test.failure
35
+
36
+ if test.requeued?
37
+ "Requeued:\n#{test.class}##{test.name} [#{location(e)}]:\n#{e.message}"
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ def result_line
44
+ "#{super}, #{requeues} requeues"
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,202 @@
1
+ require 'minitest/reporters'
2
+ require 'minitest/reporters/failure_formatter'
3
+
4
+ module Minitest
5
+ module Reporters
6
+ module RedisReporter
7
+ include ANSI::Code
8
+
9
+ COUNTERS = %w(
10
+ assertions
11
+ errors
12
+ failures
13
+ skips
14
+ requeues
15
+ total_time
16
+ ).freeze
17
+
18
+ class << self
19
+ attr_accessor :failure_formatter
20
+ end
21
+ self.failure_formatter = FailureFormatter
22
+
23
+ class Error
24
+ class << self
25
+ def load(payload)
26
+ Marshal.load(payload)
27
+ end
28
+ end
29
+
30
+ def initialize(data)
31
+ @data = data
32
+ end
33
+
34
+ def dump
35
+ Marshal.dump(self)
36
+ end
37
+
38
+ def test_name
39
+ @data[:test_name]
40
+ end
41
+
42
+ def test_and_module_name
43
+ @data[:test_and_module_name]
44
+ end
45
+
46
+ def to_s
47
+ output
48
+ end
49
+
50
+ def output
51
+ @data[:output]
52
+ end
53
+ end
54
+
55
+ class Base < BaseReporter
56
+ def initialize(redis:, build_id:, **options)
57
+ @redis = redis
58
+ @key = "build:#{build_id}"
59
+ super(options)
60
+ end
61
+
62
+ def total
63
+ redis.get(key('total')).to_i
64
+ end
65
+
66
+ def processed
67
+ redis.get(key('processed')).to_i
68
+ end
69
+
70
+ def completed?
71
+ total > 0 && total == processed
72
+ end
73
+
74
+ def error_reports
75
+ redis.hgetall(key('error-reports')).sort_by(&:first).map { |k, v| Error.load(v) }
76
+ end
77
+
78
+ private
79
+
80
+ attr_reader :redis
81
+
82
+ def key(*args)
83
+ [@key, *args].join(':')
84
+ end
85
+ end
86
+
87
+ class Summary < Base
88
+ include ANSI::Code
89
+
90
+ def report(io: STDOUT)
91
+ io.puts aggregates
92
+ errors = error_reports
93
+ io.puts errors
94
+
95
+ errors.empty?
96
+ end
97
+
98
+ def record(*)
99
+ raise NotImplementedError
100
+ end
101
+
102
+ def failures
103
+ fetch_summary['failures'].to_i
104
+ end
105
+
106
+ def errors
107
+ fetch_summary['errors'].to_i
108
+ end
109
+
110
+ def assertions
111
+ fetch_summary['assertions'].to_i
112
+ end
113
+
114
+ def skips
115
+ fetch_summary['skips'].to_i
116
+ end
117
+
118
+ def requeues
119
+ fetch_summary['requeues'].to_i
120
+ end
121
+
122
+ def total_time
123
+ fetch_summary['total_time'].to_f
124
+ end
125
+
126
+ private
127
+
128
+ def aggregates
129
+ success = failures.zero? && errors.zero?
130
+ failures_count = "#{failures} failures, #{errors} errors,"
131
+
132
+ [
133
+ 'Ran %d tests, %d assertions,' % [processed, assertions],
134
+ success ? green(failures_count) : red(failures_count),
135
+ yellow("#{skips} skips, #{requeues} requeues"),
136
+ 'in %.2fs (aggregated)' % total_time,
137
+ ].join(' ')
138
+ end
139
+
140
+ def fetch_summary
141
+ @summary ||= begin
142
+ counts = redis.pipelined do
143
+ COUNTERS.each { |c| redis.hgetall(key(c)) }
144
+ end
145
+ COUNTERS.zip(counts.map { |h| h.values.map(&:to_f).inject(:+).to_f }).to_h
146
+ end
147
+ end
148
+ end
149
+
150
+ class Worker < Base
151
+ attr_accessor :requeues
152
+
153
+ def initialize(worker_id:, **options)
154
+ super
155
+ @worker_id = worker_id
156
+ self.failures = 0
157
+ self.errors = 0
158
+ self.skips = 0
159
+ self.requeues = 0
160
+ end
161
+
162
+ def report
163
+ # noop
164
+ end
165
+
166
+ def record(test)
167
+ super
168
+
169
+ self.total_time = Minitest.clock_time - start_time
170
+ if test.requeued?
171
+ self.requeues += 1
172
+ elsif test.skipped?
173
+ self.skips += 1
174
+ elsif test.error?
175
+ self.errors += 1
176
+ elsif test.failure
177
+ self.failures += 1
178
+ end
179
+
180
+ redis.multi do
181
+ if (test.failure || test.error?) && !test.skipped?
182
+ redis.hset(key('error-reports'), "#{test.class}##{test.name}", dump(test))
183
+ else
184
+ redis.hdel(key('error-reports'), "#{test.class}##{test.name}")
185
+ end
186
+ COUNTERS.each do |counter|
187
+ redis.hset(key(counter), worker_id, send(counter))
188
+ end
189
+ end
190
+ end
191
+
192
+ private
193
+
194
+ def dump(test)
195
+ Error.new(RedisReporter.failure_formatter.new(test).to_h).dump
196
+ end
197
+
198
+ attr_reader :worker_id, :aggregates
199
+ end
200
+ end
201
+ end
202
+ end
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.1.0
4
+ version: 0.2.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: 2016-11-15 00:00:00.000000000 Z
11
+ date: 2016-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0.12'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest-reporters
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.1'
83
97
  description: To parallelize your CI without having to balance your tests
84
98
  email:
85
99
  - jean.boussier@shopify.com
@@ -102,11 +116,15 @@ files:
102
116
  - lib/ci/queue/file.rb
103
117
  - lib/ci/queue/redis.rb
104
118
  - lib/ci/queue/redis/base.rb
119
+ - lib/ci/queue/redis/retry.rb
105
120
  - lib/ci/queue/redis/supervisor.rb
106
121
  - lib/ci/queue/redis/worker.rb
107
122
  - lib/ci/queue/static.rb
108
123
  - lib/ci/queue/version.rb
109
124
  - lib/minitest/queue.rb
125
+ - lib/minitest/reporters/failure_formatter.rb
126
+ - lib/minitest/reporters/queue_reporter.rb
127
+ - lib/minitest/reporters/redis_reporter.rb
110
128
  homepage: https://github.com/Shopify/ci-queue
111
129
  licenses:
112
130
  - MIT
@@ -127,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
145
  version: '0'
128
146
  requirements: []
129
147
  rubyforge_project:
130
- rubygems_version: 2.2.5
148
+ rubygems_version: 2.5.1
131
149
  signing_key:
132
150
  specification_version: 4
133
151
  summary: Distribute tests over many workers using a queue