minitest 5.20.0 → 5.24.1

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.
data/lib/minitest.rb CHANGED
@@ -1,15 +1,15 @@
1
1
  require "optparse"
2
- require "thread"
3
- require "mutex_m"
4
- require "minitest/parallel"
5
2
  require "stringio"
6
3
  require "etc"
7
4
 
5
+ require_relative "minitest/parallel"
6
+ require_relative "minitest/compress"
7
+
8
8
  ##
9
9
  # :include: README.rdoc
10
10
 
11
11
  module Minitest
12
- VERSION = "5.20.0" # :nodoc:
12
+ VERSION = "5.24.0" # :nodoc:
13
13
 
14
14
  @@installed_at_exit ||= false
15
15
  @@after_run = []
@@ -98,20 +98,19 @@ module Minitest
98
98
  @@after_run << block
99
99
  end
100
100
 
101
- def self.init_plugins options # :nodoc:
102
- self.extensions.each do |name|
103
- msg = "plugin_#{name}_init"
104
- send msg, options if self.respond_to? msg
105
- end
101
+ ##
102
+ # Register a plugin to be used. Does NOT require / load it.
103
+
104
+ def self.register_plugin name_or_mod
105
+ self.extensions << name_or_mod
106
+ nil
106
107
  end
107
108
 
108
109
  def self.load_plugins # :nodoc:
109
- return unless self.extensions.empty?
110
+ return unless defined? Gem
110
111
 
111
112
  seen = {}
112
113
 
113
- require "rubygems" unless defined? Gem
114
-
115
114
  Gem.find_files("minitest/*_plugin.rb").each do |plugin_path|
116
115
  name = File.basename plugin_path, "_plugin.rb"
117
116
 
@@ -123,67 +122,22 @@ module Minitest
123
122
  end
124
123
  end
125
124
 
126
- ##
127
- # This is the top-level run method. Everything starts from here. It
128
- # tells each Runnable sub-class to run, and each of those are
129
- # responsible for doing whatever they do.
130
- #
131
- # The overall structure of a run looks like this:
132
- #
133
- # Minitest.autorun
134
- # Minitest.run(args)
135
- # Minitest.__run(reporter, options)
136
- # Runnable.runnables.each
137
- # runnable.run(reporter, options)
138
- # self.runnable_methods.each
139
- # self.run_one_method(self, runnable_method, reporter)
140
- # Minitest.run_one_method(klass, runnable_method)
141
- # klass.new(runnable_method).run
142
-
143
- def self.run args = []
144
- self.load_plugins unless args.delete("--no-plugins") || ENV["MT_NO_PLUGINS"]
145
-
146
- options = process_args args
147
-
148
- Minitest.seed = options[:seed]
149
- srand Minitest.seed
150
-
151
- reporter = CompositeReporter.new
152
- reporter << SummaryReporter.new(options[:io], options)
153
- reporter << ProgressReporter.new(options[:io], options)
154
-
155
- self.reporter = reporter # this makes it available to plugins
156
- self.init_plugins options
157
- self.reporter = nil # runnables shouldn't depend on the reporter, ever
158
-
159
- self.parallel_executor.start if parallel_executor.respond_to?(:start)
160
- reporter.start
161
- begin
162
- __run reporter, options
163
- rescue Interrupt
164
- warn "Interrupted. Exiting..."
125
+ def self.init_plugins options # :nodoc:
126
+ self.extensions.each do |mod_or_meth|
127
+ case mod_or_meth
128
+ when Symbol, String then
129
+ name = mod_or_meth
130
+ msg = "plugin_#{name}_init"
131
+ next unless self.respond_to? msg
132
+ send msg, options
133
+ when Module then
134
+ recv = mod_or_meth
135
+ next unless recv.respond_to? :minitest_plugin_init
136
+ recv.minitest_plugin_init options
137
+ else
138
+ raise ArgumentError, "plugin is %p, but it must be a symbol, string or module" % [mod_or_meth]
139
+ end
165
140
  end
166
- self.parallel_executor.shutdown
167
- reporter.report
168
-
169
- reporter.passed?
170
- end
171
-
172
- ##
173
- # Internal run method. Responsible for telling all Runnable
174
- # sub-classes to run.
175
-
176
- def self.__run reporter, options
177
- suites = Runnable.runnables.shuffle
178
- parallel, serial = suites.partition { |s| s.test_order == :parallel }
179
-
180
- # If we run the parallel tests before the serial tests, the parallel tests
181
- # could run in parallel with the serial tests. This would be bad because
182
- # the serial tests won't lock around Reporter#record. Run the serial tests
183
- # first, so that after they complete, the parallel tests will lock when
184
- # recording results.
185
- serial.map { |suite| suite.run reporter, options } +
186
- parallel.map { |suite| suite.run reporter, options }
187
141
  end
188
142
 
189
143
  def self.process_args args = [] # :nodoc:
@@ -212,6 +166,10 @@ module Minitest
212
166
  options[:verbose] = true
213
167
  end
214
168
 
169
+ opts.on "-q", "--quiet", "Quiet. Show no progress processing files." do
170
+ options[:quiet] = true
171
+ end
172
+
215
173
  opts.on "--show-skips", "Show skipped at the end of run." do
216
174
  options[:show_skips] = true
217
175
  end
@@ -228,13 +186,37 @@ module Minitest
228
186
  options[:skip] = s.chars.to_a
229
187
  end
230
188
 
189
+ ruby27plus = ::Warning.respond_to?(:[]=)
190
+
191
+ opts.on "-W[error]", String, "Turn Ruby warnings into errors" do |s|
192
+ options[:Werror] = true
193
+ case s
194
+ when "error", "all", nil then
195
+ require "minitest/error_on_warning"
196
+ $VERBOSE = true
197
+ ::Warning[:deprecated] = true if ruby27plus
198
+ else
199
+ ::Warning[s.to_sym] = true if ruby27plus # check validity of category
200
+ end
201
+ end
202
+
231
203
  unless extensions.empty?
232
204
  opts.separator ""
233
205
  opts.separator "Known extensions: #{extensions.join(", ")}"
234
206
 
235
- extensions.each do |meth|
236
- msg = "plugin_#{meth}_options"
237
- send msg, opts, options if self.respond_to?(msg)
207
+ extensions.each do |mod_or_meth|
208
+ case mod_or_meth
209
+ when Symbol, String then
210
+ meth = mod_or_meth
211
+ msg = "plugin_#{meth}_options"
212
+ send msg, opts, options if respond_to?(msg)
213
+ when Module
214
+ recv = mod_or_meth
215
+ next unless recv.respond_to? :minitest_plugin_options
216
+ recv.minitest_plugin_options opts, options
217
+ else
218
+ raise ArgumentError, "plugin is %p, but it must be a symbol, string or module" % [mod_or_meth]
219
+ end
238
220
  end
239
221
  end
240
222
 
@@ -264,6 +246,93 @@ module Minitest
264
246
  options
265
247
  end
266
248
 
249
+ ##
250
+ # This is the top-level run method. Everything starts from here. It
251
+ # tells each Runnable sub-class to run, and each of those are
252
+ # responsible for doing whatever they do.
253
+ #
254
+ # The overall structure of a run looks like this:
255
+ #
256
+ # Minitest.autorun
257
+ # Minitest.run(args)
258
+ # Minitest.load_plugins
259
+ # Minitest.process_args
260
+ # Minitest.init_plugins
261
+ # Minitest.__run(reporter, options)
262
+ # Runnable.runnables.each
263
+ # runnable_klass.run(reporter, options)
264
+ # self.runnable_methods.each
265
+ # self.run_one_method(self, runnable_method, reporter)
266
+ # Minitest.run_one_method(klass, runnable_method)
267
+ # klass.new(runnable_method).run
268
+
269
+ def self.run args = []
270
+ self.load_plugins unless args.delete("--no-plugins") || ENV["MT_NO_PLUGINS"]
271
+
272
+ options = process_args args
273
+
274
+ Minitest.seed = options[:seed]
275
+ srand Minitest.seed
276
+
277
+ reporter = CompositeReporter.new
278
+ reporter << SummaryReporter.new(options[:io], options)
279
+ reporter << ProgressReporter.new(options[:io], options) unless options[:quiet]
280
+
281
+ self.reporter = reporter # this makes it available to plugins
282
+ self.init_plugins options
283
+ self.reporter = nil # runnables shouldn't depend on the reporter, ever
284
+
285
+ self.parallel_executor.start if parallel_executor.respond_to?(:start)
286
+ reporter.start
287
+ begin
288
+ __run reporter, options
289
+ rescue Interrupt
290
+ warn "Interrupted. Exiting..."
291
+ end
292
+ self.parallel_executor.shutdown
293
+
294
+ # might have been removed/replaced during init_plugins:
295
+ summary = reporter.reporters.grep(SummaryReporter).first
296
+
297
+ reporter.report
298
+
299
+ return empty_run! options if summary && summary.count == 0
300
+ reporter.passed?
301
+ end
302
+
303
+ def self.empty_run! options # :nodoc:
304
+ filter = options[:filter]
305
+ return true unless filter # no filter, but nothing ran == success
306
+
307
+ warn "Nothing ran for filter: %s" % [filter]
308
+
309
+ require "did_you_mean" # soft dependency, punt if it doesn't load
310
+
311
+ ms = Runnable.runnables.flat_map(&:runnable_methods)
312
+ cs = DidYouMean::SpellChecker.new(dictionary: ms).correct filter
313
+
314
+ warn DidYouMean::Formatter.message_for cs unless cs.empty?
315
+ rescue LoadError
316
+ # do nothing
317
+ end
318
+
319
+ ##
320
+ # Internal run method. Responsible for telling all Runnable
321
+ # sub-classes to run.
322
+
323
+ def self.__run reporter, options
324
+ suites = Runnable.runnables.shuffle
325
+ parallel, serial = suites.partition { |s| s.test_order == :parallel }
326
+
327
+ # If we run the parallel tests before the serial tests, the parallel tests
328
+ # could run in parallel with the serial tests. This would be bad because
329
+ # the serial tests won't lock around Reporter#record. Run the serial tests
330
+ # first, so that after they complete, the parallel tests will lock when
331
+ # recording results.
332
+ serial.map { |suite| suite.run reporter, options } +
333
+ parallel.map { |suite| suite.run reporter, options }
334
+ end
335
+
267
336
  def self.filter_backtrace bt # :nodoc:
268
337
  result = backtrace_filter.filter bt
269
338
  result = bt.dup if result.empty?
@@ -334,25 +403,15 @@ module Minitest
334
403
  # reporter to record.
335
404
 
336
405
  def self.run reporter, options = {}
337
- filtered_methods = if options[:filter]
338
- filter = options[:filter]
339
- filter = Regexp.new $1 if filter.is_a?(String) && filter =~ %r%/(.*)/%
406
+ pos = options[:filter]
407
+ neg = options[:exclude]
340
408
 
341
- self.runnable_methods.find_all { |m|
342
- filter === m || filter === "#{self}##{m}"
343
- }
344
- else
345
- self.runnable_methods
346
- end
409
+ pos = Regexp.new $1 if pos.is_a?(String) && pos =~ %r%/(.*)/%
410
+ neg = Regexp.new $1 if neg.is_a?(String) && neg =~ %r%/(.*)/%
347
411
 
348
- if options[:exclude]
349
- exclude = options[:exclude]
350
- exclude = Regexp.new $1 if exclude =~ %r%/(.*)/%
351
-
352
- filtered_methods.delete_if { |m|
353
- exclude === m || exclude === "#{self}##{m}"
354
- }
355
- end
412
+ filtered_methods = self.runnable_methods
413
+ .select { |m| !pos || pos === m || pos === "#{self}##{m}" }
414
+ .reject { |m| neg && (neg === m || neg === "#{self}##{m}") }
356
415
 
357
416
  return if filtered_methods.empty?
358
417
 
@@ -525,12 +584,14 @@ module Minitest
525
584
  not self.failure
526
585
  end
527
586
 
587
+ BASE_DIR = "#{Dir.pwd}/" # :nodoc:
588
+
528
589
  ##
529
590
  # The location identifier of this test. Depends on a method
530
591
  # existing called class_name.
531
592
 
532
593
  def location
533
- loc = " [#{self.failure.location}]" unless passed? or error?
594
+ loc = " [#{self.failure.location.delete_prefix BASE_DIR}]" unless passed? or error?
534
595
  "#{self.class_name}##{self.name}#{loc}"
535
596
  end
536
597
 
@@ -619,7 +680,10 @@ module Minitest
619
680
  # you want. Go nuts.
620
681
 
621
682
  class AbstractReporter
622
- include Mutex_m
683
+
684
+ def initialize # :nodoc:
685
+ @mutex = Mutex.new
686
+ end
623
687
 
624
688
  ##
625
689
  # Starts reporting on the run.
@@ -655,6 +719,10 @@ module Minitest
655
719
  def passed?
656
720
  true
657
721
  end
722
+
723
+ def synchronize(&block) # :nodoc:
724
+ @mutex.synchronize(&block)
725
+ end
658
726
  end
659
727
 
660
728
  class Reporter < AbstractReporter # :nodoc:
@@ -758,6 +826,11 @@ module Minitest
758
826
 
759
827
  attr_accessor :errors
760
828
 
829
+ ##
830
+ # Total number of tests that warned.
831
+
832
+ attr_accessor :warnings
833
+
761
834
  ##
762
835
  # Total number of tests that where skipped.
763
836
 
@@ -773,6 +846,7 @@ module Minitest
773
846
  self.total_time = nil
774
847
  self.failures = nil
775
848
  self.errors = nil
849
+ self.warnings = nil
776
850
  self.skips = nil
777
851
  end
778
852
 
@@ -801,6 +875,7 @@ module Minitest
801
875
  self.total_time = Minitest.clock_time - start_time
802
876
  self.failures = aggregate[Assertion].size
803
877
  self.errors = aggregate[UnexpectedError].size
878
+ self.warnings = aggregate[UnexpectedWarning].size
804
879
  self.skips = aggregate[Skip].size
805
880
  end
806
881
  end
@@ -828,7 +903,7 @@ module Minitest
828
903
  io.puts "# Running:"
829
904
  io.puts
830
905
 
831
- self.sync = io.respond_to? :"sync=" # stupid emacs
906
+ self.sync = io.respond_to? :"sync="
832
907
  self.old_sync, io.sync = io.sync, true if self.sync
833
908
  end
834
909
 
@@ -876,6 +951,8 @@ module Minitest
876
951
  results.any?(&:skipped?) unless
877
952
  options[:verbose] or options[:show_skips] or ENV["MT_NO_SKIP_MSG"]
878
953
 
954
+ extra.prepend ", %d warnings" % [warnings] if options[:Werror]
955
+
879
956
  "%d runs, %d assertions, %d failures, %d errors, %d skips%s" %
880
957
  [count, assertions, failures, errors, skips, extra]
881
958
  end
@@ -936,6 +1013,8 @@ module Minitest
936
1013
  # Represents run failures.
937
1014
 
938
1015
  class Assertion < Exception
1016
+ RE = /in [`'](?:[^']+[#.])?(?:assert|refute|flunk|pass|fail|raise|must|wont)/ # :nodoc:
1017
+
939
1018
  def error # :nodoc:
940
1019
  self
941
1020
  end
@@ -944,12 +1023,11 @@ module Minitest
944
1023
  # Where was this run before an assertion was raised?
945
1024
 
946
1025
  def location
947
- last_before_assertion = ""
948
- self.backtrace.reverse_each do |s|
949
- break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/
950
- last_before_assertion = s
951
- end
952
- last_before_assertion.sub(/:in .*$/, "")
1026
+ bt = Minitest.filter_backtrace self.backtrace
1027
+ idx = bt.rindex { |s| s.match? RE } || -1 # fall back to first item
1028
+ loc = bt[idx+1] || bt.last || "unknown:-1"
1029
+
1030
+ loc.sub(/:in .*$/, "")
953
1031
  end
954
1032
 
955
1033
  def result_code # :nodoc:
@@ -974,11 +1052,21 @@ module Minitest
974
1052
  # Assertion wrapping an unexpected error that was raised during a run.
975
1053
 
976
1054
  class UnexpectedError < Assertion
1055
+ include Minitest::Compress
1056
+
977
1057
  # TODO: figure out how to use `cause` instead
978
1058
  attr_accessor :error # :nodoc:
979
1059
 
980
1060
  def initialize error # :nodoc:
981
1061
  super "Unexpected exception"
1062
+
1063
+ if SystemStackError === error then
1064
+ bt = error.backtrace
1065
+ new_bt = compress bt
1066
+ error = error.exception "#{bt.size} -> #{new_bt.size}"
1067
+ error.set_backtrace new_bt
1068
+ end
1069
+
982
1070
  self.error = error
983
1071
  end
984
1072
 
@@ -986,8 +1074,11 @@ module Minitest
986
1074
  self.error.backtrace
987
1075
  end
988
1076
 
1077
+ BASE_RE = %r%#{Dir.pwd}/% # :nodoc:
1078
+
989
1079
  def message # :nodoc:
990
- bt = Minitest.filter_backtrace(self.backtrace).join "\n "
1080
+ bt = Minitest.filter_backtrace(self.backtrace).join("\n ")
1081
+ .gsub(BASE_RE, "")
991
1082
  "#{self.error.class}: #{self.error.message}\n #{bt}"
992
1083
  end
993
1084
 
@@ -996,6 +1087,15 @@ module Minitest
996
1087
  end
997
1088
  end
998
1089
 
1090
+ ##
1091
+ # Assertion raised on warning when running in -Werror mode.
1092
+
1093
+ class UnexpectedWarning < Assertion
1094
+ def result_label # :nodoc:
1095
+ "Warning"
1096
+ end
1097
+ end
1098
+
999
1099
  ##
1000
1100
  # Provides a simple set of guards that you can use in your tests
1001
1101
  # to skip execution if it is not applicable. These methods are
@@ -1069,7 +1169,13 @@ module Minitest
1069
1169
 
1070
1170
  class BacktraceFilter
1071
1171
 
1072
- MT_RE = %r%lib/minitest% #:nodoc:
1172
+ MT_RE = %r%lib/minitest|internal:warning% #:nodoc:
1173
+
1174
+ attr_accessor :regexp
1175
+
1176
+ def initialize regexp = MT_RE
1177
+ self.regexp = regexp
1178
+ end
1073
1179
 
1074
1180
  ##
1075
1181
  # Filter +bt+ to something useful. Returns the whole thing if
@@ -1080,9 +1186,9 @@ module Minitest
1080
1186
 
1081
1187
  return bt.dup if $DEBUG || ENV["MT_DEBUG"]
1082
1188
 
1083
- new_bt = bt.take_while { |line| line !~ MT_RE }
1084
- new_bt = bt.select { |line| line !~ MT_RE } if new_bt.empty?
1085
- new_bt = bt.dup if new_bt.empty?
1189
+ new_bt = bt.take_while { |line| line.to_s !~ regexp }
1190
+ new_bt = bt.select { |line| line.to_s !~ regexp } if new_bt.empty?
1191
+ new_bt = bt.dup if new_bt.empty?
1086
1192
 
1087
1193
  new_bt
1088
1194
  end
@@ -8,24 +8,36 @@ class Minitest::Test
8
8
  end
9
9
 
10
10
  def with_empty_backtrace_filter
11
- original = Minitest.backtrace_filter
12
-
13
- obj = Minitest::BacktraceFilter.new
14
- def obj.filter _bt
15
- []
11
+ with_backtrace_filter Minitest::BacktraceFilter.new %r%.% do
12
+ yield
16
13
  end
14
+ end
15
+
16
+ def with_backtrace_filter filter
17
+ original = Minitest.backtrace_filter
17
18
 
18
19
  Minitest::Test.io_lock.synchronize do # try not to trounce in parallel
19
20
  begin
20
- Minitest.backtrace_filter = obj
21
+ Minitest.backtrace_filter = filter
21
22
  yield
22
23
  ensure
23
24
  Minitest.backtrace_filter = original
24
25
  end
25
26
  end
26
27
  end
27
- end
28
28
 
29
+ def error_on_warn?
30
+ defined?(Minitest::ErrorOnWarning)
31
+ end
32
+
33
+ def assert_deprecation re = /DEPRECATED/
34
+ assert_output "", re do
35
+ yield
36
+ end
37
+ rescue Minitest::UnexpectedWarning => e # raised if -Werror was used
38
+ assert_match re, e.message
39
+ end
40
+ end
29
41
 
30
42
  class FakeNamedTest < Minitest::Test
31
43
  @@count = 0
@@ -55,7 +67,7 @@ class MetaMetaMetaTestCase < Minitest::Test
55
67
  def run_tu_with_fresh_reporter flags = %w[--seed 42]
56
68
  options = Minitest.process_args flags
57
69
 
58
- @output = StringIO.new("".encode('UTF-8'))
70
+ @output = StringIO.new("".encode(Encoding::UTF_8))
59
71
 
60
72
  self.reporter = Minitest::CompositeReporter.new
61
73
  reporter << Minitest::SummaryReporter.new(@output, options)
@@ -105,15 +117,20 @@ class MetaMetaMetaTestCase < Minitest::Test
105
117
  output.gsub!(/0x[A-Fa-f0-9]+/, "0xXXX")
106
118
  output.gsub!(/ +$/, "")
107
119
 
120
+ file = ->(s) { s.start_with?("/") ? "FULLFILE" : "FILE" }
121
+
108
122
  if windows? then
109
123
  output.gsub!(/\[(?:[A-Za-z]:)?[^\]:]+:\d+\]/, "[FILE:LINE]")
110
- output.gsub!(/^(\s+)(?:[A-Za-z]:)?[^:]+:\d+:in/, '\1FILE:LINE:in')
124
+ output.gsub!(/^(\s+)(?:[A-Za-z]:)?[^:]+:\d+:in [`']/, '\1FILE:LINE:in \'')
111
125
  else
112
- output.gsub!(/\[[^\]:]+:\d+\]/, "[FILE:LINE]")
113
- output.gsub!(/^(\s+)[^:]+:\d+:in/, '\1FILE:LINE:in')
126
+ output.gsub!(/\[([^\]:]+):\d+\]/) { "[#{file[$1]}:LINE]" }
127
+ output.gsub!(/^(\s+)([^:]+):\d+:in [`']/) { "#{$1}#{file[$2]}:LINE:in '" }
114
128
  end
115
129
 
116
- output.gsub!(/( at )[^:]+:\d+/, '\1[FILE:LINE]')
130
+ output.gsub!(/in [`']block in (?:([^']+)[#.])?/, "in 'block in")
131
+ output.gsub!(/in [`'](?:([^']+)[#.])?/, "in '")
132
+
133
+ output.gsub!(/( at )[^:]+:\d+/) { "#{$1}[#{file[$2]}:LINE]" } # eval?
117
134
 
118
135
  output
119
136
  end