ci-queue 0.78.0 → 0.80.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/redis/base.rb +1 -1
- data/lib/ci/queue/redis/build_record.rb +1 -1
- data/lib/ci/queue/redis/grind_record.rb +1 -1
- data/lib/ci/queue/redis/key_shortener.rb +53 -0
- data/lib/ci/queue/redis.rb +1 -0
- data/lib/ci/queue/version.rb +1 -1
- data/lib/minitest/queue/build_status_reporter.rb +35 -1
- data/lib/rspec/queue/build_status_recorder.rb +8 -7
- data/lib/rspec/queue/error_report.rb +92 -0
- data/lib/rspec/queue/failure_formatter.rb +45 -0
- data/lib/rspec/queue.rb +88 -4
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b8f6b51411e322411c49898ac1a082148ca8b584769d8d3e52ddfd87b11cbb96
|
|
4
|
+
data.tar.gz: ee4e4beba8f6d2f97400631f7ac7b47ed8561c2af223e369ad9fcdbe88f4c6e3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d8f03c40ee904cc069300219ffd71262237de5d291eae1ab682289faae8108f90ec127cbd327b5ea1c0afe9a674e493670e3097922f8848bc3757ebbcb8c38f4
|
|
7
|
+
data.tar.gz: 8c9904ae453b65bd1be76f5cf3c49098f33a2cc1270699ec7569477465ff3c6369931da7e9a2eae789fc1949b07341148d2e8e73276d0362568bd65801fc6723
|
data/Gemfile.lock
CHANGED
data/lib/ci/queue/redis/base.rb
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'digest/md5'
|
|
3
|
+
|
|
4
|
+
module CI
|
|
5
|
+
module Queue
|
|
6
|
+
module Redis
|
|
7
|
+
module KeyShortener
|
|
8
|
+
# Suffix mapping for common key patterns
|
|
9
|
+
SUFFIX_ALIASES = {
|
|
10
|
+
'running' => 'r',
|
|
11
|
+
'processed' => 'p',
|
|
12
|
+
'queue' => 'q',
|
|
13
|
+
'owners' => 'o',
|
|
14
|
+
'error-reports' => 'e',
|
|
15
|
+
'requeues-count' => 'rc',
|
|
16
|
+
'assertions' => 'a',
|
|
17
|
+
'errors' => 'er',
|
|
18
|
+
'failures' => 'f',
|
|
19
|
+
'skips' => 's',
|
|
20
|
+
'requeues' => 'rq',
|
|
21
|
+
'total_time' => 't',
|
|
22
|
+
'test_failed_count' => 'fc',
|
|
23
|
+
'completed' => 'c',
|
|
24
|
+
'master-status' => 'm',
|
|
25
|
+
'created-at' => 'ca',
|
|
26
|
+
'workers' => 'w',
|
|
27
|
+
'worker' => 'w',
|
|
28
|
+
'warnings' => 'wn',
|
|
29
|
+
'worker-errors' => 'we',
|
|
30
|
+
'flaky-reports' => 'fl',
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
# We're transforming the key to a shorter format to minimize network traffic.
|
|
34
|
+
#
|
|
35
|
+
# Strategy:
|
|
36
|
+
# - Shorten prefix: 'b' instead of 'build'
|
|
37
|
+
# - Hash UUID: 8-char MD5 instead of 36-char UUID
|
|
38
|
+
# - Alias suffixes: single letters instead of full words
|
|
39
|
+
#
|
|
40
|
+
# Example:
|
|
41
|
+
# build:unit:019aef0e-c010-433e-b706-c658d3c16372:running (55 bytes)
|
|
42
|
+
# -> b:f03d3bef:r (13 bytes, 76% reduction)
|
|
43
|
+
|
|
44
|
+
def self.key(build_id, *args)
|
|
45
|
+
digest = Digest::MD5.hexdigest(build_id)[0..7]
|
|
46
|
+
shortened_args = args.map { |arg| SUFFIX_ALIASES[arg] || arg }
|
|
47
|
+
|
|
48
|
+
['b', digest, *shortened_args].join(':')
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/lib/ci/queue/redis.rb
CHANGED
data/lib/ci/queue/version.rb
CHANGED
|
@@ -153,7 +153,10 @@ module Minitest
|
|
|
153
153
|
puts
|
|
154
154
|
|
|
155
155
|
errors = error_reports
|
|
156
|
-
|
|
156
|
+
if errors.any?
|
|
157
|
+
pretty_print_summary(errors)
|
|
158
|
+
pretty_print_failures(errors)
|
|
159
|
+
end
|
|
157
160
|
|
|
158
161
|
build.worker_errors.to_a.sort.each do |worker_id, error|
|
|
159
162
|
puts red("Worker #{worker_id } crashed")
|
|
@@ -224,6 +227,37 @@ module Minitest
|
|
|
224
227
|
|
|
225
228
|
attr_reader :build, :supervisor
|
|
226
229
|
|
|
230
|
+
def pretty_print_summary(errors)
|
|
231
|
+
test_paths = errors.map(&:test_file).compact
|
|
232
|
+
return unless test_paths.any?
|
|
233
|
+
|
|
234
|
+
file_counts = test_paths.each_with_object(Hash.new(0)) { |path, counts| counts[path] += 1 }
|
|
235
|
+
|
|
236
|
+
puts "\n" + "=" * 80
|
|
237
|
+
puts "FAILED TESTS SUMMARY:"
|
|
238
|
+
puts "=" * 80
|
|
239
|
+
file_counts.sort_by { |path, _| path }.each do |path, count|
|
|
240
|
+
relative_path = Minitest::Queue.relative_path(path)
|
|
241
|
+
if count == 1
|
|
242
|
+
puts " #{relative_path}"
|
|
243
|
+
else
|
|
244
|
+
puts " #{relative_path} (#{count} failures)"
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
puts "=" * 80
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def pretty_print_failures(errors)
|
|
251
|
+
errors.each_with_index do |error, index|
|
|
252
|
+
puts "\n" + "-" * 80
|
|
253
|
+
puts "Error #{index + 1} of #{errors.size}"
|
|
254
|
+
puts "-" * 80
|
|
255
|
+
puts error
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
puts "=" * 80
|
|
259
|
+
end
|
|
260
|
+
|
|
227
261
|
def timed_out?
|
|
228
262
|
supervisor.time_left.to_i <= 0
|
|
229
263
|
end
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require 'rspec/queue/failure_formatter'
|
|
3
|
+
require 'rspec/queue/error_report'
|
|
4
|
+
|
|
2
5
|
module RSpec
|
|
3
6
|
module Queue
|
|
4
7
|
class BuildStatusRecorder
|
|
@@ -6,7 +9,9 @@ module RSpec
|
|
|
6
9
|
|
|
7
10
|
class << self
|
|
8
11
|
attr_accessor :build
|
|
12
|
+
attr_accessor :failure_formatter
|
|
9
13
|
end
|
|
14
|
+
self.failure_formatter = FailureFormatter
|
|
10
15
|
|
|
11
16
|
def initialize(*)
|
|
12
17
|
end
|
|
@@ -18,17 +23,13 @@ module RSpec
|
|
|
18
23
|
|
|
19
24
|
def example_failed(notification)
|
|
20
25
|
example = notification.example
|
|
21
|
-
build.record_error(example.id,
|
|
22
|
-
notification.fully_formatted(nil),
|
|
23
|
-
colorized_rerun_command(example),
|
|
24
|
-
].join("\n"))
|
|
26
|
+
build.record_error(example.id, dump(notification))
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
private
|
|
28
30
|
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
colorizer.wrap("# #{example.full_description}", RSpec.configuration.detail_color)
|
|
31
|
+
def dump(notification)
|
|
32
|
+
ErrorReport.new(self.class.failure_formatter.new(notification).to_h).dump
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def build
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module RSpec
|
|
3
|
+
module Queue
|
|
4
|
+
class ErrorReport
|
|
5
|
+
class << self
|
|
6
|
+
attr_accessor :coder
|
|
7
|
+
|
|
8
|
+
def load(payload)
|
|
9
|
+
new(coder.load(payload))
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Default to Marshal
|
|
14
|
+
self.coder = Marshal
|
|
15
|
+
|
|
16
|
+
# Try to use SnappyPack if available from consumer's bundle
|
|
17
|
+
begin
|
|
18
|
+
require 'snappy'
|
|
19
|
+
require 'msgpack'
|
|
20
|
+
require 'stringio'
|
|
21
|
+
|
|
22
|
+
module SnappyPack
|
|
23
|
+
extend self
|
|
24
|
+
|
|
25
|
+
MSGPACK = MessagePack::Factory.new
|
|
26
|
+
MSGPACK.register_type(0x00, Symbol)
|
|
27
|
+
|
|
28
|
+
def load(payload)
|
|
29
|
+
io = StringIO.new(Snappy.inflate(payload))
|
|
30
|
+
MSGPACK.unpacker(io).unpack
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def dump(object)
|
|
34
|
+
io = StringIO.new
|
|
35
|
+
packer = MSGPACK.packer(io)
|
|
36
|
+
packer.pack(object)
|
|
37
|
+
packer.flush
|
|
38
|
+
io.rewind
|
|
39
|
+
Snappy.deflate(io.string).force_encoding(Encoding::UTF_8)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
self.coder = SnappyPack
|
|
44
|
+
rescue LoadError
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def initialize(data)
|
|
48
|
+
@data = data
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def dump
|
|
52
|
+
self.class.coder.dump(@data)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_name
|
|
56
|
+
@data[:test_name]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def error_class
|
|
60
|
+
@data[:error_class]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_and_module_name
|
|
64
|
+
@data[:test_and_module_name]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_suite
|
|
68
|
+
@data[:test_suite]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_file
|
|
72
|
+
@data[:test_file]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_line
|
|
76
|
+
@data[:test_line]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_h
|
|
80
|
+
@data
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def to_s
|
|
84
|
+
output
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def output
|
|
88
|
+
@data[:output]
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'delegate'
|
|
3
|
+
require 'ci/queue/output_helpers'
|
|
4
|
+
|
|
5
|
+
module RSpec
|
|
6
|
+
module Queue
|
|
7
|
+
class FailureFormatter < SimpleDelegator
|
|
8
|
+
include ::CI::Queue::OutputHelpers
|
|
9
|
+
|
|
10
|
+
def initialize(notification)
|
|
11
|
+
@notification = notification
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_s
|
|
16
|
+
[
|
|
17
|
+
@notification.fully_formatted(nil),
|
|
18
|
+
colorized_rerun_command(@notification.example)
|
|
19
|
+
].join("\n")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_h
|
|
23
|
+
example = @notification.example
|
|
24
|
+
{
|
|
25
|
+
test_file: example.file_path,
|
|
26
|
+
test_line: example.metadata[:line_number],
|
|
27
|
+
test_and_module_name: example.id,
|
|
28
|
+
test_name: example.description,
|
|
29
|
+
test_suite: example.example_group.description,
|
|
30
|
+
error_class: @notification.exception.class.name,
|
|
31
|
+
output: to_s,
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
attr_reader :notification
|
|
38
|
+
|
|
39
|
+
def colorized_rerun_command(example, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
|
|
40
|
+
colorizer.wrap("rspec #{example.location_rerun_argument}", RSpec.configuration.failure_color) + " " +
|
|
41
|
+
colorizer.wrap("# #{example.full_description}", RSpec.configuration.detail_color)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/rspec/queue.rb
CHANGED
|
@@ -5,6 +5,7 @@ require 'rspec/core'
|
|
|
5
5
|
require 'ci/queue'
|
|
6
6
|
require 'rspec/queue/build_status_recorder'
|
|
7
7
|
require 'rspec/queue/order_recorder'
|
|
8
|
+
require 'rspec/queue/error_report'
|
|
8
9
|
|
|
9
10
|
module RSpec
|
|
10
11
|
module Queue
|
|
@@ -291,16 +292,56 @@ module RSpec
|
|
|
291
292
|
end
|
|
292
293
|
end
|
|
293
294
|
|
|
294
|
-
|
|
295
|
-
|
|
295
|
+
errors = supervisor.build.error_reports.sort_by(&:first).map do |_, error_data|
|
|
296
|
+
RSpec::Queue::ErrorReport.load(error_data)
|
|
297
|
+
end
|
|
296
298
|
if errors.empty?
|
|
297
299
|
step(green('No errors found'))
|
|
298
300
|
0
|
|
299
301
|
else
|
|
300
302
|
message = errors.size == 1 ? "1 error found" : "#{errors.size} errors found"
|
|
301
303
|
step(red(message), collapsed: false)
|
|
302
|
-
|
|
304
|
+
|
|
305
|
+
pretty_print_summary(errors)
|
|
306
|
+
pretty_print_failures(errors)
|
|
303
307
|
1
|
|
308
|
+
# Example output
|
|
309
|
+
#
|
|
310
|
+
# FAILED TESTS SUMMARY:
|
|
311
|
+
# =================================================================================
|
|
312
|
+
# ./spec/dummy_spec.rb
|
|
313
|
+
# ./spec/dummy_spec_2.rb (2 failures)
|
|
314
|
+
# ./spec/dummy_spec_3.rb (3 failures)
|
|
315
|
+
# =================================================================================
|
|
316
|
+
#
|
|
317
|
+
# --------------------------------------------------------------------------------
|
|
318
|
+
# Error 1 of 3
|
|
319
|
+
# --------------------------------------------------------------------------------
|
|
320
|
+
#
|
|
321
|
+
# Object doesn't work on first try
|
|
322
|
+
# Failure/Error: expect(1 + 1).to be == 42
|
|
323
|
+
#
|
|
324
|
+
# expected: == 42
|
|
325
|
+
# got: 2
|
|
326
|
+
#
|
|
327
|
+
# --- stacktrace will be here ---
|
|
328
|
+
# --- rerun command will be here ---
|
|
329
|
+
#
|
|
330
|
+
# --------------------------------------------------------------------------------
|
|
331
|
+
# Error 2 of 3
|
|
332
|
+
# --------------------------------------------------------------------------------
|
|
333
|
+
#
|
|
334
|
+
# Object doesn't work on first try
|
|
335
|
+
# Failure/Error: expect(1 + 1).to be == 42
|
|
336
|
+
#
|
|
337
|
+
# expected: == 42
|
|
338
|
+
# got: 2
|
|
339
|
+
#
|
|
340
|
+
# --- stacktrace will be here ---
|
|
341
|
+
# --- rerun command will be here ---
|
|
342
|
+
#
|
|
343
|
+
# ... etc
|
|
344
|
+
# =================================================================================
|
|
304
345
|
end
|
|
305
346
|
end
|
|
306
347
|
|
|
@@ -320,6 +361,38 @@ module RSpec
|
|
|
320
361
|
invalid_usage!('Missing --queue parameter') unless queue_url
|
|
321
362
|
invalid_usage!('Missing --build parameter') unless RSpec::Queue.config.build_id
|
|
322
363
|
end
|
|
364
|
+
|
|
365
|
+
private
|
|
366
|
+
|
|
367
|
+
def pretty_print_summary(errors)
|
|
368
|
+
test_paths = errors.map(&:test_file).compact
|
|
369
|
+
return unless test_paths.any?
|
|
370
|
+
|
|
371
|
+
file_counts = test_paths.each_with_object(Hash.new(0)) { |path, counts| counts[path] += 1 }
|
|
372
|
+
|
|
373
|
+
puts "\n" + "=" * 80
|
|
374
|
+
puts "FAILED TESTS SUMMARY:"
|
|
375
|
+
puts "=" * 80
|
|
376
|
+
file_counts.sort_by { |path, _| path }.each do |path, count|
|
|
377
|
+
if count == 1
|
|
378
|
+
puts " #{path}"
|
|
379
|
+
else
|
|
380
|
+
puts " #{path} (#{count} failures)"
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
puts "=" * 80
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def pretty_print_failures(errors)
|
|
387
|
+
errors.each_with_index do |error, index|
|
|
388
|
+
puts "\n" + "-" * 80
|
|
389
|
+
puts "Error #{index + 1} of #{errors.size}"
|
|
390
|
+
puts "-" * 80
|
|
391
|
+
puts error.to_s
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
puts "=" * 80
|
|
395
|
+
end
|
|
323
396
|
end
|
|
324
397
|
|
|
325
398
|
class QueueReporter < SimpleDelegator
|
|
@@ -362,7 +435,18 @@ module RSpec
|
|
|
362
435
|
invalid_usage!('Missing --queue parameter') unless queue_url
|
|
363
436
|
invalid_usage!('Missing --build parameter') unless RSpec::Queue.config.build_id
|
|
364
437
|
invalid_usage!('Missing --worker parameter') unless RSpec::Queue.config.worker_id
|
|
365
|
-
RSpec.
|
|
438
|
+
RSpec.configure do |config|
|
|
439
|
+
config.backtrace_exclusion_patterns = [
|
|
440
|
+
# Filter bundler paths
|
|
441
|
+
%r{/tmp/bundle/},
|
|
442
|
+
# RSpec internals
|
|
443
|
+
%r{/gems/rspec-},
|
|
444
|
+
# ci-queue and rspec-queue internals
|
|
445
|
+
%r{exe/rspec-queue},
|
|
446
|
+
%r{lib/ci/queue/},
|
|
447
|
+
%r{rspec/queue}
|
|
448
|
+
]
|
|
449
|
+
end
|
|
366
450
|
end
|
|
367
451
|
|
|
368
452
|
def run_specs(example_groups)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ci-queue
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.80.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jean Boussier
|
|
@@ -203,6 +203,7 @@ files:
|
|
|
203
203
|
- lib/ci/queue/redis/grind_record.rb
|
|
204
204
|
- lib/ci/queue/redis/grind_supervisor.rb
|
|
205
205
|
- lib/ci/queue/redis/heartbeat.lua
|
|
206
|
+
- lib/ci/queue/redis/key_shortener.rb
|
|
206
207
|
- lib/ci/queue/redis/monitor.rb
|
|
207
208
|
- lib/ci/queue/redis/release.lua
|
|
208
209
|
- lib/ci/queue/redis/requeue.lua
|
|
@@ -234,6 +235,8 @@ files:
|
|
|
234
235
|
- lib/minitest/reporters/statsd_reporter.rb
|
|
235
236
|
- lib/rspec/queue.rb
|
|
236
237
|
- lib/rspec/queue/build_status_recorder.rb
|
|
238
|
+
- lib/rspec/queue/error_report.rb
|
|
239
|
+
- lib/rspec/queue/failure_formatter.rb
|
|
237
240
|
- lib/rspec/queue/order_recorder.rb
|
|
238
241
|
homepage: https://github.com/Shopify/ci-queue
|
|
239
242
|
licenses:
|