ci-queue 0.64.0 → 0.65.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: af2b3f83ebe968c7f35a010a76a4676f13c6a0e7bf49bfd4f6d6a25ff04270f8
4
- data.tar.gz: f647f458fd19dc6117c5ccd5b085b505c0b4f75c68f4ce0e0c0402bd4ca0d909
3
+ metadata.gz: 0bd235c5c9a18d43b30d5fc614e960e297b60fbaa3391a47db9eaa07cfa1bb4f
4
+ data.tar.gz: 9f61f75c48456aaac0abb2b25e3cb200a22f00baf5c5612828bd61b54a59ed46
5
5
  SHA512:
6
- metadata.gz: 227d2721922584d7130c7aef4eb88b42404ec62265e43f35e996e96aaf602904b0403d07eee436b2f6e228ed5755de46c994e1c457a975c7567ec53be46db988
7
- data.tar.gz: 7b46d8842c805c1b36e26e82d10eab61183e8db562c1a0ac3714cb99006ce657f8b46f8812df029db98bc6b8fb7c4f37d8cda29b4ff8361409dcd2fef633baa4
6
+ metadata.gz: 640842ac7e07b54184cab3a9881ec5f0401f3c128b6d8bf0011d576650e94c4f85864664fcc51b3fac6d6028b4fd95a7943364aa397e060acc3ddc56647662f6
7
+ data.tar.gz: 4d8aa80210424460498cb29bee6fb7d18db2f1a9c453550c98e4133f34862d7a9882df5c60bf0b0f88e245210af7b6be943cf811c20d3e0f5a53d5ab2bf8e8dd
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ci-queue (0.64.0)
4
+ ci-queue (0.65.0)
5
5
  logger
6
6
 
7
7
  GEM
@@ -25,7 +25,14 @@ module CI
25
25
 
26
26
  def load_flaky_tests(path)
27
27
  return [] unless path
28
- ::File.readlines(path).map(&:chomp).to_set
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
@@ -133,11 +133,15 @@ module CI
133
133
  redis.zcard(key('running'))
134
134
  end
135
135
 
136
- def to_a
136
+ def test_ids
137
137
  redis.multi do |transaction|
138
138
  transaction.lrange(key('queue'), 0, -1)
139
139
  transaction.zrange(key('running'), 0, -1)
140
- end.flatten.reverse.map { |k| index.fetch(k) }
140
+ end.flatten
141
+ end
142
+
143
+ def to_a
144
+ test_ids.reverse.map { |k| index.fetch(k) }
141
145
  end
142
146
 
143
147
  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?
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CI
4
4
  module Queue
5
- VERSION = '0.64.0'
5
+ VERSION = '0.65.0'
6
6
  DEV_SCRIPTS_ROOT = ::File.expand_path('../../../../../redis', __FILE__)
7
7
  RELEASE_SCRIPTS_ROOT = ::File.expand_path('../redis', __FILE__)
8
8
  end
@@ -4,8 +4,91 @@ module Minitest
4
4
  class BuildStatusReporter < Minitest::Reporters::BaseReporter
5
5
  include ::CI::Queue::OutputHelpers
6
6
 
7
- def initialize(build:, **options)
8
- @build = build
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
- requeued_tests.to_a.sort.each do |test_id, count|
34
- puts yellow("REQUEUE")
35
- puts "#{test_id} (requeued #{count} times)"
36
- puts ""
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
- errors.empty?
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.write(file, error_reports.map(&:to_h).to_json)
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.write(file, flaky_reports.to_json)
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?
@@ -54,6 +54,10 @@ module Minitest
54
54
  @data[:test_name]
55
55
  end
56
56
 
57
+ def error_class
58
+ @data[:error_class]
59
+ end
60
+
57
61
  def test_and_module_name
58
62
  @data[:test_and_module_name]
59
63
  end
@@ -257,23 +257,16 @@ module Minitest
257
257
  end
258
258
 
259
259
  unless supervisor.exhausted?
260
- reporter = BuildStatusReporter.new(build: supervisor.build)
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
- msg = "#{supervisor.size} tests weren't run."
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(build: supervisor.build)
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.write(queue_config.warnings_file, warnings.to_json)
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.64.0
4
+ version: 0.65.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-03-12 00:00:00.000000000 Z
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.5
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: []