minitest 5.20.0 → 5.25.1

Sign up to get free protection for your applications and to get access to all the features.
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.25.1" # :nodoc:
13
13
 
14
14
  @@installed_at_exit ||= false
15
15
  @@after_run = []
@@ -67,9 +67,8 @@ module Minitest
67
67
  # Registers Minitest to run at process exit
68
68
 
69
69
  def self.autorun
70
- if Object.const_defined?(:Warning) && Warning.respond_to?(:[]=)
71
- Warning[:deprecated] = true
72
- end
70
+ Warning[:deprecated] = true if
71
+ Object.const_defined?(:Warning) && Warning.respond_to?(:[]=)
73
72
 
74
73
  at_exit {
75
74
  next if $! and not ($!.kind_of? SystemExit and $!.success?)
@@ -98,20 +97,19 @@ module Minitest
98
97
  @@after_run << block
99
98
  end
100
99
 
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
100
+ ##
101
+ # Register a plugin to be used. Does NOT require / load it.
102
+
103
+ def self.register_plugin name_or_mod
104
+ self.extensions << name_or_mod
105
+ nil
106
106
  end
107
107
 
108
108
  def self.load_plugins # :nodoc:
109
- return unless self.extensions.empty?
109
+ return unless defined? Gem
110
110
 
111
111
  seen = {}
112
112
 
113
- require "rubygems" unless defined? Gem
114
-
115
113
  Gem.find_files("minitest/*_plugin.rb").each do |plugin_path|
116
114
  name = File.basename plugin_path, "_plugin.rb"
117
115
 
@@ -123,72 +121,27 @@ module Minitest
123
121
  end
124
122
  end
125
123
 
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..."
124
+ def self.init_plugins options # :nodoc:
125
+ self.extensions.each do |mod_or_meth|
126
+ case mod_or_meth
127
+ when Symbol, String then
128
+ name = mod_or_meth
129
+ msg = "plugin_#{name}_init"
130
+ next unless self.respond_to? msg
131
+ send msg, options
132
+ when Module then
133
+ recv = mod_or_meth
134
+ next unless recv.respond_to? :minitest_plugin_init
135
+ recv.minitest_plugin_init options
136
+ else
137
+ raise ArgumentError, "plugin is %p, but it must be a symbol, string or module" % [mod_or_meth]
138
+ end
165
139
  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
140
  end
188
141
 
189
142
  def self.process_args args = [] # :nodoc:
190
143
  options = {
191
- :io => $stdout,
144
+ :io => $stdout,
192
145
  }
193
146
  orig_args = args.dup
194
147
 
@@ -212,6 +165,10 @@ module Minitest
212
165
  options[:verbose] = true
213
166
  end
214
167
 
168
+ opts.on "-q", "--quiet", "Quiet. Show no progress processing files." do
169
+ options[:quiet] = true
170
+ end
171
+
215
172
  opts.on "--show-skips", "Show skipped at the end of run." do
216
173
  options[:show_skips] = true
217
174
  end
@@ -228,13 +185,37 @@ module Minitest
228
185
  options[:skip] = s.chars.to_a
229
186
  end
230
187
 
188
+ ruby27plus = ::Warning.respond_to? :[]=
189
+
190
+ opts.on "-W[error]", String, "Turn Ruby warnings into errors" do |s|
191
+ options[:Werror] = true
192
+ case s
193
+ when "error", "all", nil then
194
+ require "minitest/error_on_warning"
195
+ $VERBOSE = true
196
+ ::Warning[:deprecated] = true if ruby27plus
197
+ else
198
+ ::Warning[s.to_sym] = true if ruby27plus # check validity of category
199
+ end
200
+ end
201
+
231
202
  unless extensions.empty?
232
203
  opts.separator ""
233
- opts.separator "Known extensions: #{extensions.join(", ")}"
234
-
235
- extensions.each do |meth|
236
- msg = "plugin_#{meth}_options"
237
- send msg, opts, options if self.respond_to?(msg)
204
+ opts.separator "Known extensions: #{extensions.join ", "}"
205
+
206
+ extensions.each do |mod_or_meth|
207
+ case mod_or_meth
208
+ when Symbol, String then
209
+ meth = mod_or_meth
210
+ msg = "plugin_#{meth}_options"
211
+ send msg, opts, options if respond_to? msg
212
+ when Module
213
+ recv = mod_or_meth
214
+ next unless recv.respond_to? :minitest_plugin_options
215
+ recv.minitest_plugin_options opts, options
216
+ else
217
+ raise ArgumentError, "plugin is %p, but it must be a symbol, string or module" % [mod_or_meth]
218
+ end
238
219
  end
239
220
  end
240
221
 
@@ -258,12 +239,99 @@ module Minitest
258
239
  end
259
240
 
260
241
  options[:args] = orig_args.map { |s|
261
- s =~ /[\s|&<>$()]/ ? s.inspect : s
242
+ s.match?(/[\s|&<>$()]/) ? s.inspect : s
262
243
  }.join " "
263
244
 
264
245
  options
265
246
  end
266
247
 
248
+ ##
249
+ # This is the top-level run method. Everything starts from here. It
250
+ # tells each Runnable sub-class to run, and each of those are
251
+ # responsible for doing whatever they do.
252
+ #
253
+ # The overall structure of a run looks like this:
254
+ #
255
+ # Minitest.autorun
256
+ # Minitest.run(args)
257
+ # Minitest.load_plugins
258
+ # Minitest.process_args
259
+ # Minitest.init_plugins
260
+ # Minitest.__run(reporter, options)
261
+ # Runnable.runnables.each
262
+ # runnable_klass.run(reporter, options)
263
+ # self.runnable_methods.each
264
+ # self.run_one_method(self, runnable_method, reporter)
265
+ # Minitest.run_one_method(klass, runnable_method)
266
+ # klass.new(runnable_method).run
267
+
268
+ def self.run args = []
269
+ self.load_plugins unless args.delete("--no-plugins") || ENV["MT_NO_PLUGINS"]
270
+
271
+ options = process_args args
272
+
273
+ Minitest.seed = options[:seed]
274
+ srand Minitest.seed
275
+
276
+ reporter = CompositeReporter.new
277
+ reporter << SummaryReporter.new(options[:io], options)
278
+ reporter << ProgressReporter.new(options[:io], options) unless options[:quiet]
279
+
280
+ self.reporter = reporter # this makes it available to plugins
281
+ self.init_plugins options
282
+ self.reporter = nil # runnables shouldn't depend on the reporter, ever
283
+
284
+ self.parallel_executor.start if parallel_executor.respond_to? :start
285
+ reporter.start
286
+ begin
287
+ __run reporter, options
288
+ rescue Interrupt
289
+ warn "Interrupted. Exiting..."
290
+ end
291
+ self.parallel_executor.shutdown
292
+
293
+ # might have been removed/replaced during init_plugins:
294
+ summary = reporter.reporters.grep(SummaryReporter).first
295
+
296
+ reporter.report
297
+
298
+ return empty_run! options if summary && summary.count == 0
299
+ reporter.passed?
300
+ end
301
+
302
+ def self.empty_run! options # :nodoc:
303
+ filter = options[:filter]
304
+ return true unless filter # no filter, but nothing ran == success
305
+
306
+ warn "Nothing ran for filter: %s" % [filter]
307
+
308
+ require "did_you_mean" # soft dependency, punt if it doesn't load
309
+
310
+ ms = Runnable.runnables.flat_map(&:runnable_methods)
311
+ cs = DidYouMean::SpellChecker.new(dictionary: ms).correct filter
312
+
313
+ warn DidYouMean::Formatter.message_for cs unless cs.empty?
314
+ rescue LoadError
315
+ # do nothing
316
+ end
317
+
318
+ ##
319
+ # Internal run method. Responsible for telling all Runnable
320
+ # sub-classes to run.
321
+
322
+ def self.__run reporter, options
323
+ suites = Runnable.runnables.shuffle
324
+ parallel, serial = suites.partition { |s| s.test_order == :parallel }
325
+
326
+ # If we run the parallel tests before the serial tests, the parallel tests
327
+ # could run in parallel with the serial tests. This would be bad because
328
+ # the serial tests won't lock around Reporter#record. Run the serial tests
329
+ # first, so that after they complete, the parallel tests will lock when
330
+ # recording results.
331
+ serial.map { |suite| suite.run reporter, options } +
332
+ parallel.map { |suite| suite.run reporter, options }
333
+ end
334
+
267
335
  def self.filter_backtrace bt # :nodoc:
268
336
  result = backtrace_filter.filter bt
269
337
  result = bt.dup if result.empty?
@@ -334,30 +402,34 @@ module Minitest
334
402
  # reporter to record.
335
403
 
336
404
  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%/(.*)/%
340
-
341
- self.runnable_methods.find_all { |m|
342
- filter === m || filter === "#{self}##{m}"
343
- }
344
- else
345
- self.runnable_methods
346
- end
405
+ pos = options[:filter]
406
+ neg = options[:exclude]
347
407
 
348
- if options[:exclude]
349
- exclude = options[:exclude]
350
- exclude = Regexp.new $1 if exclude =~ %r%/(.*)/%
408
+ pos = Regexp.new $1 if pos.kind_of?(String) && pos =~ %r%/(.*)/%
409
+ neg = Regexp.new $1 if neg.kind_of?(String) && neg =~ %r%/(.*)/%
351
410
 
352
- filtered_methods.delete_if { |m|
353
- exclude === m || exclude === "#{self}##{m}"
354
- }
355
- end
411
+ filtered_methods = self.runnable_methods
412
+ .select { |m| !pos || pos === m || pos === "#{self}##{m}" }
413
+ .reject { |m| neg && (neg === m || neg === "#{self}##{m}") }
356
414
 
357
415
  return if filtered_methods.empty?
358
416
 
417
+ t0 = name = nil
418
+
419
+ @_info_handler = lambda do
420
+ unless reporter.passed? then
421
+ warn "Current results:"
422
+ warn reporter.reporters.grep(SummaryReporter).first
423
+ end
424
+
425
+ warn "Current: %s#%s %.2fs" % [self, name, Minitest.clock_time - t0]
426
+ end
427
+
359
428
  with_info_handler reporter do
360
429
  filtered_methods.each do |method_name|
430
+ name = method_name
431
+ t0 = Minitest.clock_time
432
+
361
433
  run_one_method self, method_name, reporter
362
434
  end
363
435
  end
@@ -383,16 +455,7 @@ module Minitest
383
455
  end
384
456
 
385
457
  def self.with_info_handler reporter, &block # :nodoc:
386
- handler = lambda do
387
- unless reporter.passed? then
388
- warn "Current results:"
389
- warn ""
390
- warn reporter.reporters.first
391
- warn ""
392
- end
393
- end
394
-
395
- on_signal ::Minitest.info_signal, handler, &block
458
+ on_signal ::Minitest.info_signal, @_info_handler, &block
396
459
  end
397
460
 
398
461
  SIGNALS = Signal.list # :nodoc:
@@ -430,7 +493,7 @@ module Minitest
430
493
  def marshal_dump # :nodoc:
431
494
  unless @@marshal_dump_warned then
432
495
  warn ["Minitest::Runnable#marshal_dump is deprecated.",
433
- "You might be violating internals. From", caller.first].join " "
496
+ "You might be violating internals. From", caller(1..1).first].join " "
434
497
  @@marshal_dump_warned = true
435
498
  end
436
499
 
@@ -525,12 +588,14 @@ module Minitest
525
588
  not self.failure
526
589
  end
527
590
 
591
+ BASE_DIR = "#{Dir.pwd}/" # :nodoc:
592
+
528
593
  ##
529
594
  # The location identifier of this test. Depends on a method
530
595
  # existing called class_name.
531
596
 
532
597
  def location
533
- loc = " [#{self.failure.location}]" unless passed? or error?
598
+ loc = " [#{self.failure.location.delete_prefix BASE_DIR}]" unless passed? or error?
534
599
  "#{self.class_name}##{self.name}#{loc}"
535
600
  end
536
601
 
@@ -556,7 +621,7 @@ module Minitest
556
621
  # Did this run error?
557
622
 
558
623
  def error?
559
- self.failures.any? { |f| UnexpectedError === f }
624
+ self.failures.any? UnexpectedError
560
625
  end
561
626
  end
562
627
 
@@ -619,7 +684,10 @@ module Minitest
619
684
  # you want. Go nuts.
620
685
 
621
686
  class AbstractReporter
622
- include Mutex_m
687
+
688
+ def initialize # :nodoc:
689
+ @mutex = Mutex.new
690
+ end
623
691
 
624
692
  ##
625
693
  # Starts reporting on the run.
@@ -655,6 +723,10 @@ module Minitest
655
723
  def passed?
656
724
  true
657
725
  end
726
+
727
+ def synchronize &block # :nodoc:
728
+ @mutex.synchronize(&block)
729
+ end
658
730
  end
659
731
 
660
732
  class Reporter < AbstractReporter # :nodoc:
@@ -684,11 +756,11 @@ module Minitest
684
756
  # own.
685
757
 
686
758
  class ProgressReporter < Reporter
687
- def prerecord klass, name #:nodoc:
688
- if options[:verbose] then
689
- io.print "%s#%s = " % [klass.name, name]
690
- io.flush
691
- end
759
+ def prerecord klass, name # :nodoc:
760
+ return unless options[:verbose]
761
+
762
+ io.print "%s#%s = " % [klass.name, name]
763
+ io.flush
692
764
  end
693
765
 
694
766
  def record result # :nodoc:
@@ -758,6 +830,11 @@ module Minitest
758
830
 
759
831
  attr_accessor :errors
760
832
 
833
+ ##
834
+ # Total number of tests that warned.
835
+
836
+ attr_accessor :warnings
837
+
761
838
  ##
762
839
  # Total number of tests that where skipped.
763
840
 
@@ -773,6 +850,7 @@ module Minitest
773
850
  self.total_time = nil
774
851
  self.failures = nil
775
852
  self.errors = nil
853
+ self.warnings = nil
776
854
  self.skips = nil
777
855
  end
778
856
 
@@ -801,6 +879,7 @@ module Minitest
801
879
  self.total_time = Minitest.clock_time - start_time
802
880
  self.failures = aggregate[Assertion].size
803
881
  self.errors = aggregate[UnexpectedError].size
882
+ self.warnings = aggregate[UnexpectedWarning].size
804
883
  self.skips = aggregate[Skip].size
805
884
  end
806
885
  end
@@ -815,10 +894,8 @@ module Minitest
815
894
  # own.
816
895
 
817
896
  class SummaryReporter < StatisticsReporter
818
- # :stopdoc:
819
- attr_accessor :sync
820
- attr_accessor :old_sync
821
- # :startdoc:
897
+ attr_accessor :sync # :nodoc:
898
+ attr_accessor :old_sync # :nodoc:
822
899
 
823
900
  def start # :nodoc:
824
901
  super
@@ -828,7 +905,7 @@ module Minitest
828
905
  io.puts "# Running:"
829
906
  io.puts
830
907
 
831
- self.sync = io.respond_to? :"sync=" # stupid emacs
908
+ self.sync = io.respond_to? :"sync="
832
909
  self.old_sync, io.sync = io.sync, true if self.sync
833
910
  end
834
911
 
@@ -866,18 +943,22 @@ module Minitest
866
943
  end
867
944
 
868
945
  def to_s # :nodoc:
869
- aggregated_results(StringIO.new(''.b)).string
946
+ aggregated_results(StringIO.new("".b)).string
870
947
  end
871
948
 
872
949
  def summary # :nodoc:
873
- extra = ""
950
+ extra = []
874
951
 
875
- extra = "\n\nYou have skipped tests. Run with --verbose for details." if
952
+ extra << ", %d warnings" % [warnings] if options[:Werror]
953
+
954
+ extra << "\n\nYou have skipped tests. Run with --verbose for details." if
876
955
  results.any?(&:skipped?) unless
877
- options[:verbose] or options[:show_skips] or ENV["MT_NO_SKIP_MSG"]
956
+ options[:verbose] or
957
+ options[:show_skips] or
958
+ ENV["MT_NO_SKIP_MSG"]
878
959
 
879
960
  "%d runs, %d assertions, %d failures, %d errors, %d skips%s" %
880
- [count, assertions, failures, errors, skips, extra]
961
+ [count, assertions, failures, errors, skips, extra.join]
881
962
  end
882
963
  end
883
964
 
@@ -936,6 +1017,8 @@ module Minitest
936
1017
  # Represents run failures.
937
1018
 
938
1019
  class Assertion < Exception
1020
+ RE = /in [`'](?:[^']+[#.])?(?:assert|refute|flunk|pass|fail|raise|must|wont)/ # :nodoc:
1021
+
939
1022
  def error # :nodoc:
940
1023
  self
941
1024
  end
@@ -944,12 +1027,11 @@ module Minitest
944
1027
  # Where was this run before an assertion was raised?
945
1028
 
946
1029
  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 .*$/, "")
1030
+ bt = Minitest.filter_backtrace self.backtrace
1031
+ idx = bt.rindex { |s| s.match? RE } || -1 # fall back to first item
1032
+ loc = bt[idx+1] || bt.last || "unknown:-1"
1033
+
1034
+ loc.sub(/:in .*$/, "")
953
1035
  end
954
1036
 
955
1037
  def result_code # :nodoc:
@@ -974,11 +1056,21 @@ module Minitest
974
1056
  # Assertion wrapping an unexpected error that was raised during a run.
975
1057
 
976
1058
  class UnexpectedError < Assertion
1059
+ include Minitest::Compress
1060
+
977
1061
  # TODO: figure out how to use `cause` instead
978
1062
  attr_accessor :error # :nodoc:
979
1063
 
980
1064
  def initialize error # :nodoc:
981
1065
  super "Unexpected exception"
1066
+
1067
+ if SystemStackError === error then
1068
+ bt = error.backtrace
1069
+ new_bt = compress bt
1070
+ error = error.exception "#{bt.size} -> #{new_bt.size}"
1071
+ error.set_backtrace new_bt
1072
+ end
1073
+
982
1074
  self.error = error
983
1075
  end
984
1076
 
@@ -986,8 +1078,11 @@ module Minitest
986
1078
  self.error.backtrace
987
1079
  end
988
1080
 
1081
+ BASE_RE = %r%#{Dir.pwd}/% # :nodoc:
1082
+
989
1083
  def message # :nodoc:
990
- bt = Minitest.filter_backtrace(self.backtrace).join "\n "
1084
+ bt = Minitest.filter_backtrace(self.backtrace).join("\n ")
1085
+ .gsub(BASE_RE, "")
991
1086
  "#{self.error.class}: #{self.error.message}\n #{bt}"
992
1087
  end
993
1088
 
@@ -996,6 +1091,15 @@ module Minitest
996
1091
  end
997
1092
  end
998
1093
 
1094
+ ##
1095
+ # Assertion raised on warning when running in -Werror mode.
1096
+
1097
+ class UnexpectedWarning < Assertion
1098
+ def result_label # :nodoc:
1099
+ "Warning"
1100
+ end
1101
+ end
1102
+
999
1103
  ##
1000
1104
  # Provides a simple set of guards that you can use in your tests
1001
1105
  # to skip execution if it is not applicable. These methods are
@@ -1025,7 +1129,7 @@ module Minitest
1025
1129
 
1026
1130
  def maglev? platform = defined?(RUBY_ENGINE) && RUBY_ENGINE
1027
1131
  where = Minitest.filter_backtrace(caller).first
1028
- where = where.split(/:in /, 2).first # clean up noise
1132
+ where = where.split(":in ", 2).first # clean up noise
1029
1133
  warn "DEPRECATED: `maglev?` called from #{where}. This will fail in Minitest 6."
1030
1134
  "maglev" == platform
1031
1135
  end
@@ -1034,14 +1138,14 @@ module Minitest
1034
1138
  # Is this running on mri?
1035
1139
 
1036
1140
  def mri? platform = RUBY_DESCRIPTION
1037
- /^ruby/ =~ platform
1141
+ platform.start_with? "ruby"
1038
1142
  end
1039
1143
 
1040
1144
  ##
1041
1145
  # Is this running on macOS?
1042
1146
 
1043
1147
  def osx? platform = RUBY_PLATFORM
1044
- /darwin/ =~ platform
1148
+ platform.include? "darwin"
1045
1149
  end
1046
1150
 
1047
1151
  ##
@@ -1049,7 +1153,7 @@ module Minitest
1049
1153
 
1050
1154
  def rubinius? platform = defined?(RUBY_ENGINE) && RUBY_ENGINE
1051
1155
  where = Minitest.filter_backtrace(caller).first
1052
- where = where.split(/:in /, 2).first # clean up noise
1156
+ where = where.split(":in ", 2).first # clean up noise
1053
1157
  warn "DEPRECATED: `rubinius?` called from #{where}. This will fail in Minitest 6."
1054
1158
  "rbx" == platform
1055
1159
  end
@@ -1058,7 +1162,7 @@ module Minitest
1058
1162
  # Is this running on windows?
1059
1163
 
1060
1164
  def windows? platform = RUBY_PLATFORM
1061
- /mswin|mingw/ =~ platform
1165
+ /mswin|mingw/.match? platform
1062
1166
  end
1063
1167
  end
1064
1168
 
@@ -1069,7 +1173,16 @@ module Minitest
1069
1173
 
1070
1174
  class BacktraceFilter
1071
1175
 
1072
- MT_RE = %r%lib/minitest% #:nodoc:
1176
+ MT_RE = %r%lib/minitest|internal:warning% # :nodoc:
1177
+
1178
+ ##
1179
+ # The regular expression to use to filter backtraces. Defaults to +MT_RE+.
1180
+
1181
+ attr_accessor :regexp
1182
+
1183
+ def initialize regexp = MT_RE # :nodoc:
1184
+ self.regexp = regexp
1185
+ end
1073
1186
 
1074
1187
  ##
1075
1188
  # Filter +bt+ to something useful. Returns the whole thing if
@@ -1080,9 +1193,9 @@ module Minitest
1080
1193
 
1081
1194
  return bt.dup if $DEBUG || ENV["MT_DEBUG"]
1082
1195
 
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?
1196
+ new_bt = bt.take_while { |line| !regexp.match? line.to_s }
1197
+ new_bt = bt.select { |line| !regexp.match? line.to_s } if new_bt.empty?
1198
+ new_bt = bt.dup if new_bt.empty?
1086
1199
 
1087
1200
  new_bt
1088
1201
  end