Skip to content

Commit ea319f7

Browse files
committed
+ Added Minitest::Compress#compress and added it to UnexpectedError
[git-p4: depot-paths = "//src/minitest/dev/": change = 14004]
1 parent 43d4efc commit ea319f7

File tree

5 files changed

+245
-1
lines changed

5 files changed

+245
-1
lines changed

Manifest.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ lib/minitest.rb
99
lib/minitest/assertions.rb
1010
lib/minitest/autorun.rb
1111
lib/minitest/benchmark.rb
12+
lib/minitest/compress.rb
1213
lib/minitest/expectations.rb
1314
lib/minitest/hell.rb
1415
lib/minitest/mock.rb

lib/minitest.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
require "optparse"
2-
require "minitest/parallel"
32
require "stringio"
43
require "etc"
54

5+
require_relative "minitest/parallel"
6+
require_relative "minitest/compress"
7+
68
##
79
# :include: README.rdoc
810

@@ -985,11 +987,21 @@ def result_label # :nodoc:
985987
# Assertion wrapping an unexpected error that was raised during a run.
986988

987989
class UnexpectedError < Assertion
990+
include Minitest::Compress
991+
988992
# TODO: figure out how to use `cause` instead
989993
attr_accessor :error # :nodoc:
990994

991995
def initialize error # :nodoc:
992996
super "Unexpected exception"
997+
998+
if SystemStackError === error then
999+
bt = error.backtrace
1000+
new_bt = compress bt
1001+
error = error.exception "#{bt.size} -> #{new_bt.size}"
1002+
error.set_backtrace new_bt
1003+
end
1004+
9931005
self.error = error
9941006
end
9951007

lib/minitest/compress.rb

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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+
[
68+
" +->> #{n} cycles of #{lines.size} lines:",
69+
*lines.map { |s| " | #{s}" },
70+
" +-<<",
71+
]
72+
else
73+
lines
74+
end
75+
}
76+
end
77+
78+
ary
79+
end
80+
end
81+
end

test/minitest/test_minitest_reporter.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,25 @@ def error_test
4848
@et
4949
end
5050

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+
5170
def fail_test
5271
unless defined? @ft then
5372
@ft = Minitest::Test.new(:woot)
@@ -314,6 +333,42 @@ def test_report_error
314333
assert_equal exp, normalize_output(io.string)
315334
end
316335

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
365+
366+
1 runs, 0 assertions, 0 failures, 1 errors, 0 skips
367+
EOM
368+
369+
assert_equal exp, normalize_output(io.string)
370+
end
371+
317372
def test_report_skipped
318373
r.start
319374
r.record skip_test

test/minitest/test_minitest_test.rb

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,3 +1280,98 @@ def test_method
12801280
end
12811281
end
12821282
end
1283+
1284+
class TestUnexpectedError < Minitest::Test
1285+
def assert_compress exp, input
1286+
e = Minitest::UnexpectedError.new RuntimeError.new
1287+
1288+
exp = exp.lines.map(&:chomp) if String === exp
1289+
act = e.compress input
1290+
1291+
assert_equal exp, act
1292+
end
1293+
1294+
ACT1 = %w[ a b c b c b c b c d ]
1295+
1296+
def test_normal
1297+
assert_compress <<~EXP, %w[ a b c b c b c b c d ]
1298+
a
1299+
+->> 4 cycles of 2 lines:
1300+
| b
1301+
| c
1302+
+-<<
1303+
d
1304+
EXP
1305+
end
1306+
1307+
def test_normal2
1308+
assert_compress <<~EXP, %w[ a b c b c b c b c ]
1309+
a
1310+
+->> 4 cycles of 2 lines:
1311+
| b
1312+
| c
1313+
+-<<
1314+
EXP
1315+
end
1316+
1317+
def test_longer_c_than_b
1318+
# the extra c in the front makes the overall length longer sorting it first
1319+
assert_compress <<~EXP, %w[ c a b c b c b c b c b d ]
1320+
c
1321+
a
1322+
b
1323+
+->> 4 cycles of 2 lines:
1324+
| c
1325+
| b
1326+
+-<<
1327+
d
1328+
EXP
1329+
end
1330+
1331+
def test_1_line_cycles
1332+
assert_compress <<~EXP, %w[ c a b c b c b c b c b b b d ]
1333+
c
1334+
a
1335+
+->> 4 cycles of 2 lines:
1336+
| b
1337+
| c
1338+
+-<<
1339+
+->> 3 cycles of 1 lines:
1340+
| b
1341+
+-<<
1342+
d
1343+
EXP
1344+
end
1345+
1346+
def test_sanity3
1347+
pre = ("aa".."am").to_a
1348+
mid = ("a".."z").to_a * 67
1349+
post = ("aa".."am").to_a
1350+
ary = pre + mid + post
1351+
1352+
exp = pre +
1353+
[" +->> 67 cycles of 26 lines:"] +
1354+
("a".."z").map { |s| " | #{s}" } +
1355+
[" +-<<"] +
1356+
post
1357+
1358+
assert_compress exp, ary
1359+
end
1360+
1361+
def test_absurd_patterns
1362+
skip "NOOOO!!! but I don't care enough right now."
1363+
1364+
assert_compress <<~EXP, %w[ a b c b c a b c b c a b c ]
1365+
+->> 2 cycles of 5 lines:
1366+
| a
1367+
| +->> 2 cycles of 2 lines:
1368+
| | b
1369+
| | c
1370+
| +-<<
1371+
+-<<
1372+
a
1373+
b
1374+
c
1375+
EXP
1376+
end
1377+
end

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy