ci-queue 0.1.0 → 0.2.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
  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