ci-queue 0.77.0 → 0.79.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
  SHA256:
3
- metadata.gz: d179aa863885754053bf872092be72aab9f8fc521e01b4a06998fd4f52d4abcb
4
- data.tar.gz: 464c80baf9b169ef4dc7fddd91bb893074ff3b15d89e430f2447b3fce8fc8a82
3
+ metadata.gz: 3a6bc964bea9e748549dc7e57a2ec4c0207e080fa5d644fac6d75165b88b44cf
4
+ data.tar.gz: 22acfa15e29f811514aea54861ae9f4c422e11d42eadbed7241b030f5a3ed79f
5
5
  SHA512:
6
- metadata.gz: 93c43b61bbcf9ac0f92dcbb257e5d58605e6bc6d047c1c4db8455750db69ec95033e4bedeabdc615d887804d2174e74cea832b0589511d9d0546f4593d6c9c94
7
- data.tar.gz: e708efad7772916ed5aab6fd34070a10be745509689cc31896d104ba5ac69c3f81960bc1cf8c76b5fe88fda152c472ed3c6368d20d6ebdc6ca6a0c5d53d91089
6
+ metadata.gz: 02b7be5eea0c4f50eacd9caa19bb64f7ab52720deb92897573efad680bf1130aa717f09302b3c6573bd0c7e5eed592ba3acb601cfeb8a2eabbdc9cb422826d2e
7
+ data.tar.gz: dbdca360f7eba2da549559f982c73175302dc5e92f414972f39c8a6fb77de9a95d3dfc783bae4be3a63dcefa2844c69e5fae3842f828dff42d66a2b9058aae64
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ci-queue (0.77.0)
4
+ ci-queue (0.79.0)
5
5
  logger
6
6
 
7
7
  GEM
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CI
4
4
  module Queue
5
- VERSION = '0.77.0'
5
+ VERSION = '0.79.0'
6
6
  DEV_SCRIPTS_ROOT = ::File.expand_path('../../../../../redis', __FILE__)
7
7
  RELEASE_SCRIPTS_ROOT = ::File.expand_path('../redis', __FILE__)
8
8
  end
@@ -153,7 +153,10 @@ module Minitest
153
153
  puts
154
154
 
155
155
  errors = error_reports
156
- puts errors
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
@@ -193,6 +193,7 @@ module Minitest
193
193
  puts reopen_previous_step
194
194
  puts red("The failing test does not exist.")
195
195
  File.write('log/test_order.log', "")
196
+ File.write('log/bisect_test_details.log', "")
196
197
  exit! 1
197
198
  end
198
199
 
@@ -200,6 +201,7 @@ module Minitest
200
201
  puts reopen_previous_step
201
202
  puts red("The test fail when ran alone, no need to bisect.")
202
203
  File.write('log/test_order.log', queue_config.failing_test)
204
+ File.write('log/bisect_test_details.log', "")
203
205
  exit! 0
204
206
  end
205
207
 
@@ -218,6 +220,7 @@ module Minitest
218
220
  if queue.suspects_left == 0
219
221
  step(yellow("The failing test was the first test in the test order so there is nothing to bisect."))
220
222
  File.write('log/test_order.log', "")
223
+ File.write('log/bisect_test_details.log', "")
221
224
  exit! 1
222
225
  end
223
226
 
@@ -226,6 +229,7 @@ module Minitest
226
229
  if run_tests_in_fork(failing_order)
227
230
  step(yellow("The bisection was inconclusive, there might not be any leaky test here."))
228
231
  File.write('log/test_order.log', "")
232
+ File.write('log/bisect_test_details.log', "")
229
233
  exit! 1
230
234
  else
231
235
  step(green('The following command should reproduce the leak on your machine:'), collapsed: false)
@@ -238,6 +242,16 @@ module Minitest
238
242
  puts
239
243
 
240
244
  File.write('log/test_order.log', failing_order.to_a.map(&:id).join("\n"))
245
+
246
+ bisect_test_details = failing_order.to_a.map do |test|
247
+ source_location = test.source_location
248
+ file_path = source_location&.first || 'unknown'
249
+ line_number = source_location&.last || -1
250
+ "#{test.id} #{file_path}:#{line_number}"
251
+ end
252
+
253
+ File.write('log/bisect_test_details.log', bisect_test_details.join("\n"))
254
+
241
255
  exit! 0
242
256
  end
243
257
  end
@@ -257,6 +257,12 @@ module Minitest
257
257
  Minitest.queue.flaky?(self)
258
258
  end
259
259
 
260
+ def source_location
261
+ @runnable.instance_method(@method_name).source_location
262
+ rescue NameError, NoMethodError
263
+ nil
264
+ end
265
+
260
266
  private
261
267
 
262
268
  def current_timestamp
@@ -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 colorized_rerun_command(example, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
30
- colorizer.wrap("rspec #{example.location_rerun_argument}", RSpec.configuration.failure_color) + " " +
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
- # TODO: better reporting
295
- errors = supervisor.build.error_reports.sort_by(&:first).map(&:last)
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
- puts errors
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.configuration.backtrace_formatter.filter_gem('ci-queue')
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.77.0
4
+ version: 0.79.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
@@ -234,6 +234,8 @@ files:
234
234
  - lib/minitest/reporters/statsd_reporter.rb
235
235
  - lib/rspec/queue.rb
236
236
  - lib/rspec/queue/build_status_recorder.rb
237
+ - lib/rspec/queue/error_report.rb
238
+ - lib/rspec/queue/failure_formatter.rb
237
239
  - lib/rspec/queue/order_recorder.rb
238
240
  homepage: https://github.com/Shopify/ci-queue
239
241
  licenses:
@@ -254,7 +256,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
254
256
  - !ruby/object:Gem::Version
255
257
  version: '0'
256
258
  requirements: []
257
- rubygems_version: 3.7.1
259
+ rubygems_version: 3.7.2
258
260
  specification_version: 4
259
261
  summary: Distribute tests over many workers using a queue
260
262
  test_files: []