minitest 5.12.0 → 5.22.2

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.
@@ -32,8 +32,6 @@ module Minitest
32
32
  @diff = if (RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ &&
33
33
  system("diff.exe", __FILE__, __FILE__)) then
34
34
  "diff.exe -u"
35
- elsif Minitest::Test.maglev? then
36
- "diff -u"
37
35
  elsif system("gdiff", __FILE__, __FILE__)
38
36
  "gdiff -u" # solaris and kin suck
39
37
  elsif system("diff", __FILE__, __FILE__)
@@ -55,25 +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
- e1, e2 = expect.include?("\n"), expect.include?("\\n")
65
- b1, b2 = butwas.include?("\n"), butwas.include?("\\n")
66
-
67
- need_to_diff =
68
- (e1 ^ e2 ||
69
- b1 ^ b2 ||
70
- expect.size > 30 ||
71
- butwas.size > 30 ||
72
- expect == butwas) &&
73
- Minitest::Assertions.diff
62
+ expect, butwas = things_to_diff(exp, act)
74
63
 
75
64
  return "Expected: #{mu_pp exp}\n Actual: #{mu_pp act}" unless
76
- need_to_diff
65
+ expect
77
66
 
78
67
  Tempfile.open("expect") do |a|
79
68
  a.puts expect
@@ -102,6 +91,34 @@ module Minitest
102
91
  result
103
92
  end
104
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
+
105
122
  ##
106
123
  # This returns a human-readable version of +obj+. By default
107
124
  # #inspect is called. You can override this to use #pretty_inspect
@@ -136,8 +153,8 @@ module Minitest
136
153
  str = mu_pp obj
137
154
 
138
155
  # both '\n' & '\\n' (_after_ mu_pp (aka inspect))
139
- single = str.match?(/(?<!\\|^)\\n/)
140
- double = str.match?(/(?<=\\|^)\\n/)
156
+ single = !!str.match(/(?<!\\|^)\\n/)
157
+ double = !!str.match(/(?<=\\|^)\\n/)
141
158
 
142
159
  process =
143
160
  if single ^ double then
@@ -181,6 +198,11 @@ module Minitest
181
198
  assert obj.empty?, msg
182
199
  end
183
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
+
184
206
  E = "" # :nodoc:
185
207
 
186
208
  ##
@@ -204,10 +226,7 @@ module Minitest
204
226
  if Minitest::VERSION =~ /^6/ then
205
227
  refute_nil exp, "Use assert_nil if expecting nil."
206
228
  else
207
- where = Minitest.filter_backtrace(caller).first
208
- where = where.split(/:in /, 2).first # clean up noise
209
-
210
- 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."
211
230
  end
212
231
  end
213
232
 
@@ -276,6 +295,8 @@ module Minitest
276
295
  assert_respond_to matcher, :"=~"
277
296
  matcher = Regexp.new Regexp.escape matcher if String === matcher
278
297
  assert matcher =~ obj, msg
298
+
299
+ Regexp.last_match
279
300
  end
280
301
 
281
302
  ##
@@ -324,6 +345,44 @@ module Minitest
324
345
  x = send out_msg, stdout, out, "In stdout" if out_msg
325
346
 
326
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
327
386
  end
328
387
 
329
388
  ##
@@ -346,7 +405,21 @@ module Minitest
346
405
  #
347
406
  # +exp+ takes an optional message on the end to help explain
348
407
  # failures and defaults to StandardError if no exception class is
349
- # 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
350
423
 
351
424
  def assert_raises *exp
352
425
  flunk "assert_raises requires a block to capture errors." unless
@@ -360,7 +433,7 @@ module Minitest
360
433
  rescue *exp => e
361
434
  pass # count assertion
362
435
  return e
363
- rescue Minitest::Skip, Minitest::Assertion
436
+ rescue Minitest::Assertion # incl Skip & UnexpectedError
364
437
  # don't count assertion
365
438
  raise
366
439
  rescue SignalException, SystemExit
@@ -378,12 +451,13 @@ module Minitest
378
451
 
379
452
  ##
380
453
  # Fails unless +obj+ responds to +meth+.
454
+ # include_all defaults to false to match Object#respond_to?
381
455
 
382
- def assert_respond_to obj, meth, msg = nil
456
+ def assert_respond_to obj, meth, msg = nil, include_all: false
383
457
  msg = message(msg) {
384
458
  "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}"
385
459
  }
386
- assert obj.respond_to?(meth), msg
460
+ assert obj.respond_to?(meth, include_all), msg
387
461
  end
388
462
 
389
463
  ##
@@ -403,9 +477,7 @@ module Minitest
403
477
  # Fails unless the call returns a true value
404
478
 
405
479
  def assert_send send_ary, m = nil
406
- where = Minitest.filter_backtrace(caller).first
407
- where = where.split(/:in /, 2).first # clean up noise
408
- warn "DEPRECATED: assert_send. From #{where}"
480
+ warn "DEPRECATED: assert_send. From #{_where}"
409
481
 
410
482
  recv, msg, *args = send_ary
411
483
  m = message(m) {
@@ -430,7 +502,7 @@ module Minitest
430
502
  def assert_throws sym, msg = nil
431
503
  default = "Expected #{mu_pp(sym)} to have been thrown"
432
504
  caught = true
433
- catch(sym) do
505
+ value = catch(sym) do
434
506
  begin
435
507
  yield
436
508
  rescue ThreadError => e # wtf?!? 1.8 + threads == suck
@@ -446,6 +518,11 @@ module Minitest
446
518
  end
447
519
 
448
520
  assert caught, message(msg) { default }
521
+ value
522
+ rescue Assertion
523
+ raise
524
+ rescue => e
525
+ raise UnexpectedError, e
449
526
  end
450
527
 
451
528
  ##
@@ -514,10 +591,13 @@ module Minitest
514
591
 
515
592
  return captured_stdout.read, captured_stderr.read
516
593
  ensure
517
- captured_stdout.unlink
518
- captured_stderr.unlink
519
594
  $stdout.reopen orig_stdout
520
595
  $stderr.reopen orig_stderr
596
+
597
+ orig_stdout.close
598
+ orig_stderr.close
599
+ captured_stdout.close!
600
+ captured_stderr.close!
521
601
  end
522
602
  end
523
603
  end
@@ -537,7 +617,16 @@ module Minitest
537
617
  end
538
618
 
539
619
  ##
540
- # 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+.
541
630
 
542
631
  def flunk msg = nil
543
632
  msg ||= "Epic Fail!"
@@ -567,7 +656,7 @@ module Minitest
567
656
 
568
657
  def refute test, msg = nil
569
658
  msg ||= message { "Expected #{mu_pp(test)} to not be truthy" }
570
- not assert !test, msg
659
+ assert !test, msg
571
660
  end
572
661
 
573
662
  ##
@@ -659,6 +748,30 @@ module Minitest
659
748
  refute obj.nil?, msg
660
749
  end
661
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
+
662
775
  ##
663
776
  # Fails if +o1+ is not +op+ +o2+. Eg:
664
777
  #
@@ -671,6 +784,14 @@ module Minitest
671
784
  refute o1.__send__(op, o2), msg
672
785
  end
673
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
+
674
795
  ##
675
796
  # For testing with predicates.
676
797
  #
@@ -687,11 +808,12 @@ module Minitest
687
808
 
688
809
  ##
689
810
  # Fails if +obj+ responds to the message +meth+.
811
+ # include_all defaults to false to match Object#respond_to?
690
812
 
691
- def refute_respond_to obj, meth, msg = nil
813
+ def refute_respond_to obj, meth, msg = nil, include_all: false
692
814
  msg = message(msg) { "Expected #{mu_pp(obj)} to not respond to #{meth}" }
693
815
 
694
- refute obj.respond_to?(meth), msg
816
+ refute obj.respond_to?(meth, include_all), msg
695
817
  end
696
818
 
697
819
  ##
@@ -710,10 +832,22 @@ module Minitest
710
832
  # gets listed at the end of the run but doesn't cause a failure
711
833
  # exit code.
712
834
 
713
- def skip msg = nil, bt = caller
835
+ def skip msg = nil, _ignored = nil
714
836
  msg ||= "Skipped, no message given"
715
837
  @skip = true
716
- 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]
717
851
  end
718
852
 
719
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