ci-queue 0.19.0 → 0.20.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 436b1f0bca2725e6f532cbd4a4df9a880e6b1dd2e11a80b9587178d213fe6137
4
- data.tar.gz: 93fe3b4055e7c99e6c61af1f106e63157e79dd2fe3717d5b1cfc74e096195e9a
3
+ metadata.gz: 4c7ce73cd1dd2d350b7e9b5293f92380d0bdb61a21aea1924e587fe897a31755
4
+ data.tar.gz: 70fb38ffa65e591ee0f86b3990dbc07bf587776f9e6d05937209efb50565b467
5
5
  SHA512:
6
- metadata.gz: 2bc328cd567173a875bc8273a812d2ad48ea32bad0eb0f7b0a586b279a80c4eaf08eede41a15e98d71929776dc1a5d59e7fee94b844b55a1924b3f1aa0be4272
7
- data.tar.gz: fab60979f729dd77fbf8661d8e1b644deb7e9c126a5ea27a97537e0fb960416b71c69b2b01b4123e6f19216b6a764a136079412875686c93f949a6323ebf0a63
6
+ metadata.gz: b3affe2bb8f9d7f1b4b25acd42257e671c981ee212f5c792c69af917cfdc4394de0d0e83c5b94b155d71ef60004c66a01115a3aaf2b19f872dbe81b63cdc7191
7
+ data.tar.gz: b8493fa643cb3405a42879b320a8714d192c7dfed6be62efd39220ee9d951c10a074ee4a69204784eb9119faa523c50f17788cb286032baf447cca3bbb636e84
data/README.md CHANGED
@@ -51,7 +51,9 @@ The runner also comes with a tool to investigate leaky tests:
51
51
  minitest-queue --queue path/to/test_order.log --failing-test 'SomeTest#test_something' bisect -Itest test/**/*_test.rb
52
52
  ```
53
53
 
54
- ### RSpec
54
+ ### RSpec [DEPRECATED]
55
+
56
+ The rspec-queue runner is deprecated. The minitest-queue runner continues to be supported and is actively being improved. At Shopify, we strongly recommend that new projects set up their test suite using Minitest rather than RSpec.
55
57
 
56
58
  Assuming you use one of the supported CI providers, the command can be as simple as:
57
59
 
@@ -67,4 +69,4 @@ rspec-queue --queue redis://example.com --timeout 600 --report
67
69
 
68
70
  #### Limitations
69
71
 
70
- Because of how `ci-queue` execute the examples, `before(:all)` and `after(:all)` hooks are not supported. `rspec-queue` will explicitly reject them.
72
+ Because of how `ci-queue` executes the examples, `before(:all)` and `after(:all)` hooks are not supported. `rspec-queue` will explicitly reject them.
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CI
4
4
  module Queue
5
- VERSION = '0.19.0'
5
+ VERSION = '0.20.4'
6
6
  DEV_SCRIPTS_ROOT = ::File.expand_path('../../../../../redis', __FILE__)
7
7
  RELEASE_SCRIPTS_ROOT = ::File.expand_path('../redis', __FILE__)
8
8
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'shellwords'
2
3
  require 'minitest'
3
4
  require 'minitest/reporters'
4
5
 
@@ -102,6 +103,47 @@ module Minitest
102
103
  end
103
104
 
104
105
  module Queue
106
+ attr_writer :run_command_formatter, :project_root
107
+
108
+ def run_command_formatter
109
+ @run_command_formatter ||= if defined?(Rails) && defined?(Rails::TestUnitRailtie)
110
+ RAILS_RUN_COMMAND_FORMATTER
111
+ else
112
+ DEFAULT_RUN_COMMAND_FORMATTER
113
+ end
114
+ end
115
+
116
+ DEFAULT_RUN_COMMAND_FORMATTER = lambda do |runnable|
117
+ filename = Minitest::Queue.relative_path(runnable.source_location[0])
118
+ identifier = "#{runnable.klass}##{runnable.name}"
119
+ ['bundle', 'exec', 'ruby', '-Ilib:test', filename, '-n', identifier]
120
+ end
121
+
122
+ RAILS_RUN_COMMAND_FORMATTER = lambda do |runnable|
123
+ filename = Minitest::Queue.relative_path(runnable.source_location[0])
124
+ lineno = runnable.source_location[1]
125
+ ['bin/rails', 'test', "#{filename}:#{lineno}"]
126
+ end
127
+
128
+ def run_command_for_runnable(runnable)
129
+ command = run_command_formatter.call(runnable)
130
+ if command.is_a?(Array)
131
+ Shellwords.join(command)
132
+ else
133
+ command
134
+ end
135
+ end
136
+
137
+ def self.project_root
138
+ @project_root ||= Dir.pwd
139
+ end
140
+
141
+ def self.relative_path(path, root: project_root)
142
+ Pathname(path).relative_path_from(Pathname(root)).to_s
143
+ rescue ArgumentError
144
+ path
145
+ end
146
+
105
147
  class SingleExample
106
148
 
107
149
  def initialize(runnable, method_name)
@@ -58,6 +58,10 @@ module Minitest
58
58
  @data[:test_and_module_name]
59
59
  end
60
60
 
61
+ def test_suite
62
+ @data[:test_suite]
63
+ end
64
+
61
65
  def test_file
62
66
  @data[:test_file]
63
67
  end
@@ -27,6 +27,7 @@ module Minitest
27
27
  test_line: test_line,
28
28
  test_and_module_name: "#{test.klass}##{test.name}",
29
29
  test_name: test.name,
30
+ test_suite: test.klass,
30
31
  error_class: test.failure.exception.class.name,
31
32
  output: to_s,
32
33
  }
@@ -1,101 +1,128 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'minitest/reporters'
3
- require 'builder'
4
+ require 'rexml/document'
4
5
  require 'fileutils'
5
6
 
6
7
  module Minitest
7
8
  module Queue
8
9
  class JUnitReporter < Minitest::Reporters::BaseReporter
9
- class XmlMarkup < ::Builder::XmlMarkup
10
- def trunc!(txt)
11
- txt.sub(/\n.*/m, '...')
12
- end
13
- end
14
-
15
10
  def initialize(report_path = 'log/junit.xml', options = {})
16
11
  super({})
17
12
  @report_path = File.absolute_path(report_path)
18
13
  @base_path = options[:base_path] || Dir.pwd
19
14
  end
20
15
 
21
- def report
22
- super
23
-
16
+ def generate_document
24
17
  suites = tests.group_by { |test| test.klass }
25
18
 
26
- xml = Builder::XmlMarkup.new(indent: 2)
27
- xml.instruct!
28
- xml.testsuites do
29
- suites.each do |suite, tests|
30
- add_tests_to(xml, suite, tests)
31
- end
19
+ doc = REXML::Document.new(nil, {
20
+ :prologue_quote => :quote,
21
+ :attribute_quote => :quote,
22
+ })
23
+ doc << REXML::XMLDecl.new('1.1', 'utf-8')
24
+
25
+ testsuites = doc.add_element('testsuites')
26
+ suites.each do |suite, tests|
27
+ add_tests_to(testsuites, suite, tests)
32
28
  end
29
+ doc
30
+ end
31
+
32
+ def format_document(doc, io)
33
+ formatter = REXML::Formatters::Pretty.new
34
+ formatter.write(doc, io)
35
+ io << "\n"
36
+ end
37
+
38
+ def report
39
+ super
40
+
33
41
  FileUtils.mkdir_p(File.dirname(@report_path))
34
- File.open(@report_path, 'w+') { |file| file << xml.target! }
42
+ File.open(@report_path, 'w+') do |file|
43
+ format_document(generate_document, file)
44
+ end
35
45
  end
36
46
 
37
47
  private
38
48
 
39
- def add_tests_to(xml, suite, tests)
49
+ def add_tests_to(testsuites, suite, tests)
40
50
  suite_result = analyze_suite(tests)
41
- file_path = Pathname.new(tests.first.source_location.first)
42
- base_path = Pathname.new(@base_path)
43
- relative_path = file_path.relative_path_from(base_path)
44
-
45
- xml.testsuite(name: suite, filepath: relative_path,
46
- skipped: suite_result[:skip_count], failures: suite_result[:fail_count],
47
- errors: suite_result[:error_count], tests: suite_result[:test_count],
48
- assertions: suite_result[:assertion_count], time: suite_result[:time]) do
49
- tests.each do |test|
50
- lineno = test.source_location.last
51
- xml.testcase(name: test.name, lineno: lineno, classname: suite, assertions: test.assertions,
52
- time: test.time, flaky_test: test.flaked?) do
53
- xml << xml_message_for(test) unless test.passed?
54
- end
55
- end
51
+ relative_path = location_for_runnable(tests.first) || '<unknown>'
52
+
53
+ testsuite = testsuites.add_element(
54
+ 'testsuite',
55
+ 'name' => suite,
56
+ 'filepath' => relative_path,
57
+ 'skipped' => suite_result[:skip_count],
58
+ 'failures' => suite_result[:fail_count],
59
+ 'errors' => suite_result[:error_count],
60
+ 'tests' => suite_result[:test_count],
61
+ 'assertions' => suite_result[:assertion_count],
62
+ 'time' => suite_result[:time],
63
+ )
64
+
65
+ tests.each do |test|
66
+ lineno = tests.first.source_location.last
67
+ attributes = {
68
+ 'name' => test.name,
69
+ 'classname' => suite,
70
+ 'assertions' => test.assertions,
71
+ 'time' => test.time,
72
+ 'flaky_test' => test.flaked?,
73
+ 'run-command' => Minitest.run_command_for_runnable(test),
74
+ }
75
+ attributes['lineno'] = lineno if lineno != -1
76
+
77
+ testcase = testsuite.add_element('testcase', attributes)
78
+ add_xml_message_for(testcase, test) unless test.passed?
56
79
  end
57
80
  end
58
81
 
59
- def xml_message_for(test)
60
- xml = XmlMarkup.new(indent: 2, margin: 2)
82
+ def add_xml_message_for(testcase, test)
61
83
  failure = test.failure
62
-
63
84
  if test.skipped? && !test.flaked?
64
- xml.skipped(type: failure.error.class.name)
85
+ testcase.add_element('skipped', 'type' => failure.error.class.name)
65
86
  elsif test.error?
66
- xml.error(type: failure.error.class.name, message: xml.trunc!(failure.message)) do
67
- xml.text!(message_for(test))
68
- end
87
+ error = testcase.add_element('error', 'type' => failure.error.class.name, 'message' => truncate_message(failure.message))
88
+ error.add_text(REXML::CData.new(message_for(test)))
69
89
  elsif failure
70
- xml.failure(type: failure.error.class.name, message: xml.trunc!(failure.message)) do
71
- xml.text!(message_for(test))
72
- end
90
+ failure = testcase.add_element('failure', 'type' => failure.error.class.name, 'message' => truncate_message(failure.message))
91
+ failure.add_text(REXML::CData.new(message_for(test)))
73
92
  end
74
93
  end
75
94
 
95
+ def truncate_message(message)
96
+ message.lines.first.chomp.gsub(/\e\[[^m]+m/, '')
97
+ end
98
+
99
+ def project_root_path_matcher
100
+ @project_root_path_matcher ||= %r{(?<=\s)#{Regexp.escape(Minitest::Queue.project_root)}/}
101
+ end
102
+
76
103
  def message_for(test)
77
104
  suite = test.klass
78
105
  name = test.name
79
106
  error = test.failure
80
107
 
108
+ message_with_relative_paths = error.message.gsub(project_root_path_matcher, '')
81
109
  if test.passed?
82
110
  nil
83
111
  elsif test.skipped?
84
- "Skipped:\n#{name}(#{suite}) [#{location(error)}]:\n#{error.message}\n"
112
+ "\nSkipped:\n#{name}(#{suite}) [#{location_for_runnable(test)}]:\n#{message_with_relative_paths}\n"
85
113
  elsif test.failure
86
- "Failure:\n#{name}(#{suite}) [#{location(error)}]:\n#{error.message}\n"
114
+ "\nFailure:\n#{name}(#{suite}) [#{location_for_runnable(test)}]:\n#{message_with_relative_paths}\n"
87
115
  elsif test.error?
88
- "Error:\n#{name}(#{suite}):\n#{error.message}"
116
+ "\nError:\n#{name}(#{suite}) [#{location_for_runnable(test)}]:\n#{message_with_relative_paths}\n"
89
117
  end
90
118
  end
91
119
 
92
- def location(exception)
93
- last_before_assertion = ''
94
- exception.backtrace.reverse_each do |s|
95
- break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/
96
- last_before_assertion = s
120
+ def location_for_runnable(runnable)
121
+ if runnable.source_location.first == 'unknown'
122
+ nil
123
+ else
124
+ Minitest::Queue.relative_path(runnable.source_location.first)
97
125
  end
98
- last_before_assertion.sub(/:in .*$/, '')
99
126
  end
100
127
 
101
128
  def analyze_suite(tests)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ci-queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.0
4
+ version: 0.20.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-27 00:00:00.000000000 Z
11
+ date: 2020-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler