ci-queue 0.64.0 → 0.66.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/Gemfile.lock +1 -1
- data/lib/ci/queue/configuration.rb +8 -1
- data/lib/ci/queue/redis/base.rb +10 -3
- data/lib/ci/queue/redis/supervisor.rb +8 -9
- data/lib/ci/queue/redis/worker.rb +8 -1
- data/lib/ci/queue/version.rb +1 -1
- data/lib/minitest/queue/build_status_reporter.rb +124 -12
- data/lib/minitest/queue/error_report.rb +4 -0
- data/lib/minitest/queue/runner.rb +6 -11
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b78efa3839e0fdc3a3998ef7d664bab01f28d0cdde96c28b790f9c912964d33a
|
4
|
+
data.tar.gz: 4bc014eb8401ce329be509dc76f449a4d80696667563e3236c5f9634514b7df0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be5f84376a0a6bc93776eaafa4a725e48061e18ea13d79c407cb599b665201031fc189ae91cd89607f3c2fa062eb388caaa554411405cc7adf6b073610ee078f
|
7
|
+
data.tar.gz: 47057ca6d1b2d35eb0068be06330802a0ce614744f8498d069ef9bc1ffdd499a8a38861a3bfa7aa62e2b10becd0be5c99306026938c7ca704ede42396109367c
|
data/Gemfile.lock
CHANGED
@@ -25,7 +25,14 @@ module CI
|
|
25
25
|
|
26
26
|
def load_flaky_tests(path)
|
27
27
|
return [] unless path
|
28
|
-
::File.
|
28
|
+
if ::File.extname(path) == ".xml"
|
29
|
+
require 'rexml/document'
|
30
|
+
REXML::Document.new(::File.read(path)).elements.to_a("//testcase").map do |element|
|
31
|
+
"#{element.attributes['classname']}##{element.attributes['name']}"
|
32
|
+
end.to_set
|
33
|
+
else
|
34
|
+
::File.readlines(path).map(&:chomp).to_set
|
35
|
+
end
|
29
36
|
rescue SystemCallError
|
30
37
|
[]
|
31
38
|
end
|
data/lib/ci/queue/redis/base.rb
CHANGED
@@ -11,6 +11,8 @@ module CI
|
|
11
11
|
::SocketError, # https://github.com/redis/redis-rb/pull/631
|
12
12
|
].freeze
|
13
13
|
|
14
|
+
DEFAULT_TIMEOUT = 2
|
15
|
+
|
14
16
|
module RedisInstrumentation
|
15
17
|
def call(command, redis_config)
|
16
18
|
logger = redis_config.custom[:debug_log]
|
@@ -45,9 +47,10 @@ module CI
|
|
45
47
|
# ci-queue should not contain any sensitive data, so we can just disable the verification.
|
46
48
|
ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE },
|
47
49
|
custom: custom_config,
|
50
|
+
timeout: DEFAULT_TIMEOUT,
|
48
51
|
)
|
49
52
|
else
|
50
|
-
@redis = ::Redis.new(url: redis_url)
|
53
|
+
@redis = ::Redis.new(url: redis_url, timeout: DEFAULT_TIMEOUT)
|
51
54
|
end
|
52
55
|
end
|
53
56
|
|
@@ -133,11 +136,15 @@ module CI
|
|
133
136
|
redis.zcard(key('running'))
|
134
137
|
end
|
135
138
|
|
136
|
-
def
|
139
|
+
def test_ids
|
137
140
|
redis.multi do |transaction|
|
138
141
|
transaction.lrange(key('queue'), 0, -1)
|
139
142
|
transaction.zrange(key('running'), 0, -1)
|
140
|
-
end.flatten
|
143
|
+
end.flatten
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_a
|
147
|
+
test_ids.reverse.map { |k| index.fetch(k) }
|
141
148
|
end
|
142
149
|
|
143
150
|
def progress
|
@@ -24,29 +24,28 @@ module CI
|
|
24
24
|
|
25
25
|
yield if block_given?
|
26
26
|
|
27
|
-
time_left = config.report_timeout - duration.to_i
|
28
|
-
time_left_with_no_workers = config.inactive_workers_timeout
|
29
|
-
until exhausted? || time_left <= 0 || max_test_failed? || time_left_with_no_workers <= 0
|
30
|
-
time_left -= 1
|
27
|
+
@time_left = config.report_timeout - duration.to_i
|
28
|
+
@time_left_with_no_workers = config.inactive_workers_timeout
|
29
|
+
until exhausted? || @time_left <= 0 || max_test_failed? || @time_left_with_no_workers <= 0
|
30
|
+
@time_left -= 1
|
31
31
|
sleep 1
|
32
32
|
|
33
33
|
if active_workers?
|
34
|
-
time_left_with_no_workers = config.inactive_workers_timeout
|
34
|
+
@time_left_with_no_workers = config.inactive_workers_timeout
|
35
35
|
else
|
36
|
-
time_left_with_no_workers -= 1
|
36
|
+
@time_left_with_no_workers -= 1
|
37
37
|
end
|
38
38
|
|
39
39
|
yield if block_given?
|
40
40
|
end
|
41
41
|
|
42
|
-
puts "Aborting, timed out." if time_left <= 0 && !exhausted?
|
43
|
-
puts "Aborting, it seems all workers died." if time_left_with_no_workers <= 0 && !exhausted?
|
44
|
-
|
45
42
|
exhausted?
|
46
43
|
rescue CI::Queue::Redis::LostMaster
|
47
44
|
false
|
48
45
|
end
|
49
46
|
|
47
|
+
attr_reader :time_left, :time_left_with_no_workers
|
48
|
+
|
50
49
|
private
|
51
50
|
|
52
51
|
def active_workers?
|
@@ -201,7 +201,14 @@ module CI
|
|
201
201
|
def push(tests)
|
202
202
|
@total = tests.size
|
203
203
|
|
204
|
-
|
204
|
+
# We set a unique value (worker_id) and read it back to make "SET if Not eXists" idempotent in case of a retry.
|
205
|
+
value = key('setup', worker_id)
|
206
|
+
_, status = redis.pipelined do |pipeline|
|
207
|
+
pipeline.set(key('master-status'), value, nx: true)
|
208
|
+
pipeline.get(key('master-status'))
|
209
|
+
end
|
210
|
+
|
211
|
+
if @master = (value == status)
|
205
212
|
puts "Worker electected as leader, pushing #{@total} tests to the queue."
|
206
213
|
puts
|
207
214
|
|
data/lib/ci/queue/version.rb
CHANGED
@@ -4,8 +4,91 @@ module Minitest
|
|
4
4
|
class BuildStatusReporter < Minitest::Reporters::BaseReporter
|
5
5
|
include ::CI::Queue::OutputHelpers
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
class JUnitReporter
|
8
|
+
def initialize(file, error_reports)
|
9
|
+
@file = file
|
10
|
+
@error_reports = error_reports
|
11
|
+
end
|
12
|
+
|
13
|
+
def write
|
14
|
+
File.open(@file, 'w+') do |file|
|
15
|
+
format_document(generate_document(@error_reports), file)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def generate_document(error_reports)
|
22
|
+
suites = error_reports.group_by { |error_report| error_report.test_suite }
|
23
|
+
|
24
|
+
doc = REXML::Document.new(nil, {
|
25
|
+
:prologue_quote => :quote,
|
26
|
+
:attribute_quote => :quote,
|
27
|
+
})
|
28
|
+
doc << REXML::XMLDecl.new('1.1', 'utf-8')
|
29
|
+
|
30
|
+
testsuites = doc.add_element('testsuites')
|
31
|
+
suites.each do |suite, error_reports|
|
32
|
+
add_tests_to(testsuites, suite, error_reports)
|
33
|
+
end
|
34
|
+
|
35
|
+
doc
|
36
|
+
end
|
37
|
+
|
38
|
+
def format_document(doc, io)
|
39
|
+
formatter = REXML::Formatters::Pretty.new
|
40
|
+
formatter.write(doc, io)
|
41
|
+
io << "\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_tests_to(testsuites, suite, error_reports)
|
45
|
+
testsuite = testsuites.add_element(
|
46
|
+
'testsuite',
|
47
|
+
'name' => suite,
|
48
|
+
'filepath' => Minitest::Queue.relative_path(error_reports.first.test_file),
|
49
|
+
'tests' => error_reports.count,
|
50
|
+
)
|
51
|
+
|
52
|
+
error_reports.each do |error_report|
|
53
|
+
attributes = {
|
54
|
+
'name' => error_report.test_name,
|
55
|
+
'classname' => error_report.test_suite,
|
56
|
+
}
|
57
|
+
attributes['lineno'] = error_report.test_line
|
58
|
+
|
59
|
+
testcase = testsuite.add_element('testcase', attributes)
|
60
|
+
add_xml_message_for(testcase, error_report)
|
61
|
+
rescue REXML::ParseException, RuntimeError => error
|
62
|
+
puts error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_xml_message_for(testcase, error_report)
|
67
|
+
failure = testcase.add_element('failure', 'type' => error_report.error_class, 'message' => truncate_message(error_report.to_s))
|
68
|
+
failure.add_text(REXML::CData.new(message_for(error_report)))
|
69
|
+
end
|
70
|
+
|
71
|
+
def truncate_message(message)
|
72
|
+
message.lines.first.chomp.gsub(/\e\[[^m]+m/, '')
|
73
|
+
end
|
74
|
+
|
75
|
+
def project_root_path_matcher
|
76
|
+
@project_root_path_matcher ||= %r{(?<=\s)#{Regexp.escape(Minitest::Queue.project_root)}/}
|
77
|
+
end
|
78
|
+
|
79
|
+
def message_for(error_report)
|
80
|
+
suite = error_report.test_suite
|
81
|
+
name = error_report.test_name
|
82
|
+
error = error_report.to_s
|
83
|
+
|
84
|
+
message_with_relative_paths = error.gsub(project_root_path_matcher, '')
|
85
|
+
"\nFailure:\n#{name}(#{suite}) [#{Minitest::Queue.relative_path(error_report.test_file)}]:\n#{message_with_relative_paths}\n"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def initialize(supervisor:, **options)
|
90
|
+
@supervisor = supervisor
|
91
|
+
@build = supervisor.build
|
9
92
|
super(options)
|
10
93
|
end
|
11
94
|
|
@@ -26,23 +109,46 @@ module Minitest
|
|
26
109
|
end
|
27
110
|
|
28
111
|
def report
|
112
|
+
if requeued_tests.to_a.any?
|
113
|
+
step("Requeued #{requeued_tests.size} tests")
|
114
|
+
requeued_tests.to_a.sort.each do |test_id, count|
|
115
|
+
puts yellow("REQUEUE")
|
116
|
+
puts "#{test_id} (requeued #{count} times)"
|
117
|
+
puts ""
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
29
121
|
puts aggregates
|
30
|
-
errors = error_reports
|
31
|
-
puts errors
|
32
122
|
|
33
|
-
|
34
|
-
puts
|
35
|
-
|
36
|
-
|
123
|
+
if supervisor.time_left.to_i <= 0
|
124
|
+
puts red("Timed out waiting for tests to be executed.")
|
125
|
+
|
126
|
+
remaining_tests = supervisor.test_ids
|
127
|
+
remaining_tests.first(10).each do |id|
|
128
|
+
puts " #{id}"
|
129
|
+
end
|
130
|
+
|
131
|
+
if remaining_tests.size > 10
|
132
|
+
puts " ..."
|
133
|
+
end
|
134
|
+
elsif supervisor.time_left_with_no_workers.to_i <= 0
|
135
|
+
puts red("All workers died.")
|
136
|
+
elsif supervisor.max_test_failed?
|
137
|
+
puts red("Encountered too many failed tests. Test run was ended early.")
|
37
138
|
end
|
38
139
|
|
140
|
+
puts
|
141
|
+
|
142
|
+
errors = error_reports
|
143
|
+
puts errors
|
144
|
+
|
39
145
|
build.worker_errors.to_a.sort.each do |worker_id, error|
|
40
146
|
puts red("Worker #{worker_id } crashed")
|
41
147
|
puts error
|
42
148
|
puts ""
|
43
149
|
end
|
44
150
|
|
45
|
-
|
151
|
+
success?
|
46
152
|
end
|
47
153
|
|
48
154
|
def success?
|
@@ -83,16 +189,22 @@ module Minitest
|
|
83
189
|
end
|
84
190
|
|
85
191
|
def write_failure_file(file)
|
86
|
-
File.
|
192
|
+
File.open(file, 'w') do |f|
|
193
|
+
JSON.dump(error_reports.map(&:to_h), f)
|
194
|
+
end
|
195
|
+
xml_file = File.join(File.dirname(file), "#{File.basename(file, File.extname(file))}.xml")
|
196
|
+
JUnitReporter.new(xml_file, error_reports).write
|
87
197
|
end
|
88
198
|
|
89
199
|
def write_flaky_tests_file(file)
|
90
|
-
File.
|
200
|
+
File.open(file, 'w') do |f|
|
201
|
+
JSON.dump(flaky_reports, f)
|
202
|
+
end
|
91
203
|
end
|
92
204
|
|
93
205
|
private
|
94
206
|
|
95
|
-
attr_reader :build
|
207
|
+
attr_reader :build, :supervisor
|
96
208
|
|
97
209
|
def aggregates
|
98
210
|
success = failures.zero? && errors.zero?
|
@@ -257,23 +257,16 @@ module Minitest
|
|
257
257
|
end
|
258
258
|
|
259
259
|
unless supervisor.exhausted?
|
260
|
-
reporter = BuildStatusReporter.new(
|
260
|
+
reporter = BuildStatusReporter.new(supervisor: supervisor)
|
261
261
|
reporter.report
|
262
262
|
reporter.write_failure_file(queue_config.failure_file) if queue_config.failure_file
|
263
263
|
reporter.write_flaky_tests_file(queue_config.export_flaky_tests_file) if queue_config.export_flaky_tests_file
|
264
264
|
|
265
|
-
|
266
|
-
|
267
|
-
if supervisor.max_test_failed?
|
268
|
-
puts('Encountered too many failed tests. Test run was ended early.')
|
269
|
-
abort!(msg)
|
270
|
-
else
|
271
|
-
abort!(msg)
|
272
|
-
end
|
265
|
+
abort!("#{supervisor.size} tests weren't run.")
|
273
266
|
end
|
274
267
|
end
|
275
268
|
|
276
|
-
reporter = BuildStatusReporter.new(
|
269
|
+
reporter = BuildStatusReporter.new(supervisor: supervisor)
|
277
270
|
reporter.write_failure_file(queue_config.failure_file) if queue_config.failure_file
|
278
271
|
reporter.write_flaky_tests_file(queue_config.export_flaky_tests_file) if queue_config.export_flaky_tests_file
|
279
272
|
reporter.report
|
@@ -330,7 +323,9 @@ module Minitest
|
|
330
323
|
warnings = build.pop_warnings.map do |type, attributes|
|
331
324
|
attributes.merge(type: type)
|
332
325
|
end.compact
|
333
|
-
File.
|
326
|
+
File.open(queue_config.warnings_file, 'w') do |f|
|
327
|
+
JSON.dump(warnings, f)
|
328
|
+
end
|
334
329
|
end
|
335
330
|
|
336
331
|
def run_tests_in_fork(queue)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ci-queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.66.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: logger
|
@@ -254,7 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
254
254
|
- !ruby/object:Gem::Version
|
255
255
|
version: '0'
|
256
256
|
requirements: []
|
257
|
-
rubygems_version: 3.6.
|
257
|
+
rubygems_version: 3.6.8
|
258
258
|
specification_version: 4
|
259
259
|
summary: Distribute tests over many workers using a queue
|
260
260
|
test_files: []
|