minitest 5.11.3 → 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.
@@ -27,20 +27,18 @@ module Minitest
27
27
  # figure out what diff to use.
28
28
 
29
29
  def self.diff
30
+ return @diff if defined? @diff
31
+
30
32
  @diff = if (RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ &&
31
33
  system("diff.exe", __FILE__, __FILE__)) then
32
34
  "diff.exe -u"
33
- elsif Minitest::Test.maglev? then
34
- "diff -u"
35
35
  elsif system("gdiff", __FILE__, __FILE__)
36
36
  "gdiff -u" # solaris and kin suck
37
37
  elsif system("diff", __FILE__, __FILE__)
38
38
  "diff -u"
39
39
  else
40
40
  nil
41
- end unless defined? @diff
42
-
43
- @diff
41
+ end
44
42
  end
45
43
 
46
44
  ##
@@ -55,22 +53,16 @@ module Minitest
55
53
  # diff command or if it doesn't make sense to diff the output
56
54
  # (single line, short output), then it simply returns a basic
57
55
  # comparison between the two.
56
+ #
57
+ # See +things_to_diff+ for more info.
58
58
 
59
59
  def diff exp, act
60
- expect = mu_pp_for_diff exp
61
- butwas = mu_pp_for_diff act
62
60
  result = nil
63
61
 
64
- need_to_diff =
65
- (expect.include?("\n") ||
66
- butwas.include?("\n") ||
67
- expect.size > 30 ||
68
- butwas.size > 30 ||
69
- expect == butwas) &&
70
- Minitest::Assertions.diff
62
+ expect, butwas = things_to_diff(exp, act)
71
63
 
72
64
  return "Expected: #{mu_pp exp}\n Actual: #{mu_pp act}" unless
73
- need_to_diff
65
+ expect
74
66
 
75
67
  Tempfile.open("expect") do |a|
76
68
  a.puts expect
@@ -99,10 +91,40 @@ module Minitest
99
91
  result
100
92
  end
101
93
 
94
+ ##
95
+ # Returns things to diff [expect, butwas], or [nil, nil] if nothing to diff.
96
+ #
97
+ # Criterion:
98
+ #
99
+ # 1. Strings include newlines or escaped newlines, but not both.
100
+ # 2. or: String lengths are > 30 characters.
101
+ # 3. or: Strings are equal to each other (but maybe different encodings?).
102
+ # 4. and: we found a diff executable.
103
+
104
+ def things_to_diff exp, act
105
+ expect = mu_pp_for_diff exp
106
+ butwas = mu_pp_for_diff act
107
+
108
+ e1, e2 = expect.include?("\n"), expect.include?("\\n")
109
+ b1, b2 = butwas.include?("\n"), butwas.include?("\\n")
110
+
111
+ need_to_diff =
112
+ (e1 ^ e2 ||
113
+ b1 ^ b2 ||
114
+ expect.size > 30 ||
115
+ butwas.size > 30 ||
116
+ expect == butwas) &&
117
+ Minitest::Assertions.diff
118
+
119
+ need_to_diff && [expect, butwas]
120
+ end
121
+
102
122
  ##
103
123
  # This returns a human-readable version of +obj+. By default
104
- # #inspect is called. You can override this to use #pretty_print
124
+ # #inspect is called. You can override this to use #pretty_inspect
105
125
  # if you want.
126
+ #
127
+ # See Minitest::Test.make_my_diffs_pretty!
106
128
 
107
129
  def mu_pp obj
108
130
  s = obj.inspect
@@ -110,8 +132,11 @@ module Minitest
110
132
  if defined? Encoding then
111
133
  s = s.encode Encoding.default_external
112
134
 
113
- if String === obj && obj.encoding != Encoding.default_external then
114
- s = "# encoding: #{obj.encoding}\n#{s}"
135
+ if String === obj && (obj.encoding != Encoding.default_external ||
136
+ !obj.valid_encoding?) then
137
+ enc = "# encoding: #{obj.encoding}"
138
+ val = "# valid: #{obj.valid_encoding?}"
139
+ s = "#{enc}\n#{val}\n#{s}"
115
140
  end
116
141
  end
117
142
 
@@ -119,13 +144,32 @@ module Minitest
119
144
  end
120
145
 
121
146
  ##
122
- # This returns a diff-able human-readable version of +obj+. This
123
- # differs from the regular mu_pp because it expands escaped
124
- # newlines and makes hex-values generic (like object_ids). This
147
+ # This returns a diff-able more human-readable version of +obj+.
148
+ # This differs from the regular mu_pp because it expands escaped
149
+ # newlines and makes hex-values (like object_ids) generic. This
125
150
  # uses mu_pp to do the first pass and then cleans it up.
126
151
 
127
152
  def mu_pp_for_diff obj
128
- mu_pp(obj).gsub(/\\n/, "\n").gsub(/:0x[a-fA-F0-9]{4,}/m, ":0xXXXXXX")
153
+ str = mu_pp obj
154
+
155
+ # both '\n' & '\\n' (_after_ mu_pp (aka inspect))
156
+ single = !!str.match(/(?<!\\|^)\\n/)
157
+ double = !!str.match(/(?<=\\|^)\\n/)
158
+
159
+ process =
160
+ if single ^ double then
161
+ if single then
162
+ lambda { |s| s == "\\n" ? "\n" : s } # unescape
163
+ else
164
+ lambda { |s| s == "\\\\n" ? "\\n\n" : s } # unescape a bit, add nls
165
+ end
166
+ else
167
+ :itself # leave it alone
168
+ end
169
+
170
+ str.
171
+ gsub(/\\?\\n/, &process).
172
+ gsub(/:0x[a-fA-F0-9]{4,}/m, ":0xXXXXXX") # anonymize hex values
129
173
  end
130
174
 
131
175
  ##
@@ -154,6 +198,11 @@ module Minitest
154
198
  assert obj.empty?, msg
155
199
  end
156
200
 
201
+ def _where # :nodoc:
202
+ where = Minitest.filter_backtrace(caller).first
203
+ where = where.split(/:in /, 2).first # clean up noise
204
+ end
205
+
157
206
  E = "" # :nodoc:
158
207
 
159
208
  ##
@@ -177,10 +226,7 @@ module Minitest
177
226
  if Minitest::VERSION =~ /^6/ then
178
227
  refute_nil exp, "Use assert_nil if expecting nil."
179
228
  else
180
- where = Minitest.filter_backtrace(caller).first
181
- where = where.split(/:in /, 2).first # clean up noise
182
-
183
- warn "DEPRECATED: Use assert_nil if expecting nil from #{where}. This will fail in Minitest 6."
229
+ warn "DEPRECATED: Use assert_nil if expecting nil from #{_where}. This will fail in Minitest 6."
184
230
  end
185
231
  end
186
232
 
@@ -249,6 +295,8 @@ module Minitest
249
295
  assert_respond_to matcher, :"=~"
250
296
  matcher = Regexp.new Regexp.escape matcher if String === matcher
251
297
  assert matcher =~ obj, msg
298
+
299
+ Regexp.last_match
252
300
  end
253
301
 
254
302
  ##
@@ -283,6 +331,9 @@ module Minitest
283
331
  # See also: #assert_silent
284
332
 
285
333
  def assert_output stdout = nil, stderr = nil
334
+ flunk "assert_output requires a block to capture output." unless
335
+ block_given?
336
+
286
337
  out, err = capture_io do
287
338
  yield
288
339
  end
@@ -294,6 +345,44 @@ module Minitest
294
345
  x = send out_msg, stdout, out, "In stdout" if out_msg
295
346
 
296
347
  (!stdout || x) && (!stderr || y)
348
+ rescue Assertion
349
+ raise
350
+ rescue => e
351
+ raise UnexpectedError, e
352
+ end
353
+
354
+ ##
355
+ # Fails unless +path+ exists.
356
+
357
+ def assert_path_exists path, msg = nil
358
+ msg = message(msg) { "Expected path '#{path}' to exist" }
359
+ assert File.exist?(path), msg
360
+ end
361
+
362
+ ##
363
+ # For testing with pattern matching (only supported with Ruby 3.0 and later)
364
+ #
365
+ # # pass
366
+ # assert_pattern { [1,2,3] => [Integer, Integer, Integer] }
367
+ #
368
+ # # fail "length mismatch (given 3, expected 1)"
369
+ # assert_pattern { [1,2,3] => [Integer] }
370
+ #
371
+ # The bare <tt>=></tt> pattern will raise a NoMatchingPatternError on failure, which would
372
+ # normally be counted as a test error. This assertion rescues NoMatchingPatternError and
373
+ # generates a test failure. Any other exception will be raised as normal and generate a test
374
+ # error.
375
+
376
+ def assert_pattern
377
+ raise NotImplementedError, "only available in Ruby 3.0+" unless RUBY_VERSION >= "3.0"
378
+ flunk "assert_pattern requires a block to capture errors." unless block_given?
379
+
380
+ begin # TODO: remove after ruby 2.6 dropped
381
+ yield
382
+ pass
383
+ rescue NoMatchingPatternError => e
384
+ flunk e.message
385
+ end
297
386
  end
298
387
 
299
388
  ##
@@ -316,9 +405,26 @@ module Minitest
316
405
  #
317
406
  # +exp+ takes an optional message on the end to help explain
318
407
  # failures and defaults to StandardError if no exception class is
319
- # passed.
408
+ # passed. Eg:
409
+ #
410
+ # assert_raises(CustomError) { method_with_custom_error }
411
+ #
412
+ # With custom error message:
413
+ #
414
+ # assert_raises(CustomError, 'This should have raised CustomError') { method_with_custom_error }
415
+ #
416
+ # Using the returned object:
417
+ #
418
+ # error = assert_raises(CustomError) do
419
+ # raise CustomError, 'This is really bad'
420
+ # end
421
+ #
422
+ # assert_equal 'This is really bad', error.message
320
423
 
321
424
  def assert_raises *exp
425
+ flunk "assert_raises requires a block to capture errors." unless
426
+ block_given?
427
+
322
428
  msg = "#{exp.pop}.\n" if String === exp.last
323
429
  exp << StandardError if exp.empty?
324
430
 
@@ -327,7 +433,7 @@ module Minitest
327
433
  rescue *exp => e
328
434
  pass # count assertion
329
435
  return e
330
- rescue Minitest::Skip, Minitest::Assertion
436
+ rescue Minitest::Assertion # incl Skip & UnexpectedError
331
437
  # don't count assertion
332
438
  raise
333
439
  rescue SignalException, SystemExit
@@ -345,12 +451,13 @@ module Minitest
345
451
 
346
452
  ##
347
453
  # Fails unless +obj+ responds to +meth+.
454
+ # include_all defaults to false to match Object#respond_to?
348
455
 
349
- def assert_respond_to obj, meth, msg = nil
456
+ def assert_respond_to obj, meth, msg = nil, include_all: false
350
457
  msg = message(msg) {
351
458
  "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}"
352
459
  }
353
- assert obj.respond_to?(meth), msg
460
+ assert obj.respond_to?(meth, include_all), msg
354
461
  end
355
462
 
356
463
  ##
@@ -370,9 +477,7 @@ module Minitest
370
477
  # Fails unless the call returns a true value
371
478
 
372
479
  def assert_send send_ary, m = nil
373
- where = Minitest.filter_backtrace(caller).first
374
- where = where.split(/:in /, 2).first # clean up noise
375
- warn "DEPRECATED: assert_send. From #{where}"
480
+ warn "DEPRECATED: assert_send. From #{_where}"
376
481
 
377
482
  recv, msg, *args = send_ary
378
483
  m = message(m) {
@@ -397,7 +502,7 @@ module Minitest
397
502
  def assert_throws sym, msg = nil
398
503
  default = "Expected #{mu_pp(sym)} to have been thrown"
399
504
  caught = true
400
- catch(sym) do
505
+ value = catch(sym) do
401
506
  begin
402
507
  yield
403
508
  rescue ThreadError => e # wtf?!? 1.8 + threads == suck
@@ -413,6 +518,11 @@ module Minitest
413
518
  end
414
519
 
415
520
  assert caught, message(msg) { default }
521
+ value
522
+ rescue Assertion
523
+ raise
524
+ rescue => e
525
+ raise UnexpectedError, e
416
526
  end
417
527
 
418
528
  ##
@@ -481,10 +591,13 @@ module Minitest
481
591
 
482
592
  return captured_stdout.read, captured_stderr.read
483
593
  ensure
484
- captured_stdout.unlink
485
- captured_stderr.unlink
486
594
  $stdout.reopen orig_stdout
487
595
  $stderr.reopen orig_stderr
596
+
597
+ orig_stdout.close
598
+ orig_stderr.close
599
+ captured_stdout.close!
600
+ captured_stderr.close!
488
601
  end
489
602
  end
490
603
  end
@@ -504,7 +617,16 @@ module Minitest
504
617
  end
505
618
 
506
619
  ##
507
- # Fails with +msg+
620
+ # Fails after a given date (in the local time zone). This allows
621
+ # you to put time-bombs in your tests if you need to keep
622
+ # something around until a later date lest you forget about it.
623
+
624
+ def fail_after y,m,d,msg
625
+ flunk msg if Time.now > Time.local(y, m, d)
626
+ end
627
+
628
+ ##
629
+ # Fails with +msg+.
508
630
 
509
631
  def flunk msg = nil
510
632
  msg ||= "Epic Fail!"
@@ -534,7 +656,7 @@ module Minitest
534
656
 
535
657
  def refute test, msg = nil
536
658
  msg ||= message { "Expected #{mu_pp(test)} to not be truthy" }
537
- not assert !test, msg
659
+ assert !test, msg
538
660
  end
539
661
 
540
662
  ##
@@ -626,6 +748,30 @@ module Minitest
626
748
  refute obj.nil?, msg
627
749
  end
628
750
 
751
+ ##
752
+ # For testing with pattern matching (only supported with Ruby 3.0 and later)
753
+ #
754
+ # # pass
755
+ # refute_pattern { [1,2,3] => [String] }
756
+ #
757
+ # # fail "NoMatchingPatternError expected, but nothing was raised."
758
+ # refute_pattern { [1,2,3] => [Integer, Integer, Integer] }
759
+ #
760
+ # This assertion expects a NoMatchingPatternError exception, and will fail if none is raised. Any
761
+ # other exceptions will be raised as normal and generate a test error.
762
+
763
+ def refute_pattern
764
+ raise NotImplementedError, "only available in Ruby 3.0+" unless RUBY_VERSION >= "3.0"
765
+ flunk "refute_pattern requires a block to capture errors." unless block_given?
766
+
767
+ begin
768
+ yield
769
+ flunk("NoMatchingPatternError expected, but nothing was raised.")
770
+ rescue NoMatchingPatternError
771
+ pass
772
+ end
773
+ end
774
+
629
775
  ##
630
776
  # Fails if +o1+ is not +op+ +o2+. Eg:
631
777
  #
@@ -638,6 +784,14 @@ module Minitest
638
784
  refute o1.__send__(op, o2), msg
639
785
  end
640
786
 
787
+ ##
788
+ # Fails if +path+ exists.
789
+
790
+ def refute_path_exists path, msg = nil
791
+ msg = message(msg) { "Expected path '#{path}' to not exist" }
792
+ refute File.exist?(path), msg
793
+ end
794
+
641
795
  ##
642
796
  # For testing with predicates.
643
797
  #
@@ -654,11 +808,12 @@ module Minitest
654
808
 
655
809
  ##
656
810
  # Fails if +obj+ responds to the message +meth+.
811
+ # include_all defaults to false to match Object#respond_to?
657
812
 
658
- def refute_respond_to obj, meth, msg = nil
813
+ def refute_respond_to obj, meth, msg = nil, include_all: false
659
814
  msg = message(msg) { "Expected #{mu_pp(obj)} to not respond to #{meth}" }
660
815
 
661
- refute obj.respond_to?(meth), msg
816
+ refute obj.respond_to?(meth, include_all), msg
662
817
  end
663
818
 
664
819
  ##
@@ -677,10 +832,22 @@ module Minitest
677
832
  # gets listed at the end of the run but doesn't cause a failure
678
833
  # exit code.
679
834
 
680
- def skip msg = nil, bt = caller
835
+ def skip msg = nil, _ignored = nil
681
836
  msg ||= "Skipped, no message given"
682
837
  @skip = true
683
- raise Minitest::Skip, msg, bt
838
+ raise Minitest::Skip, msg
839
+ end
840
+
841
+ ##
842
+ # Skips the current run until a given date (in the local time
843
+ # zone). This allows you to put some fixes on hold until a later
844
+ # date, but still holds you accountable and prevents you from
845
+ # forgetting it.
846
+
847
+ def skip_until y,m,d,msg
848
+ skip msg if Time.now < Time.local(y, m, d)
849
+ where = caller.first.rpartition(':in').reject(&:empty?).first
850
+ warn "Stale skip_until %p at %s" % [msg, where]
684
851
  end
685
852
 
686
853
  ##
@@ -109,8 +109,8 @@ module Minitest
109
109
  # is applied against the slope itself. As such, you probably want
110
110
  # to tighten it from the default.
111
111
  #
112
- # See http://www.graphpad.com/curvefit/goodness_of_fit.htm for
113
- # more details.
112
+ # See https://www.graphpad.com/guides/prism/8/curve-fitting/reg_intepretingnonlinr2.htm
113
+ # for more details.
114
114
  #
115
115
  # Fit is calculated by #fit_linear.
116
116
  #
@@ -217,7 +217,7 @@ module Minitest
217
217
  ##
218
218
  # Takes an array of x/y pairs and calculates the general R^2 value.
219
219
  #
220
- # See: http://en.wikipedia.org/wiki/Coefficient_of_determination
220
+ # See: https://en.wikipedia.org/wiki/Coefficient_of_determination
221
221
 
222
222
  def fit_error xys
223
223
  y_bar = sigma(xys) { |_, y| y } / xys.size.to_f
@@ -232,7 +232,7 @@ module Minitest
232
232
  #
233
233
  # Takes x and y values and returns [a, b, r^2].
234
234
  #
235
- # See: http://mathworld.wolfram.com/LeastSquaresFittingExponential.html
235
+ # See: https://mathworld.wolfram.com/LeastSquaresFittingExponential.html
236
236
 
237
237
  def fit_exponential xs, ys
238
238
  n = xs.size
@@ -254,7 +254,7 @@ module Minitest
254
254
  #
255
255
  # Takes x and y values and returns [a, b, r^2].
256
256
  #
257
- # See: http://mathworld.wolfram.com/LeastSquaresFittingLogarithmic.html
257
+ # See: https://mathworld.wolfram.com/LeastSquaresFittingLogarithmic.html
258
258
 
259
259
  def fit_logarithmic xs, ys
260
260
  n = xs.size
@@ -276,7 +276,7 @@ module Minitest
276
276
  #
277
277
  # Takes x and y values and returns [a, b, r^2].
278
278
  #
279
- # See: http://mathworld.wolfram.com/LeastSquaresFitting.html
279
+ # See: https://mathworld.wolfram.com/LeastSquaresFitting.html
280
280
 
281
281
  def fit_linear xs, ys
282
282
  n = xs.size
@@ -298,7 +298,7 @@ module Minitest
298
298
  #
299
299
  # Takes x and y values and returns [a, b, r^2].
300
300
  #
301
- # See: http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
301
+ # See: https://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
302
302
 
303
303
  def fit_power xs, ys
304
304
  n = xs.size
@@ -0,0 +1,94 @@
1
+ module Minitest
2
+ ##
3
+ # Compresses backtraces.
4
+
5
+ module Compress
6
+
7
+ ##
8
+ # Takes a backtrace (array of strings) and compresses repeating
9
+ # cycles in it to make it more readable.
10
+
11
+ def compress orig
12
+ ary = orig
13
+
14
+ eswo = ->(ary, n, off) { # each_slice_with_offset
15
+ if off.zero? then
16
+ ary.each_slice n
17
+ else
18
+ # [ ...off... [...n...] [...n...] ... ]
19
+ front, back = ary.take(off), ary.drop(off)
20
+ [front].chain back.each_slice n
21
+ end
22
+ }
23
+
24
+ 3.times do # maybe don't use loop do here?
25
+ index = ary # [ a b c b c b c d ]
26
+ .size
27
+ .times # 0...size
28
+ .group_by { |i| ary[i] } # { a: [0] b: [1 3 5], c: [2 4 6], d: [7] }
29
+
30
+ order = index
31
+ .reject { |k, v| v.size == 1 } # { b: [1 3 5], c: [2 4 6] }
32
+ .sort_by { |k, ary| ### sort by max dist + min offset
33
+ d = ary.each_cons(2).sum { |a, b| b-a }
34
+ [-d, ary.first]
35
+ } # b: [1 3 5] c: [2 4 6]
36
+
37
+ ranges = order
38
+ .map { |k, ary| # [[1..2 3..4] [2..3 4..5]]
39
+ ary
40
+ .each_cons(2)
41
+ .map { |a, b| a..b-1 }
42
+ }
43
+
44
+ big_ranges = ranges
45
+ .flat_map { |a| # [1..2 3..4 2..3 4..5]
46
+ a.sort_by { |r| [-r.size, r.first] }.first 5
47
+ }
48
+ .first(100)
49
+
50
+ culprits = big_ranges
51
+ .map { |r|
52
+ eswo[ary, r.size, r.begin] # [o1 s1 s1 s2 s2]
53
+ .chunk_while { |a,b| a == b } # [[o1] [s1 s1] [s2 s2]]
54
+ .map { |a| [a.size, a.first] } # [[1 o1] [2 s1] [2 s2]]
55
+ }
56
+ .select { |chunks|
57
+ chunks.any? { |a| a.first > 1 } # compressed anything?
58
+ }
59
+
60
+ min = culprits
61
+ .min_by { |a| a.flatten.size } # most compressed
62
+
63
+ break unless min
64
+
65
+ ary = min.flat_map { |(n, lines)|
66
+ if n > 1 then
67
+ [[n, compress(lines)]] # [o1 [2 s1] [2 s2]]
68
+ else
69
+ lines
70
+ end
71
+ }
72
+ end
73
+
74
+ format = ->(lines) {
75
+ lines.flat_map { |line|
76
+ case line
77
+ when Array then
78
+ n, lines = line
79
+ lines = format[lines]
80
+ [
81
+ " +->> #{n} cycles of #{lines.size} lines:",
82
+ *lines.map { |s| " | #{s}" },
83
+ " +-<<",
84
+ ]
85
+ else
86
+ line
87
+ end
88
+ }
89
+ }
90
+
91
+ format[ary]
92
+ end
93
+ end
94
+ end