minitest 5.18.0 → 5.22.3

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.18.0" # :nodoc:
12
+ VERSION = "5.22.2" # :nodoc:
13
13
 
14
14
  @@installed_at_exit ||= false
15
15
  @@after_run = []
@@ -32,7 +32,7 @@ module Minitest
32
32
 
33
33
  cattr_accessor :parallel_executor
34
34
 
35
- warn "DEPRECATED: use MT_CPU instead of N for parallel test runs" if ENV["N"]
35
+ warn "DEPRECATED: use MT_CPU instead of N for parallel test runs" if ENV["N"] && ENV["N"].to_i > 0
36
36
  n_threads = (ENV["MT_CPU"] || ENV["N"] || Etc.nprocessors).to_i
37
37
 
38
38
  self.parallel_executor = Parallel::Executor.new n_threads
@@ -60,6 +60,9 @@ module Minitest
60
60
  cattr_accessor :info_signal
61
61
  self.info_signal = "INFO"
62
62
 
63
+ cattr_accessor :allow_fork
64
+ self.allow_fork = false
65
+
63
66
  ##
64
67
  # Registers Minitest to run at process exit
65
68
 
@@ -75,7 +78,7 @@ module Minitest
75
78
 
76
79
  pid = Process.pid
77
80
  at_exit {
78
- next if Process.pid != pid
81
+ next if !Minitest.allow_fork && Process.pid != pid
79
82
  @@after_run.reverse_each(&:call)
80
83
  exit exit_code || false
81
84
  }
@@ -131,7 +134,7 @@ module Minitest
131
134
  # Minitest.run(args)
132
135
  # Minitest.__run(reporter, options)
133
136
  # Runnable.runnables.each
134
- # runnable.run(reporter, options)
137
+ # runnable_klass.run(reporter, options)
135
138
  # self.runnable_methods.each
136
139
  # self.run_one_method(self, runnable_method, reporter)
137
140
  # Minitest.run_one_method(klass, runnable_method)
@@ -147,7 +150,7 @@ module Minitest
147
150
 
148
151
  reporter = CompositeReporter.new
149
152
  reporter << SummaryReporter.new(options[:io], options)
150
- reporter << ProgressReporter.new(options[:io], options)
153
+ reporter << ProgressReporter.new(options[:io], options) unless options[:quiet]
151
154
 
152
155
  self.reporter = reporter # this makes it available to plugins
153
156
  self.init_plugins options
@@ -161,11 +164,32 @@ module Minitest
161
164
  warn "Interrupted. Exiting..."
162
165
  end
163
166
  self.parallel_executor.shutdown
167
+
168
+ # might have been removed/replaced during init_plugins:
169
+ summary = reporter.reporters.grep(SummaryReporter).first
170
+ return empty_run! options if summary && summary.count == 0
171
+
164
172
  reporter.report
165
173
 
166
174
  reporter.passed?
167
175
  end
168
176
 
177
+ def self.empty_run! options # :nodoc:
178
+ filter = options[:filter]
179
+ return true unless filter # no filter, but nothing ran == success
180
+
181
+ warn "Nothing ran for filter: %s" % [filter]
182
+
183
+ require "did_you_mean" # soft dependency, punt if it doesn't load
184
+
185
+ ms = Runnable.runnables.flat_map(&:runnable_methods)
186
+ cs = DidYouMean::SpellChecker.new(dictionary: ms).correct filter
187
+
188
+ warn DidYouMean::Formatter.message_for cs unless cs.empty?
189
+ rescue LoadError
190
+ # do nothing
191
+ end
192
+
169
193
  ##
170
194
  # Internal run method. Responsible for telling all Runnable
171
195
  # sub-classes to run.
@@ -209,6 +233,10 @@ module Minitest
209
233
  options[:verbose] = true
210
234
  end
211
235
 
236
+ opts.on "-q", "--quiet", "Quiet. Show no progress processing files." do
237
+ options[:quiet] = true
238
+ end
239
+
212
240
  opts.on "--show-skips", "Show skipped at the end of run." do
213
241
  options[:show_skips] = true
214
242
  end
@@ -331,19 +359,15 @@ module Minitest
331
359
  # reporter to record.
332
360
 
333
361
  def self.run reporter, options = {}
334
- filter = options[:filter] || "/./"
335
- filter = Regexp.new $1 if filter.is_a?(String) && filter =~ %r%/(.*)/%
362
+ pos = options[:filter]
363
+ neg = options[:exclude]
336
364
 
337
- filtered_methods = self.runnable_methods.find_all { |m|
338
- filter === m || filter === "#{self}##{m}"
339
- }
340
-
341
- exclude = options[:exclude]
342
- exclude = Regexp.new $1 if exclude =~ %r%/(.*)/%
365
+ pos = Regexp.new $1 if pos.is_a?(String) && pos =~ %r%/(.*)/%
366
+ neg = Regexp.new $1 if neg.is_a?(String) && neg =~ %r%/(.*)/%
343
367
 
344
- filtered_methods.delete_if { |m|
345
- exclude === m || exclude === "#{self}##{m}"
346
- }
368
+ filtered_methods = self.runnable_methods
369
+ .select { |m| !pos || pos === m || pos === "#{self}##{m}" }
370
+ .reject { |m| neg && (neg === m || neg === "#{self}##{m}") }
347
371
 
348
372
  return if filtered_methods.empty?
349
373
 
@@ -365,6 +389,14 @@ module Minitest
365
389
  reporter.record Minitest.run_one_method(klass, method_name)
366
390
  end
367
391
 
392
+ ##
393
+ # Defines the order to run tests (:random by default). Override
394
+ # this or use a convenience method to change it for your tests.
395
+
396
+ def self.test_order
397
+ :random
398
+ end
399
+
368
400
  def self.with_info_handler reporter, &block # :nodoc:
369
401
  handler = lambda do
370
402
  unless reporter.passed? then
@@ -432,6 +464,31 @@ module Minitest
432
464
  self.name = name
433
465
  self.failures = []
434
466
  self.assertions = 0
467
+ # lazy initializer for metadata
468
+ end
469
+
470
+ ##
471
+ # Metadata you attach to the test results that get sent to the reporter.
472
+ #
473
+ # Lazily initializes to a hash, to keep memory down.
474
+ #
475
+ # NOTE: this data *must* be plain (read: marshal-able) data!
476
+ # Hashes! Arrays! Strings!
477
+
478
+ def metadata
479
+ @metadata ||= {}
480
+ end
481
+
482
+ ##
483
+ # Sets metadata, mainly used for +Result.from+.
484
+
485
+ attr_writer :metadata
486
+
487
+ ##
488
+ # Returns true if metadata exists.
489
+
490
+ def metadata?
491
+ defined? @metadata
435
492
  end
436
493
 
437
494
  ##
@@ -483,12 +540,14 @@ module Minitest
483
540
  not self.failure
484
541
  end
485
542
 
543
+ BASE_DIR = "#{Dir.pwd}/" # :nodoc:
544
+
486
545
  ##
487
546
  # The location identifier of this test. Depends on a method
488
547
  # existing called class_name.
489
548
 
490
549
  def location
491
- loc = " [#{self.failure.location}]" unless passed? or error?
550
+ loc = " [#{self.failure.location.delete_prefix BASE_DIR}]" unless passed? or error?
492
551
  "#{self.class_name}##{self.name}#{loc}"
493
552
  end
494
553
 
@@ -552,6 +611,7 @@ module Minitest
552
611
  r.assertions = o.assertions
553
612
  r.failures = o.failures.dup
554
613
  r.time = o.time
614
+ r.metadata = o.metadata if o.metadata?
555
615
 
556
616
  r.source_location = o.method(o.name).source_location rescue ["unknown", -1]
557
617
 
@@ -576,7 +636,10 @@ module Minitest
576
636
  # you want. Go nuts.
577
637
 
578
638
  class AbstractReporter
579
- include Mutex_m
639
+
640
+ def initialize # :nodoc:
641
+ @mutex = Mutex.new
642
+ end
580
643
 
581
644
  ##
582
645
  # Starts reporting on the run.
@@ -612,6 +675,10 @@ module Minitest
612
675
  def passed?
613
676
  true
614
677
  end
678
+
679
+ def synchronize(&block) # :nodoc:
680
+ @mutex.synchronize(&block)
681
+ end
615
682
  end
616
683
 
617
684
  class Reporter < AbstractReporter # :nodoc:
@@ -785,7 +852,7 @@ module Minitest
785
852
  io.puts "# Running:"
786
853
  io.puts
787
854
 
788
- self.sync = io.respond_to? :"sync=" # stupid emacs
855
+ self.sync = io.respond_to? :"sync="
789
856
  self.old_sync, io.sync = io.sync, true if self.sync
790
857
  end
791
858
 
@@ -893,6 +960,8 @@ module Minitest
893
960
  # Represents run failures.
894
961
 
895
962
  class Assertion < Exception
963
+ RE = /in [`'](?:[^']+[#.])?(?:assert|refute|flunk|pass|fail|raise|must|wont)/ # :nodoc:
964
+
896
965
  def error # :nodoc:
897
966
  self
898
967
  end
@@ -901,12 +970,11 @@ module Minitest
901
970
  # Where was this run before an assertion was raised?
902
971
 
903
972
  def location
904
- last_before_assertion = ""
905
- self.backtrace.reverse_each do |s|
906
- break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/
907
- last_before_assertion = s
908
- end
909
- last_before_assertion.sub(/:in .*$/, "")
973
+ bt = Minitest.filter_backtrace self.backtrace
974
+ idx = bt.rindex { |s| s.match? RE } || -1 # fall back to first item
975
+ loc = bt[idx+1] || bt.last || "unknown:-1"
976
+
977
+ loc.sub(/:in .*$/, "")
910
978
  end
911
979
 
912
980
  def result_code # :nodoc:
@@ -931,11 +999,21 @@ module Minitest
931
999
  # Assertion wrapping an unexpected error that was raised during a run.
932
1000
 
933
1001
  class UnexpectedError < Assertion
1002
+ include Minitest::Compress
1003
+
934
1004
  # TODO: figure out how to use `cause` instead
935
1005
  attr_accessor :error # :nodoc:
936
1006
 
937
1007
  def initialize error # :nodoc:
938
1008
  super "Unexpected exception"
1009
+
1010
+ if SystemStackError === error then
1011
+ bt = error.backtrace
1012
+ new_bt = compress bt
1013
+ error = error.exception "#{bt.size} -> #{new_bt.size}"
1014
+ error.set_backtrace new_bt
1015
+ end
1016
+
939
1017
  self.error = error
940
1018
  end
941
1019
 
@@ -943,8 +1021,11 @@ module Minitest
943
1021
  self.error.backtrace
944
1022
  end
945
1023
 
1024
+ BASE_RE = %r%#{Dir.pwd}/% # :nodoc:
1025
+
946
1026
  def message # :nodoc:
947
- bt = Minitest.filter_backtrace(self.backtrace).join "\n "
1027
+ bt = Minitest.filter_backtrace(self.backtrace).join("\n ")
1028
+ .gsub(BASE_RE, "")
948
1029
  "#{self.error.class}: #{self.error.message}\n #{bt}"
949
1030
  end
950
1031
 
@@ -1028,6 +1109,12 @@ module Minitest
1028
1109
 
1029
1110
  MT_RE = %r%lib/minitest% #:nodoc:
1030
1111
 
1112
+ attr_accessor :regexp
1113
+
1114
+ def initialize regexp = MT_RE
1115
+ self.regexp = regexp
1116
+ end
1117
+
1031
1118
  ##
1032
1119
  # Filter +bt+ to something useful. Returns the whole thing if
1033
1120
  # $DEBUG (ruby) or $MT_DEBUG (env).
@@ -1037,9 +1124,9 @@ module Minitest
1037
1124
 
1038
1125
  return bt.dup if $DEBUG || ENV["MT_DEBUG"]
1039
1126
 
1040
- new_bt = bt.take_while { |line| line !~ MT_RE }
1041
- new_bt = bt.select { |line| line !~ MT_RE } if new_bt.empty?
1042
- new_bt = bt.dup if new_bt.empty?
1127
+ new_bt = bt.take_while { |line| line.to_s !~ regexp }
1128
+ new_bt = bt.select { |line| line.to_s !~ regexp } if new_bt.empty?
1129
+ new_bt = bt.dup if new_bt.empty?
1043
1130
 
1044
1131
  new_bt
1045
1132
  end
@@ -8,16 +8,17 @@ 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
@@ -105,15 +106,20 @@ class MetaMetaMetaTestCase < Minitest::Test
105
106
  output.gsub!(/0x[A-Fa-f0-9]+/, "0xXXX")
106
107
  output.gsub!(/ +$/, "")
107
108
 
109
+ file = ->(s) { s.start_with?("/") ? "FULLFILE" : "FILE" }
110
+
108
111
  if windows? then
109
112
  output.gsub!(/\[(?:[A-Za-z]:)?[^\]:]+:\d+\]/, "[FILE:LINE]")
110
- output.gsub!(/^(\s+)(?:[A-Za-z]:)?[^:]+:\d+:in/, '\1FILE:LINE:in')
113
+ output.gsub!(/^(\s+)(?:[A-Za-z]:)?[^:]+:\d+:in [`']/, '\1FILE:LINE:in \'')
111
114
  else
112
- output.gsub!(/\[[^\]:]+:\d+\]/, "[FILE:LINE]")
113
- output.gsub!(/^(\s+)[^:]+:\d+:in/, '\1FILE:LINE:in')
115
+ output.gsub!(/\[([^\]:]+):\d+\]/) { "[#{file[$1]}:LINE]" }
116
+ output.gsub!(/^(\s+)([^:]+):\d+:in [`']/) { "#{$1}#{file[$2]}:LINE:in '" }
114
117
  end
115
118
 
116
- output.gsub!(/( at )[^:]+:\d+/, '\1[FILE:LINE]')
119
+ output.gsub!(/in [`']block in (?:([^']+)[#.])?/, "in 'block in")
120
+ output.gsub!(/in [`'](?:([^']+)[#.])?/, "in '")
121
+
122
+ output.gsub!(/( at )[^:]+:\d+/) { "#{$1}[#{file[$2]}:LINE]" } # eval?
117
123
 
118
124
  output
119
125
  end
@@ -762,12 +762,13 @@ class TestMinitestAssertions < Minitest::Test
762
762
  Class: <SomeError>
763
763
  Message: <\"blah\">
764
764
  ---Backtrace---
765
- FILE:LINE:in \`block in test_assert_raises_default_triggered\'
765
+ FILE:LINE:in \'block in test_assert_raises_default_triggered\'
766
766
  ---------------
767
767
  EOM
768
768
 
769
769
  actual = e.message.gsub(/^.+:\d+/, "FILE:LINE")
770
770
  actual.gsub!(RE_LEVELS, "") unless jruby?
771
+ actual.gsub!(/[`']block in (?:TestMinitestAssertions#)?/, "'block in ")
771
772
 
772
773
  assert_equal expected, actual
773
774
  end
@@ -841,12 +842,13 @@ class TestMinitestAssertions < Minitest::Test
841
842
  Class: <AnError>
842
843
  Message: <\"some message\">
843
844
  ---Backtrace---
844
- FILE:LINE:in \`block in test_assert_raises_subclass_triggered\'
845
+ FILE:LINE:in \'block in test_assert_raises_subclass_triggered\'
845
846
  ---------------
846
847
  EOM
847
848
 
848
849
  actual = e.message.gsub(/^.+:\d+/, "FILE:LINE")
849
850
  actual.gsub!(RE_LEVELS, "") unless jruby?
851
+ actual.gsub!(/[`']block in (?:TestMinitestAssertions#)?/, "'block in ")
850
852
 
851
853
  assert_equal expected.chomp, actual
852
854
  end
@@ -863,12 +865,13 @@ class TestMinitestAssertions < Minitest::Test
863
865
  Class: <SyntaxError>
864
866
  Message: <\"icky\">
865
867
  ---Backtrace---
866
- FILE:LINE:in \`block in test_assert_raises_triggered_different\'
868
+ FILE:LINE:in \'block in test_assert_raises_triggered_different\'
867
869
  ---------------
868
870
  EOM
869
871
 
870
872
  actual = e.message.gsub(/^.+:\d+/, "FILE:LINE")
871
873
  actual.gsub!(RE_LEVELS, "") unless jruby?
874
+ actual.gsub!(/[`']block in (?:TestMinitestAssertions#)?/, "'block in ")
872
875
 
873
876
  assert_equal expected, actual
874
877
  end
@@ -886,12 +889,13 @@ class TestMinitestAssertions < Minitest::Test
886
889
  Class: <SyntaxError>
887
890
  Message: <\"icky\">
888
891
  ---Backtrace---
889
- FILE:LINE:in \`block in test_assert_raises_triggered_different_msg\'
892
+ FILE:LINE:in \'block in test_assert_raises_triggered_different_msg\'
890
893
  ---------------
891
894
  EOM
892
895
 
893
896
  actual = e.message.gsub(/^.+:\d+/, "FILE:LINE")
894
897
  actual.gsub!(RE_LEVELS, "") unless jruby?
898
+ actual.gsub!(/[`']block in (?:TestMinitestAssertions#)?/, "'block in ")
895
899
 
896
900
  assert_equal expected.chomp, actual
897
901
  end
@@ -936,6 +940,16 @@ class TestMinitestAssertions < Minitest::Test
936
940
  end
937
941
  end
938
942
 
943
+ def test_assert_respond_to__include_all
944
+ @tc.assert_respond_to @tc, :exit, include_all: true
945
+ end
946
+
947
+ def test_assert_respond_to__include_all_triggered
948
+ assert_triggered(/Expected .+::DummyTest. to respond to #exit\?/) do
949
+ @tc.assert_respond_to @tc, :exit?, include_all: true
950
+ end
951
+ end
952
+
939
953
  def test_assert_same
940
954
  @assertion_count = 3
941
955
 
@@ -1153,18 +1167,14 @@ class TestMinitestAssertions < Minitest::Test
1153
1167
  def test_class_asserts_match_refutes
1154
1168
  @assertion_count = 0
1155
1169
 
1156
- methods = Minitest::Assertions.public_instance_methods
1157
- methods.map!(&:to_s) if Symbol === methods.first
1170
+ methods = Minitest::Assertions.public_instance_methods.map(&:to_s)
1158
1171
 
1159
1172
  # These don't have corresponding refutes _on purpose_. They're
1160
1173
  # useless and will never be added, so don't bother.
1161
1174
  ignores = %w[assert_output assert_raises assert_send
1162
1175
  assert_silent assert_throws assert_mock]
1163
1176
 
1164
- # These are test/unit methods. I'm not actually sure why they're still here
1165
- ignores += %w[assert_no_match assert_not_equal assert_not_nil
1166
- assert_not_same assert_nothing_raised
1167
- assert_nothing_thrown assert_raise]
1177
+ ignores += %w[assert_allocations] # for minitest-gcstats
1168
1178
 
1169
1179
  asserts = methods.grep(/^assert/).sort - ignores
1170
1180
  refutes = methods.grep(/^refute/).sort - ignores
@@ -1444,6 +1454,16 @@ class TestMinitestAssertions < Minitest::Test
1444
1454
  end
1445
1455
  end
1446
1456
 
1457
+ def test_refute_respond_to__include_all
1458
+ @tc.refute_respond_to "blah", :missing, include_all: true
1459
+ end
1460
+
1461
+ def test_refute_respond_to__include_all_triggered
1462
+ assert_triggered(/Expected .*DummyTest.* to not respond to exit./) do
1463
+ @tc.refute_respond_to @tc, :exit, include_all: true
1464
+ end
1465
+ end
1466
+
1447
1467
  def test_refute_same
1448
1468
  @tc.refute_same 1, 2
1449
1469
  end
@@ -728,7 +728,7 @@ class TestMinitestStub < Minitest::Test
728
728
  end
729
729
 
730
730
  exp = jruby? ? /Undefined method nope_nope_nope for '#{self.class}::Time'/ :
731
- /undefined method `nope_nope_nope' for( class)? `#{self.class}::Time'/
731
+ /undefined method [`']nope_nope_nope' for( class)? [`']#{self.class}::Time'/
732
732
  assert_match exp, e.message
733
733
  end
734
734
 
@@ -1083,7 +1083,7 @@ class TestMinitestStub < Minitest::Test
1083
1083
  end
1084
1084
  end
1085
1085
  end
1086
- exp = /undefined method `write' for nil/
1086
+ exp = /undefined method [`']write' for nil/
1087
1087
  assert_match exp, e.message
1088
1088
  end
1089
1089
 
@@ -48,6 +48,25 @@ class TestMinitestReporter < MetaMetaMetaTestCase
48
48
  @et
49
49
  end
50
50
 
51
+ def system_stack_error_test
52
+ unless defined? @sse then
53
+
54
+ ex = SystemStackError.new
55
+
56
+ pre = ("a".."c").to_a
57
+ mid = ("aa".."ad").to_a * 67
58
+ post = ("d".."f").to_a
59
+ ary = pre + mid + post
60
+
61
+ ex.set_backtrace ary
62
+
63
+ @sse = Minitest::Test.new(:woot)
64
+ @sse.failures << Minitest::UnexpectedError.new(ex)
65
+ @sse = Minitest::Result.from @sse
66
+ end
67
+ @sse
68
+ end
69
+
51
70
  def fail_test
52
71
  unless defined? @ft then
53
72
  @ft = Minitest::Test.new(:woot)
@@ -65,6 +84,12 @@ class TestMinitestReporter < MetaMetaMetaTestCase
65
84
  @pt ||= Minitest::Result.from Minitest::Test.new(:woot)
66
85
  end
67
86
 
87
+ def passing_test_with_metadata
88
+ test = Minitest::Test.new(:woot)
89
+ test.metadata[:meta] = :data
90
+ @pt ||= Minitest::Result.from test
91
+ end
92
+
68
93
  def skip_test
69
94
  unless defined? @st then
70
95
  @st = Minitest::Test.new(:woot)
@@ -166,6 +191,29 @@ class TestMinitestReporter < MetaMetaMetaTestCase
166
191
  assert_equal 0, r.assertions
167
192
  end
168
193
 
194
+ def test_record_pass_with_metadata
195
+ reporter = self.r
196
+
197
+ def reporter.metadata
198
+ @metadata
199
+ end
200
+
201
+ def reporter.record result
202
+ super
203
+ @metadata = result.metadata if result.metadata?
204
+ end
205
+
206
+ r.record passing_test_with_metadata
207
+
208
+ exp = { :meta => :data }
209
+ assert_equal exp, reporter.metadata
210
+
211
+ assert_equal ".", io.string
212
+ assert_empty r.results
213
+ assert_equal 1, r.count
214
+ assert_equal 0, r.assertions
215
+ end
216
+
169
217
  def test_record_fail
170
218
  fail_test = self.fail_test
171
219
  r.record fail_test
@@ -276,8 +324,44 @@ class TestMinitestReporter < MetaMetaMetaTestCase
276
324
  1) Error:
277
325
  Minitest::Test#woot:
278
326
  RuntimeError: no
279
- FILE:LINE:in `error_test'
280
- FILE:LINE:in `test_report_error'
327
+ FILE:LINE:in 'error_test'
328
+ FILE:LINE:in 'test_report_error'
329
+
330
+ 1 runs, 0 assertions, 0 failures, 1 errors, 0 skips
331
+ EOM
332
+
333
+ assert_equal exp, normalize_output(io.string)
334
+ end
335
+
336
+ def test_report_error__sse
337
+ r.start
338
+ r.record system_stack_error_test
339
+ r.report
340
+
341
+ exp = clean <<-EOM
342
+ Run options:
343
+
344
+ # Running:
345
+
346
+ E
347
+
348
+ Finished in 0.00
349
+
350
+ 1) Error:
351
+ Minitest::Test#woot:
352
+ SystemStackError: 274 -> 12
353
+ a
354
+ b
355
+ c
356
+ +->> 67 cycles of 4 lines:
357
+ | aa
358
+ | ab
359
+ | ac
360
+ | ad
361
+ +-<<
362
+ d
363
+ e
364
+ f
281
365
 
282
366
  1 runs, 0 assertions, 0 failures, 1 errors, 0 skips
283
367
  EOM
@@ -309,4 +393,48 @@ class TestMinitestReporter < MetaMetaMetaTestCase
309
393
 
310
394
  assert_equal exp, normalize_output(io.string)
311
395
  end
396
+
397
+ def test_report_failure_uses_backtrace_filter
398
+ filter = Minitest::BacktraceFilter.new
399
+ def filter.filter _bt
400
+ ["foo.rb:123:in 'foo'"]
401
+ end
402
+
403
+ with_backtrace_filter filter do
404
+ r.start
405
+ r.record fail_test
406
+ r.report
407
+ end
408
+
409
+ exp = "Minitest::Test#woot [foo.rb:123]"
410
+
411
+ assert_includes io.string, exp
412
+ end
413
+
414
+ def test_report_failure_uses_backtrace_filter_complex_sorbet
415
+ backtrace = <<~EOBT
416
+ /Users/user/.gem/ruby/3.2.2/gems/minitest-5.20.0/lib/minitest/assertions.rb:183:in 'assert'
417
+ example_test.rb:9:in 'assert_false'
418
+ /Users/user/.gem/ruby/3.2.2/gems/sorbet-runtime-0.5.11068/lib/types/private/methods/call_validation.rb:256:in 'bind_call'
419
+ /Users/user/.gem/ruby/3.2.2/gems/sorbet-runtime-0.5.11068/lib/types/private/methods/call_validation.rb:256:in 'validate_call'
420
+ /Users/user/.gem/ruby/3.2.2/gems/sorbet-runtime-0.5.11068/lib/types/private/methods/_methods.rb:275:in 'block in _on_method_added'
421
+ example_test.rb:25:in 'test_something'
422
+ /Users/user/.gem/ruby/3.2.2/gems/minitest-5.20.0/lib/minitest/test.rb:94:in 'block (3 levels) in run'
423
+ /Users/user/.gem/ruby/3.2.2/gems/minitest-5.20.0/lib/minitest/test.rb:191:in 'capture_exceptions'
424
+ /Users/user/.gem/ruby/3.2.2/gems/minitest-5.20.0/lib/minitest/test.rb:89:in 'block (2 levels) in run'
425
+ ... so many lines ...
426
+ EOBT
427
+
428
+ filter = Minitest::BacktraceFilter.new %r%lib/minitest|gems/sorbet%
429
+
430
+ with_backtrace_filter filter do
431
+ begin
432
+ assert_equal 1, 2
433
+ rescue Minitest::Assertion => e
434
+ e.set_backtrace backtrace.lines.map(&:chomp)
435
+
436
+ assert_match "example_test.rb:25", e.location
437
+ end
438
+ end
439
+ end
312
440
  end