ci-queue 0.16.0 → 0.17.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 +4 -4
- data/.gitignore +2 -1
- data/Rakefile +1 -0
- data/bin/console +1 -0
- data/ci-queue.gemspec +2 -0
- data/exe/minitest-queue +1 -0
- data/exe/rspec-queue +1 -0
- data/lib/ci/queue.rb +3 -0
- data/lib/ci/queue/bisect.rb +1 -0
- data/lib/ci/queue/build_record.rb +1 -0
- data/lib/ci/queue/circuit_breaker.rb +39 -0
- data/lib/ci/queue/common.rb +3 -2
- data/lib/ci/queue/configuration.rb +25 -11
- data/lib/ci/queue/file.rb +1 -0
- data/lib/ci/queue/grind.rb +23 -0
- data/lib/ci/queue/output_helpers.rb +1 -0
- data/lib/ci/queue/redis.rb +6 -0
- data/lib/ci/queue/redis/base.rb +1 -0
- data/lib/ci/queue/redis/build_record.rb +4 -3
- data/lib/ci/queue/redis/grind.rb +17 -0
- data/lib/ci/queue/redis/grind_record.rb +66 -0
- data/lib/ci/queue/redis/grind_supervisor.rb +13 -0
- data/lib/ci/queue/redis/retry.rb +1 -0
- data/lib/ci/queue/redis/supervisor.rb +2 -1
- data/lib/ci/queue/redis/test_time_record.rb +66 -0
- data/lib/ci/queue/redis/worker.rb +2 -1
- data/lib/ci/queue/static.rb +3 -1
- data/lib/ci/queue/version.rb +3 -1
- data/lib/minitest/queue.rb +10 -4
- data/lib/minitest/queue/build_status_recorder.rb +1 -0
- data/lib/minitest/queue/build_status_reporter.rb +1 -0
- data/lib/minitest/queue/error_report.rb +13 -0
- data/lib/minitest/queue/failure_formatter.rb +4 -0
- data/lib/minitest/queue/grind_recorder.rb +68 -0
- data/lib/minitest/queue/grind_reporter.rb +74 -0
- data/lib/minitest/queue/junit_reporter.rb +6 -5
- data/lib/minitest/queue/local_requeue_reporter.rb +1 -0
- data/lib/minitest/queue/order_reporter.rb +1 -0
- data/lib/minitest/queue/runner.rb +156 -32
- data/lib/minitest/queue/statsd.rb +1 -0
- data/lib/minitest/queue/test_data.rb +138 -0
- data/lib/minitest/queue/test_data_reporter.rb +32 -0
- data/lib/minitest/queue/test_time_recorder.rb +17 -0
- data/lib/minitest/queue/test_time_reporter.rb +70 -0
- data/lib/minitest/reporters/bisect_reporter.rb +1 -0
- data/lib/minitest/reporters/statsd_reporter.rb +1 -0
- data/lib/rspec/queue.rb +15 -14
- data/lib/rspec/queue/build_status_recorder.rb +1 -0
- data/lib/rspec/queue/order_recorder.rb +20 -0
- metadata +29 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85d4db3b22fa289a9fed0d6456071f79a5e4ced83c6fc4fdee7cc4d8725df647
|
4
|
+
data.tar.gz: e4635ddf545a73f4680eac9660bfd82e07962e52cc23281a7328e8c7beac6b7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79b7d97bfb440d829695c56aaf6634805098f95967afd07a52a7140c7d8b610eca8ae07029cf6e81846a51890960be4eba41e2b430f27b10a43dd426e9c3a038
|
7
|
+
data.tar.gz: 199500511e7864d46d4de0a4cb8f01dab40149005137b3d8cb8ef17d7050c4a5d5c7bc2d4c088ff325a996929207a837badd5af82d5ee42136ecb342f93fa796
|
data/.gitignore
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
test/fixtures/log/junit.xml
|
2
2
|
test/fixtures/log/test_order.log
|
3
|
+
test/fixtures/log/test_data.json
|
3
4
|
/.byebug_history
|
4
5
|
/.bundle/
|
5
6
|
/.yardoc
|
@@ -11,4 +12,4 @@ test/fixtures/log/test_order.log
|
|
11
12
|
/spec/reports/
|
12
13
|
/tmp/
|
13
14
|
/lib/ci/queue/redis/*.lua
|
14
|
-
|
15
|
+
test_order.log
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
data/ci-queue.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'ci/queue/version'
|
@@ -38,4 +39,5 @@ Gem::Specification.new do |spec|
|
|
38
39
|
|
39
40
|
spec.add_development_dependency 'snappy'
|
40
41
|
spec.add_development_dependency 'msgpack'
|
42
|
+
spec.add_development_dependency 'rubocop'
|
41
43
|
end
|
data/exe/minitest-queue
CHANGED
data/exe/rspec-queue
CHANGED
data/lib/ci/queue.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
require 'cgi'
|
3
5
|
|
@@ -9,6 +11,7 @@ require 'ci/queue/common'
|
|
9
11
|
require 'ci/queue/build_record'
|
10
12
|
require 'ci/queue/static'
|
11
13
|
require 'ci/queue/file'
|
14
|
+
require 'ci/queue/grind'
|
12
15
|
require 'ci/queue/bisect'
|
13
16
|
|
14
17
|
module CI
|
data/lib/ci/queue/bisect.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module CI
|
2
3
|
module Queue
|
3
4
|
class CircuitBreaker
|
@@ -13,6 +14,40 @@ module CI
|
|
13
14
|
def open?
|
14
15
|
false
|
15
16
|
end
|
17
|
+
|
18
|
+
def message
|
19
|
+
''
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Timeout
|
24
|
+
attr_reader :duration, :opened_at, :closes_at
|
25
|
+
|
26
|
+
def initialize(duration:)
|
27
|
+
@duration = duration
|
28
|
+
@opened_at = current_timestamp
|
29
|
+
@closes_at = @opened_at + duration
|
30
|
+
end
|
31
|
+
|
32
|
+
def report_failure!
|
33
|
+
end
|
34
|
+
|
35
|
+
def report_success!
|
36
|
+
end
|
37
|
+
|
38
|
+
def open?
|
39
|
+
closes_at < current_timestamp
|
40
|
+
end
|
41
|
+
|
42
|
+
def message
|
43
|
+
"This worker is exiting early because it reached its timeout of #{duration} seconds"
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def current_timestamp
|
49
|
+
Time.now.to_i
|
50
|
+
end
|
16
51
|
end
|
17
52
|
|
18
53
|
def initialize(max_consecutive_failures:)
|
@@ -31,6 +66,10 @@ module CI
|
|
31
66
|
def open?
|
32
67
|
@consecutive_failures >= @max
|
33
68
|
end
|
69
|
+
|
70
|
+
def message
|
71
|
+
'This worker is exiting early because it encountered too many consecutive test failures, probably because of some corrupted state.'
|
72
|
+
end
|
34
73
|
end
|
35
74
|
end
|
36
75
|
end
|
data/lib/ci/queue/common.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module CI
|
2
3
|
module Queue
|
3
4
|
module Common
|
@@ -15,11 +16,11 @@ module CI
|
|
15
16
|
end
|
16
17
|
|
17
18
|
def report_failure!
|
18
|
-
config.
|
19
|
+
config.circuit_breakers.each(&:report_failure!)
|
19
20
|
end
|
20
21
|
|
21
22
|
def report_success!
|
22
|
-
config.
|
23
|
+
config.circuit_breakers.each(&:report_success!)
|
23
24
|
end
|
24
25
|
|
25
26
|
def rescue_connection_errors(handler = ->(err) { nil })
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module CI
|
2
3
|
module Queue
|
3
4
|
class Configuration
|
4
|
-
attr_accessor :timeout, :build_id, :worker_id, :max_requeues
|
5
|
+
attr_accessor :timeout, :build_id, :worker_id, :max_requeues, :grind_count, :failure_file
|
5
6
|
attr_accessor :requeue_tolerance, :namespace, :seed, :failing_test, :statsd_endpoint
|
6
|
-
|
7
|
+
attr_accessor :max_test_duration, :max_test_duration_percentile
|
8
|
+
attr_reader :circuit_breakers
|
7
9
|
|
8
10
|
class << self
|
9
11
|
def from_env(env)
|
@@ -26,25 +28,37 @@ module CI
|
|
26
28
|
|
27
29
|
def initialize(
|
28
30
|
timeout: 30, build_id: nil, worker_id: nil, max_requeues: 0, requeue_tolerance: 0,
|
29
|
-
namespace: nil, seed: nil, flaky_tests: [], statsd_endpoint: nil, max_consecutive_failures: nil
|
31
|
+
namespace: nil, seed: nil, flaky_tests: [], statsd_endpoint: nil, max_consecutive_failures: nil,
|
32
|
+
grind_count: nil, max_duration: nil, failure_file: nil, max_test_duration: nil,
|
33
|
+
max_test_duration_percentile: 0.5
|
30
34
|
)
|
31
|
-
@
|
32
|
-
@timeout = timeout
|
35
|
+
@circuit_breakers = [CircuitBreaker::Disabled]
|
33
36
|
@build_id = build_id
|
34
|
-
@
|
37
|
+
@failure_file = failure_file
|
38
|
+
@flaky_tests = flaky_tests
|
39
|
+
@grind_count = grind_count
|
35
40
|
@max_requeues = max_requeues
|
41
|
+
@namespace = namespace
|
36
42
|
@requeue_tolerance = requeue_tolerance
|
37
43
|
@seed = seed
|
38
|
-
@flaky_tests = flaky_tests
|
39
44
|
@statsd_endpoint = statsd_endpoint
|
45
|
+
@timeout = timeout
|
46
|
+
@worker_id = worker_id
|
47
|
+
@max_test_duration = max_test_duration
|
48
|
+
@max_test_duration_percentile = max_test_duration_percentile
|
49
|
+
self.max_duration = max_duration
|
40
50
|
self.max_consecutive_failures = max_consecutive_failures
|
41
51
|
end
|
42
52
|
|
43
53
|
def max_consecutive_failures=(max)
|
44
|
-
|
45
|
-
CircuitBreaker.new(max_consecutive_failures: max)
|
46
|
-
|
47
|
-
|
54
|
+
if max
|
55
|
+
@circuit_breakers << CircuitBreaker.new(max_consecutive_failures: max)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def max_duration=(duration)
|
60
|
+
if duration
|
61
|
+
@circuit_breakers << CircuitBreaker::Timeout.new(duration: duration)
|
48
62
|
end
|
49
63
|
end
|
50
64
|
|
data/lib/ci/queue/file.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'ci/queue/static'
|
3
|
+
|
4
|
+
module CI
|
5
|
+
module Queue
|
6
|
+
class Grind < Static
|
7
|
+
class << self
|
8
|
+
def from_uri(uri, config)
|
9
|
+
new(uri.path, config)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(path, config)
|
14
|
+
io = path == '-' ? STDIN : ::File.open(path)
|
15
|
+
|
16
|
+
tests_to_run = io.each_line.map(&:strip).reject(&:empty?)
|
17
|
+
test_grinds = (tests_to_run * config.grind_count).sort
|
18
|
+
|
19
|
+
super(test_grinds, config)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/ci/queue/redis.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'redis'
|
2
4
|
require 'ci/queue/redis/build_record'
|
3
5
|
require 'ci/queue/redis/base'
|
4
6
|
require 'ci/queue/redis/worker'
|
7
|
+
require 'ci/queue/redis/grind_record'
|
8
|
+
require 'ci/queue/redis/grind'
|
5
9
|
require 'ci/queue/redis/retry'
|
6
10
|
require 'ci/queue/redis/supervisor'
|
11
|
+
require 'ci/queue/redis/grind_supervisor'
|
12
|
+
require 'ci/queue/redis/test_time_record'
|
7
13
|
|
8
14
|
module CI
|
9
15
|
module Queue
|
data/lib/ci/queue/redis/base.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module CI
|
2
3
|
module Queue
|
3
4
|
module Redis
|
@@ -37,8 +38,8 @@ module CI
|
|
37
38
|
redis.pipelined do
|
38
39
|
redis.hset(
|
39
40
|
key('error-reports'),
|
40
|
-
id.force_encoding(Encoding::BINARY),
|
41
|
-
payload.force_encoding(Encoding::BINARY),
|
41
|
+
id.dup.force_encoding(Encoding::BINARY),
|
42
|
+
payload.dup.force_encoding(Encoding::BINARY),
|
42
43
|
)
|
43
44
|
record_stats(stats)
|
44
45
|
end
|
@@ -47,7 +48,7 @@ module CI
|
|
47
48
|
|
48
49
|
def record_success(id, stats: nil)
|
49
50
|
redis.pipelined do
|
50
|
-
redis.hdel(key('error-reports'), id.force_encoding(Encoding::BINARY))
|
51
|
+
redis.hdel(key('error-reports'), id.dup.force_encoding(Encoding::BINARY))
|
51
52
|
record_stats(stats)
|
52
53
|
end
|
53
54
|
nil
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module CI
|
3
|
+
module Queue
|
4
|
+
module Redis
|
5
|
+
class Grind < Worker
|
6
|
+
|
7
|
+
def build
|
8
|
+
@build ||= GrindRecord.new(self, redis, config)
|
9
|
+
end
|
10
|
+
|
11
|
+
def supervisor
|
12
|
+
GrindSupervisor.new(redis_url, config)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module CI
|
3
|
+
module Queue
|
4
|
+
module Redis
|
5
|
+
class GrindRecord
|
6
|
+
|
7
|
+
def initialize(queue, redis, config)
|
8
|
+
@queue = queue
|
9
|
+
@redis = redis
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def record_error(payload, stats: nil)
|
14
|
+
redis.pipelined do
|
15
|
+
redis.lpush(
|
16
|
+
key('error-reports'),
|
17
|
+
payload.force_encoding(Encoding::BINARY),
|
18
|
+
)
|
19
|
+
record_stats(stats)
|
20
|
+
end
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def record_success(stats: nil)
|
25
|
+
record_stats(stats)
|
26
|
+
end
|
27
|
+
|
28
|
+
def record_warning(_,_)
|
29
|
+
#do nothing
|
30
|
+
end
|
31
|
+
|
32
|
+
def error_reports
|
33
|
+
redis.lrange(key('error-reports'), 0, -1)
|
34
|
+
end
|
35
|
+
|
36
|
+
def fetch_stats(stat_names)
|
37
|
+
counts = redis.pipelined do
|
38
|
+
stat_names.each { |c| redis.hvals(key(c)) }
|
39
|
+
end
|
40
|
+
stat_names.zip(counts.map { |values| values.map(&:to_f).inject(:+).to_f }).to_h
|
41
|
+
end
|
42
|
+
|
43
|
+
def pop_warnings
|
44
|
+
[]
|
45
|
+
end
|
46
|
+
|
47
|
+
alias failed_tests error_reports
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :redis, :config
|
52
|
+
|
53
|
+
def key(*args)
|
54
|
+
['build', config.build_id, *args].join(':')
|
55
|
+
end
|
56
|
+
|
57
|
+
def record_stats(stats)
|
58
|
+
return unless stats
|
59
|
+
stats.each do |stat_name, stat_value|
|
60
|
+
redis.hset(key(stat_name), config.worker_id, stat_value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/ci/queue/redis/retry.rb
CHANGED
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module CI
|
3
|
+
module Queue
|
4
|
+
module Redis
|
5
|
+
class TestTimeRecord < Worker
|
6
|
+
def record(test_name, duration)
|
7
|
+
record_test_time(test_name, duration)
|
8
|
+
record_test_name(test_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch
|
12
|
+
fetch_all_test_names.each_with_object({}) do |test_name, test_time_hash|
|
13
|
+
test_time_hash[test_name] = fetch_test_time(test_name)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :redis
|
20
|
+
|
21
|
+
def record_test_time(test_name, duration)
|
22
|
+
redis.pipelined do
|
23
|
+
redis.lpush(
|
24
|
+
test_time_key(test_name),
|
25
|
+
duration.to_s.force_encoding(Encoding::BINARY),
|
26
|
+
)
|
27
|
+
end
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def record_test_name(test_name)
|
32
|
+
redis.pipelined do
|
33
|
+
redis.lpush(
|
34
|
+
all_test_names_key,
|
35
|
+
test_name.dup.force_encoding(Encoding::BINARY),
|
36
|
+
)
|
37
|
+
end
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch_all_test_names
|
42
|
+
values = redis.pipelined do
|
43
|
+
redis.lrange(all_test_names_key, 0, -1)
|
44
|
+
end
|
45
|
+
values.flatten.map(&:to_s)
|
46
|
+
end
|
47
|
+
|
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)
|
54
|
+
end
|
55
|
+
|
56
|
+
def all_test_names_key
|
57
|
+
"build:#{config.build_id}:list_of_test_names".dup.force_encoding(Encoding::BINARY)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_time_key(test_name)
|
61
|
+
"build:#{config.build_id}:#{test_name}".dup.force_encoding(Encoding::BINARY)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|