ci-queue 0.16.0 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|