minitest-bisect 1.5.1 → 1.7.0
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/History.rdoc +26 -0
- data/Manifest.txt +4 -18
- data/README.rdoc +48 -8
- data/Rakefile +26 -9
- data/bin/minitest_bisect +1 -1
- data/example.rb +59 -0
- data/{example-many/helper.rb → example_helper.rb} +13 -4
- data/example_inverse.rb +53 -0
- data/example_many.rb +53 -0
- data/lib/minitest/bisect.rb +134 -89
- data/lib/minitest/find_minimal_combination.rb +8 -5
- data/test/minitest/test_bisect.rb +1 -12
- data/test/minitest/test_find_minimal_combination.rb +87 -74
- data.tar.gz.sig +1 -3
- metadata +22 -35
- metadata.gz.sig +0 -0
- data/example/helper.rb +0 -25
- data/example/test_bad1.rb +0 -4
- data/example/test_bad2.rb +0 -4
- data/example/test_bad3.rb +0 -4
- data/example/test_bad4.rb +0 -4
- data/example/test_bad5.rb +0 -4
- data/example/test_bad6.rb +0 -4
- data/example/test_bad7.rb +0 -4
- data/example/test_bad8.rb +0 -4
- data/example-many/test_bad1.rb +0 -4
- data/example-many/test_bad2.rb +0 -4
- data/example-many/test_bad3.rb +0 -4
- data/example-many/test_bad4.rb +0 -4
- data/example-many/test_bad5.rb +0 -4
- data/example-many/test_bad6.rb +0 -4
- data/example-many/test_bad7.rb +0 -4
- data/example-many/test_bad8.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52bae214fa2f36196af3ec54139c2bec839de200e8dee1015f916b56a666a69b
|
4
|
+
data.tar.gz: 15a204ca83b0da8a8773e565f3bc40c439713edacef4a1d66435f2f9938edf6e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12c4227163ccd825b06f3c745bafa5a59725327e8b313c68a99cc732cea488c1cdda723c1e229d6ac50683e669594820a643c6d2bd0c5ac17867e75397969d10
|
7
|
+
data.tar.gz: '09994a51e53f704d642d27da154c835ba99b39bfbc2ddefa98e77a4357abfe062084628b6fb8c5efd9b596a641e562e595b3b0f80e196997d8bba5002c1448e5'
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/History.rdoc
CHANGED
@@ -1,3 +1,29 @@
|
|
1
|
+
=== 1.7.0 / 2023-07-06
|
2
|
+
|
3
|
+
* 1 major enhancement:
|
4
|
+
|
5
|
+
* Extend bisect_methods to do "inverse" run (eg false positives) via -n=/RE/ argument.
|
6
|
+
|
7
|
+
* 4 minor enhancements:
|
8
|
+
|
9
|
+
* Added example_inverse.rb
|
10
|
+
* Collapsed examples from directories to individual files.
|
11
|
+
* Refactor: push command generation from #run down to #bisect_methods.
|
12
|
+
* build_files_cmd no longer calls reset (zero effect change).
|
13
|
+
|
14
|
+
* 4 bug fixes:
|
15
|
+
|
16
|
+
* 100% documentation coverage.
|
17
|
+
* Fix server process ID to be a string to match rest of args.
|
18
|
+
* Fix shebang cmd on bin/minitest_bisect.
|
19
|
+
* Print known bad methods with found culprit methods in final run.
|
20
|
+
|
21
|
+
=== 1.6.0 / 2022-05-23
|
22
|
+
|
23
|
+
* 1 minor enhancement:
|
24
|
+
|
25
|
+
* Removed #bisect_files and mode accessor. Just deal with methods only for now.
|
26
|
+
|
1
27
|
=== 1.5.1 / 2019-09-22
|
2
28
|
|
3
29
|
* 1 bug fix:
|
data/Manifest.txt
CHANGED
@@ -4,24 +4,10 @@ Manifest.txt
|
|
4
4
|
README.rdoc
|
5
5
|
Rakefile
|
6
6
|
bin/minitest_bisect
|
7
|
-
example
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
example-many/test_bad4.rb
|
12
|
-
example-many/test_bad5.rb
|
13
|
-
example-many/test_bad6.rb
|
14
|
-
example-many/test_bad7.rb
|
15
|
-
example-many/test_bad8.rb
|
16
|
-
example/helper.rb
|
17
|
-
example/test_bad1.rb
|
18
|
-
example/test_bad2.rb
|
19
|
-
example/test_bad3.rb
|
20
|
-
example/test_bad4.rb
|
21
|
-
example/test_bad5.rb
|
22
|
-
example/test_bad6.rb
|
23
|
-
example/test_bad7.rb
|
24
|
-
example/test_bad8.rb
|
7
|
+
example.rb
|
8
|
+
example_helper.rb
|
9
|
+
example_inverse.rb
|
10
|
+
example_many.rb
|
25
11
|
lib/minitest/bisect.rb
|
26
12
|
lib/minitest/find_minimal_combination.rb
|
27
13
|
test/minitest/test_bisect.rb
|
data/README.rdoc
CHANGED
@@ -19,10 +19,13 @@ what minitest-bisect does best.
|
|
19
19
|
|
20
20
|
== FEATURES/PROBLEMS:
|
21
21
|
|
22
|
-
*
|
23
|
-
|
24
|
-
*
|
25
|
-
|
22
|
+
* normal mode: runs your tests with a fixed seed and minimizes the
|
23
|
+
tests to find a minimal set to reproduce the failure.
|
24
|
+
* inverted mode: runs your tests with a fixed seed and a starting
|
25
|
+
failure and minimizes the tests to find a minimal set to reproduce
|
26
|
+
the false positive.
|
27
|
+
* Includes Enumerable#find_minimal_combination and
|
28
|
+
#find_minimal_combination_and_count.
|
26
29
|
|
27
30
|
== SYNOPSIS:
|
28
31
|
|
@@ -94,8 +97,7 @@ So, you run the tests again, but this time with minitest_bisect.
|
|
94
97
|
Provide the seed given in the failure so the tests always run in the
|
95
98
|
same order and reproduce every time.
|
96
99
|
|
97
|
-
minitest_bisect will
|
98
|
-
turn around and minimize the number of methods.
|
100
|
+
minitest_bisect will minimize the number of methods.
|
99
101
|
|
100
102
|
% minitest_bisect example --seed 314
|
101
103
|
reproducing... in 203.83 sec
|
@@ -114,7 +116,7 @@ turn around and minimize the number of methods.
|
|
114
116
|
|
115
117
|
Minimal methods found in 11 steps:
|
116
118
|
|
117
|
-
Culprit methods: ["TestBad1#test_bad1_1"]
|
119
|
+
Culprit methods: ["TestBad1#test_bad1_1", "TestBad4#test_bad4_4"]
|
118
120
|
|
119
121
|
ruby -Itest:lib -e 'require "./example/test_bad1.rb" ; require "./example/test_bad2.rb" ; require "./example/test_bad3.rb" ; require "./example/test_bad4.rb" ; require "./example/test_bad5.rb" ; require "./example/test_bad6.rb" ; require "./example/test_bad7.rb" ; require "./example/test_bad8.rb"' -- --seed 314 -n "/^(?:TestBad1#(?:test_bad1_1)|TestBad4#(?:test_bad4_4))$/"
|
120
122
|
|
@@ -148,9 +150,47 @@ have and how long they take, the minimization might take a long time,
|
|
148
150
|
but each iteration can reduce the number of tests so it should get
|
149
151
|
quicker with time.
|
150
152
|
|
153
|
+
=== But sometimes, it fails:
|
154
|
+
|
155
|
+
Sometimes a test fails by itself consistently but passes most of the
|
156
|
+
time in CI. This is a false-positive in that the test SHOULD be
|
157
|
+
failing all the time but some other test is causing a side-effect that
|
158
|
+
it making it pass. This is sometimes harder to track down and reason
|
159
|
+
about. minitest-bisect now has an "inverted" mode, where you run as
|
160
|
+
normal but point it at a failing test by name. It will then validate
|
161
|
+
that the test fails by itself but passes when run in full with the
|
162
|
+
given seed. Then it tries to isolate and answer "what is the minimal
|
163
|
+
combination of tests to run to # make this test pass?". For example:
|
164
|
+
|
165
|
+
% minitest_bisect example --seed=3 -n="/failing_test_name_regexp/"
|
166
|
+
|
167
|
+
reproducing w/ scoped failure (inverted run!)... in 0.06 sec
|
168
|
+
reproducing false positive... in 0.06 sec
|
169
|
+
# of culprit methods: 2 in 0.06 sec
|
170
|
+
# of culprit methods: 1 in 0.06 sec
|
171
|
+
# of culprit methods: 1 in 0.06 sec
|
172
|
+
|
173
|
+
Minimal methods found in 3 steps:
|
174
|
+
|
175
|
+
Culprit methods: ["TestBad1#test_make_it_go", "TestBad1#test_fail_without"]
|
176
|
+
|
177
|
+
/Users/ryan/.rubies/ruby-3.2.2/bin/ruby -Itest:lib -Ilib -e 'require "./example"' -- --seed 3 -n "/^(?:TestBad1#(?:test_make_it_go|test_fail_without))$/"
|
178
|
+
|
179
|
+
Final reproduction:
|
180
|
+
|
181
|
+
Run options: --seed 3 -n "/^(?:TestBad1#(?:test_make_it_go|test_fail_without))$/"
|
182
|
+
|
183
|
+
# Running:
|
184
|
+
|
185
|
+
..
|
186
|
+
|
187
|
+
Finished in 0.000369s, 5420.0544 runs/s, 0.0000 assertions/s.
|
188
|
+
|
189
|
+
2 runs, 0 assertions, 0 failures, 0 errors, 0 skips
|
190
|
+
|
151
191
|
== REQUIREMENTS:
|
152
192
|
|
153
|
-
* minitest 5
|
193
|
+
* minitest 5+
|
154
194
|
|
155
195
|
== INSTALL:
|
156
196
|
|
data/Rakefile
CHANGED
@@ -40,11 +40,7 @@ def banner text
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def run cmd
|
43
|
-
sh cmd do end
|
44
|
-
end
|
45
|
-
|
46
|
-
def req glob
|
47
|
-
Dir["#{glob}.rb"].map { |s| "require #{s.inspect}" }.join ";"
|
43
|
+
sh cmd do end # block form lets it fail w/o halting rake
|
48
44
|
end
|
49
45
|
|
50
46
|
task :repro => :isolate do
|
@@ -56,10 +52,10 @@ task :repro => :isolate do
|
|
56
52
|
ruby = "ruby -I.:lib"
|
57
53
|
|
58
54
|
banner "Original run that causes the test order dependency bug"
|
59
|
-
run "#{ruby}
|
55
|
+
run "#{ruby} example.rb --seed 1"
|
60
56
|
|
61
57
|
banner "Reduce the problem down to the minimal reproduction"
|
62
|
-
run "#{ruby} bin/minitest_bisect -Ilib --seed
|
58
|
+
run "#{ruby} bin/minitest_bisect -Ilib --seed 1 example.rb"
|
63
59
|
end
|
64
60
|
|
65
61
|
task :many => :isolate do
|
@@ -71,10 +67,31 @@ task :many => :isolate do
|
|
71
67
|
ruby = "ruby -I.:lib"
|
72
68
|
|
73
69
|
banner "Original run that causes the test order dependency bug"
|
74
|
-
run "#{ruby}
|
70
|
+
run "#{ruby} ./example_many.rb --seed 2"
|
75
71
|
|
76
72
|
banner "Reduce the problem down to the minimal reproduction"
|
77
|
-
run "#{ruby} bin/minitest_bisect -Ilib --seed
|
73
|
+
run "#{ruby} bin/minitest_bisect -Ilib --seed 2 example_many.rb"
|
74
|
+
end
|
75
|
+
|
76
|
+
task :inverse => :isolate do
|
77
|
+
unless ENV.key? "SLEEP" then
|
78
|
+
warn "NOTE: Defaulting to sleeping 0.01 seconds per test."
|
79
|
+
warn "NOTE: Use SLEEP=0 to disable or any other value to simulate your tests."
|
80
|
+
end
|
81
|
+
|
82
|
+
ruby = "ruby -I.:lib"
|
83
|
+
|
84
|
+
banner "Original run that passes (seed 1)"
|
85
|
+
run "#{ruby} example_inverse.rb --seed 1"
|
86
|
+
|
87
|
+
banner "Original run that *looks like* a test order dependency bug (seed 3)"
|
88
|
+
run "#{ruby} example_inverse.rb --seed 3"
|
89
|
+
|
90
|
+
banner "BAD bisection (tests fail by themselves) (seed 3)"
|
91
|
+
run "#{ruby} bin/minitest_bisect -Ilib --seed 3 example_inverse.rb"
|
92
|
+
|
93
|
+
banner "Reduce the passing run down to the minimal reproduction (seed 1)"
|
94
|
+
run "#{ruby} bin/minitest_bisect -Ilib --seed 1 example_inverse.rb -n=/TestBad4#test_bad4_4$/"
|
78
95
|
end
|
79
96
|
|
80
97
|
# vim: syntax=ruby
|
data/bin/minitest_bisect
CHANGED
data/example.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require_relative "example_helper"
|
4
|
+
|
5
|
+
# 800 tests, test test_bad4_4 fails if test_bad1_1 runs before it
|
6
|
+
TestBad1 = create_test 1, 100, 1 => :infect
|
7
|
+
TestBad2 = create_test 2, 100
|
8
|
+
TestBad3 = create_test 3, 100
|
9
|
+
TestBad4 = create_test 4, 100, 4 => :flunk
|
10
|
+
TestBad5 = create_test 5, 100
|
11
|
+
TestBad6 = create_test 6, 100
|
12
|
+
TestBad7 = create_test 7, 100
|
13
|
+
TestBad8 = create_test 8, 100
|
14
|
+
|
15
|
+
# seed 1 == one fail
|
16
|
+
# seed 3 == all pass
|
17
|
+
|
18
|
+
# % SLEEP=0 ruby ./example.rb --seed 1
|
19
|
+
#
|
20
|
+
# and it fails, as expected. So we run it through minitest_bisect and see:
|
21
|
+
#
|
22
|
+
# reproducing... in 0.14 sec
|
23
|
+
# verifying... in 0.06 sec
|
24
|
+
# # of culprit methods: 128 in 0.08 sec
|
25
|
+
# # of culprit methods: 64 in 0.07 sec
|
26
|
+
# # of culprit methods: 64 in 0.07 sec
|
27
|
+
# # of culprit methods: 32 in 0.06 sec
|
28
|
+
# # of culprit methods: 16 in 0.06 sec
|
29
|
+
# # of culprit methods: 8 in 0.06 sec
|
30
|
+
# # of culprit methods: 4 in 0.06 sec
|
31
|
+
# # of culprit methods: 4 in 0.06 sec
|
32
|
+
# # of culprit methods: 2 in 0.06 sec
|
33
|
+
# # of culprit methods: 1 in 0.06 sec
|
34
|
+
#
|
35
|
+
# Minimal methods found in 10 steps:
|
36
|
+
#
|
37
|
+
# Culprit methods: ["TestBad1#test_bad1_1", "TestBad4#test_bad4_4"]
|
38
|
+
#
|
39
|
+
# /Users/ryan/.rubies/ruby-3.2.2/bin/ruby -Itest:lib -e 'require "././example.rb"' -- --seed 1 -n "/^(?:TestBad1#(?:test_bad1_1)|TestBad4#(?:test_bad4_4))$/"
|
40
|
+
#
|
41
|
+
# Final reproduction:
|
42
|
+
#
|
43
|
+
# Run options: --seed 1 -n "/^(?:TestBad1#(?:test_bad1_1)|TestBad4#(?:test_bad4_4))$/"
|
44
|
+
#
|
45
|
+
# # Running:
|
46
|
+
#
|
47
|
+
# .F
|
48
|
+
#
|
49
|
+
# Finished in 0.001349s, 1482.5797 runs/s, 741.2898 assertions/s.
|
50
|
+
#
|
51
|
+
# 1) Failure:
|
52
|
+
# TestBad4#test_bad4_4 [/Users/ryan/Work/p4/zss/src/minitest-bisect/dev/example.rb:20]:
|
53
|
+
# muahahaha order dependency bug!
|
54
|
+
#
|
55
|
+
# 2 runs, 1 assertions, 1 failures, 0 errors, 0 skips
|
56
|
+
#
|
57
|
+
# and that's all there is to it! You now know that test_bad4_4 fails
|
58
|
+
# only when paired with test_bad1_1 before it. You can now debug the
|
59
|
+
# two methods and see what they're both modifying/dependent on.
|
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
require "minitest/autorun"
|
2
|
+
|
3
|
+
$good = true
|
4
|
+
$bomb = 0
|
2
5
|
|
3
6
|
def create_test suffix, n_methods, bad_methods = {}
|
4
7
|
raise ArgumentError, "Bad args" if Hash === n_methods
|
@@ -12,10 +15,16 @@ def create_test suffix, n_methods, bad_methods = {}
|
|
12
15
|
sleep delay if delay > 0
|
13
16
|
|
14
17
|
case bad_methods[n]
|
15
|
-
when
|
16
|
-
|
18
|
+
when :flunk then
|
19
|
+
flunk "muahahaha order dependency bug!" unless $good
|
20
|
+
when :infect then
|
21
|
+
$good = false
|
22
|
+
when :fix then
|
23
|
+
$good = true
|
24
|
+
when :tick then
|
25
|
+
$bomb += 1
|
17
26
|
when Integer then
|
18
|
-
flunk "muahahaha order dependency bug!" if $
|
27
|
+
flunk "muahahaha order dependency bug!" if $bomb >= bad_methods[n]
|
19
28
|
else
|
20
29
|
assert true
|
21
30
|
end
|
data/example_inverse.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require_relative "example_helper"
|
4
|
+
|
5
|
+
$good = false
|
6
|
+
|
7
|
+
# 800 tests, test test_bad4_4 passes if test_bad1_1 runs before it
|
8
|
+
TestBad1 = create_test 1, 100, 1 => :fix
|
9
|
+
TestBad2 = create_test 2, 100
|
10
|
+
TestBad3 = create_test 3, 100
|
11
|
+
TestBad4 = create_test 4, 100, 4 => :flunk
|
12
|
+
TestBad5 = create_test 5, 100
|
13
|
+
TestBad6 = create_test 6, 100
|
14
|
+
TestBad7 = create_test 7, 100
|
15
|
+
TestBad8 = create_test 8, 100
|
16
|
+
|
17
|
+
# seed 1 == all pass
|
18
|
+
# seed 3 == one fail
|
19
|
+
|
20
|
+
# UNLIKE the scenario spelled out in example.rb...
|
21
|
+
#
|
22
|
+
# % SLEEP=0 ruby ./example_inverse.rb --seed 1
|
23
|
+
#
|
24
|
+
# passes, but
|
25
|
+
#
|
26
|
+
# % SLEEP=0 ruby ./example_inverse.rb --seed 3
|
27
|
+
#
|
28
|
+
# has 1 failure! So we run that through minitest_bisect:
|
29
|
+
#
|
30
|
+
# % SLEEP=0 ruby -Ilib bin/minitest_bisect ./example_inverse.rb --seed 3
|
31
|
+
#
|
32
|
+
# and see:
|
33
|
+
#
|
34
|
+
# Tests fail by themselves. This may not be an ordering issue.
|
35
|
+
#
|
36
|
+
# followed by a result that doesn't actually lead to the failure.
|
37
|
+
#
|
38
|
+
# Doing a normal run of minitest_bisect in this scenario doesn't
|
39
|
+
# detect anything actually relevant. This is because we're not dealing
|
40
|
+
# with a false *negative*, we're dealing with a false *positive*. The
|
41
|
+
# question is "what makes this test pass?". So, we run it again with
|
42
|
+
# the passing seed but this time point it at the failing test by name:
|
43
|
+
#
|
44
|
+
# % SLEEP=0 ruby -Ilib bin/minitest_bisect ./example_inverse.rb --seed 1 -n=/TestBad4#test_bad4_4$/
|
45
|
+
#
|
46
|
+
# and it outputs:
|
47
|
+
#
|
48
|
+
# Culprit methods: ["TestBad1#test_bad1_1", "TestBad4#test_bad4_4"]
|
49
|
+
|
50
|
+
# and shows a minimized run with the 2 passing tests. You now know
|
51
|
+
# that test_bad4_4 passes only when paired with test_bad1_1 before it.
|
52
|
+
# You can now debug the two methods and see what they're both
|
53
|
+
# modifying/dependent on.
|
data/example_many.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require_relative "example_helper"
|
4
|
+
|
5
|
+
TestBad1 = create_test 1, 100, 1 => :tick
|
6
|
+
TestBad2 = create_test 2, 100
|
7
|
+
TestBad3 = create_test 3, 100, 72 => :tick
|
8
|
+
TestBad4 = create_test 4, 100
|
9
|
+
TestBad5 = create_test 5, 100, 17 => :tick
|
10
|
+
TestBad6 = create_test 6, 100
|
11
|
+
TestBad7 = create_test 7, 100
|
12
|
+
TestBad8 = create_test 8, 100, 43 => 3
|
13
|
+
|
14
|
+
# seed 1 == all pass
|
15
|
+
# seed 2 == one fail
|
16
|
+
|
17
|
+
# % ruby example_many.rb --seed 2
|
18
|
+
|
19
|
+
# and it fails, as expected. So we run it through minitest_bisect and see:
|
20
|
+
|
21
|
+
# % ruby -Ilib bin/minitest_bisect example_many.rb --seed 2
|
22
|
+
|
23
|
+
# reproducing... in 0.15 sec
|
24
|
+
# verifying... in 0.06 sec
|
25
|
+
# # of culprit methods: 256 in 0.09 sec
|
26
|
+
# ... 82 more bisections ...
|
27
|
+
# # of culprit methods: 3 in 0.06 sec
|
28
|
+
#
|
29
|
+
# Minimal methods found in 84 steps:
|
30
|
+
#
|
31
|
+
# Culprit methods: ["TestBad3#test_bad3_72", "TestBad5#test_bad5_17", "TestBad1#test_bad1_1", "TestBad8#test_bad8_43"]
|
32
|
+
#
|
33
|
+
# /Users/ryan/.rubies/ruby-3.2.2/bin/ruby -Itest:lib -e 'require "./example_many.rb"' -- --seed 2 -n "/^(?:TestBad3#(?:test_bad3_72)|TestBad5#(?:test_bad5_17)|TestBad1#(?:test_bad1_1)|TestBad8#(?:test_bad8_43))$/"
|
34
|
+
#
|
35
|
+
# Final reproduction:
|
36
|
+
#
|
37
|
+
# Run options: --seed 2 -n "/^(?:TestBad3#(?:test_bad3_72)|TestBad5#(?:test_bad5_17)|TestBad1#(?:test_bad1_1)|TestBad8#(?:test_bad8_43))$/"
|
38
|
+
#
|
39
|
+
# # Running:
|
40
|
+
#
|
41
|
+
# ...F
|
42
|
+
#
|
43
|
+
# Finished in 0.001404s, 2849.0028 runs/s, 712.2507 assertions/s.
|
44
|
+
#
|
45
|
+
# 1) Failure:
|
46
|
+
# TestBad8#test_bad8_43 [/Users/ryan/Work/p4/zss/src/minitest-bisect/dev/example_many.rb:20]:
|
47
|
+
# muahahaha order dependency bug!
|
48
|
+
#
|
49
|
+
# 4 runs, 1 assertions, 1 failures, 0 errors, 0 skips
|
50
|
+
#
|
51
|
+
# and that's all there is to it! You now know that the failing test
|
52
|
+
# fails only when paired with the other 3 tests before it. You can now
|
53
|
+
# debug the 4 methods and see what they're all modifying/dependent on.
|
data/lib/minitest/bisect.rb
CHANGED
@@ -4,10 +4,15 @@ require "shellwords"
|
|
4
4
|
require "rbconfig"
|
5
5
|
require "path_expander"
|
6
6
|
|
7
|
+
module Minitest; end # :nodoc:
|
8
|
+
|
9
|
+
##
|
10
|
+
# Minitest::Bisect helps you isolate and debug random test failures.
|
11
|
+
|
7
12
|
class Minitest::Bisect
|
8
|
-
VERSION = "1.
|
13
|
+
VERSION = "1.7.0" # :nodoc:
|
9
14
|
|
10
|
-
class PathExpander < ::PathExpander
|
15
|
+
class PathExpander < ::PathExpander # :nodoc:
|
11
16
|
TEST_GLOB = "**/{test_*,*_test,spec_*,*_spec}.rb" # :nodoc:
|
12
17
|
|
13
18
|
attr_accessor :rb_flags
|
@@ -39,7 +44,7 @@ class Minitest::Bisect
|
|
39
44
|
end
|
40
45
|
|
41
46
|
mtbv = ENV["MTB_VERBOSE"].to_i
|
42
|
-
SHH = case
|
47
|
+
SHH = case # :nodoc:
|
43
48
|
when mtbv == 1 then " > /dev/null"
|
44
49
|
when mtbv >= 2 then nil
|
45
50
|
else " > /dev/null 2>&1"
|
@@ -51,21 +56,48 @@ class Minitest::Bisect
|
|
51
56
|
RbConfig::CONFIG['ruby_install_name'] +
|
52
57
|
RbConfig::CONFIG['EXEEXT']).sub(/.*\s.*/m, '"\&"')
|
53
58
|
|
54
|
-
|
59
|
+
##
|
60
|
+
# True if this run has seen a failure.
|
61
|
+
|
62
|
+
attr_accessor :tainted
|
55
63
|
alias :tainted? :tainted
|
56
64
|
|
65
|
+
##
|
66
|
+
# Failures seen in this run. Shape:
|
67
|
+
#
|
68
|
+
# {"file.rb"=>{"Class"=>["test_method1", "test_method2"] ...} ...}
|
69
|
+
|
70
|
+
attr_accessor :failures
|
71
|
+
|
72
|
+
##
|
73
|
+
# An array of tests seen so far. NOT cleared by #reset.
|
74
|
+
|
75
|
+
attr_accessor :culprits
|
76
|
+
|
77
|
+
attr_accessor :seen_bad # :nodoc:
|
78
|
+
|
79
|
+
##
|
80
|
+
# Top-level runner. Instantiate and call +run+, handling exceptions.
|
81
|
+
|
57
82
|
def self.run files
|
58
83
|
new.run files
|
59
84
|
rescue => e
|
60
85
|
warn e.message
|
86
|
+
warn "Try running with MTB_VERBOSE=2 to verify."
|
61
87
|
exit 1
|
62
88
|
end
|
63
89
|
|
90
|
+
##
|
91
|
+
# Instantiate a new Bisect.
|
92
|
+
|
64
93
|
def initialize
|
65
94
|
self.culprits = []
|
66
95
|
self.failures = Hash.new { |h, k| h[k] = Hash.new { |h2, k2| h2[k2] = [] } }
|
67
96
|
end
|
68
97
|
|
98
|
+
##
|
99
|
+
# Reset per-bisect-run variables.
|
100
|
+
|
69
101
|
def reset
|
70
102
|
self.seen_bad = false
|
71
103
|
self.tainted = false
|
@@ -73,23 +105,23 @@ class Minitest::Bisect
|
|
73
105
|
# not clearing culprits on purpose
|
74
106
|
end
|
75
107
|
|
108
|
+
##
|
109
|
+
# Instance-level runner. Handles Minitest::Server, argument
|
110
|
+
# processing, and invoking +bisect_methods+.
|
111
|
+
|
76
112
|
def run args
|
77
113
|
Minitest::Server.run self
|
78
114
|
|
79
115
|
cmd = nil
|
80
116
|
|
81
|
-
|
82
|
-
|
83
|
-
expander = Minitest::Bisect::PathExpander.new mt_flags
|
117
|
+
mt_flags = args.dup
|
118
|
+
expander = Minitest::Bisect::PathExpander.new mt_flags
|
84
119
|
|
85
|
-
|
86
|
-
|
87
|
-
|
120
|
+
files = expander.process
|
121
|
+
rb_flags = expander.rb_flags
|
122
|
+
mt_flags += ["--server", $$.to_s]
|
88
123
|
|
89
|
-
|
90
|
-
else
|
91
|
-
cmd = bisect_methods bisect_files args
|
92
|
-
end
|
124
|
+
cmd = bisect_methods files, rb_flags, mt_flags
|
93
125
|
|
94
126
|
puts "Final reproduction:"
|
95
127
|
puts
|
@@ -99,70 +131,87 @@ class Minitest::Bisect
|
|
99
131
|
Minitest::Server.stop
|
100
132
|
end
|
101
133
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
134
|
+
##
|
135
|
+
# Normal: find "what is the minimal combination of tests to run to
|
136
|
+
# make X fail?"
|
137
|
+
#
|
138
|
+
# Run with: minitest_bisect ... --seed=N
|
139
|
+
#
|
140
|
+
# 1. Verify the failure running normally with the seed.
|
141
|
+
# 2. If no failure, punt.
|
142
|
+
# 3. If no passing tests before failure, punt. (No culprits == no debug)
|
143
|
+
# 4. Verify the failure doesn't fail in isolation.
|
144
|
+
# 5. If it still fails by itself, warn that it might not be an ordering
|
145
|
+
# issue.
|
146
|
+
# 6. Cull all tests after the failure, they're not involved.
|
147
|
+
# 7. Bisect the culprits + bad until you find a minimal combo that fails.
|
148
|
+
# 8. Display minimal combo by running one last time.
|
149
|
+
#
|
150
|
+
# Inverted: find "what is the minimal combination of tests to run to
|
151
|
+
# make this test pass?"
|
152
|
+
#
|
153
|
+
# Run with: minitest_bisect ... --seed=N -n="/failing_test_name_regexp/"
|
154
|
+
#
|
155
|
+
# 1. Verify the failure by running normally w/ the seed and -n=/.../
|
156
|
+
# 2. If no failure, punt.
|
157
|
+
# 3. Verify the passing case by running everything.
|
158
|
+
# 4. If failure, punt. This is not a false positive.
|
159
|
+
# 5. Cull all tests after the bad test from #1, they're not involved.
|
160
|
+
# 6. Bisect the culprits + bad until you find a minimal combo that passes.
|
161
|
+
# 7. Display minimal combo by running one last time.
|
162
|
+
|
163
|
+
def bisect_methods files, rb_flags, mt_flags
|
164
|
+
bad_names, mt_flags = mt_flags.partition { |s| s =~ /^(?:-n|--name)/ }
|
165
|
+
normal = bad_names.empty?
|
166
|
+
inverted = !normal
|
167
|
+
|
168
|
+
if inverted then
|
169
|
+
time_it "reproducing w/ scoped failure (inverted run!)...", build_methods_cmd(build_files_cmd(files, rb_flags, mt_flags + bad_names))
|
170
|
+
raise "No failures. Probably not a false positive. Aborting." if failures.empty?
|
171
|
+
bad = map_failures
|
120
172
|
end
|
121
173
|
|
122
|
-
|
123
|
-
puts "Minimal files found in #{count} steps:"
|
124
|
-
puts
|
125
|
-
cmd = build_files_cmd found, rb_flags, mt_flags
|
126
|
-
puts cmd
|
127
|
-
cmd
|
128
|
-
end
|
129
|
-
|
130
|
-
def bisect_methods cmd
|
131
|
-
self.mode = :methods
|
174
|
+
cmd = build_files_cmd(files, rb_flags, mt_flags)
|
132
175
|
|
133
|
-
|
176
|
+
msg = normal ? "reproducing..." : "reproducing false positive..."
|
177
|
+
time_it msg, build_methods_cmd(cmd)
|
134
178
|
|
135
|
-
|
136
|
-
|
137
|
-
|
179
|
+
if normal then
|
180
|
+
raise "Reproduction run passed? Aborting." unless tainted?
|
181
|
+
raise "Verification failed. No culprits? Aborting." if culprits.empty? && seen_bad
|
182
|
+
else
|
183
|
+
raise "Reproduction failed? Not false positive. Aborting." if tainted?
|
184
|
+
raise "Verification failed. No culprits? Aborting." if culprits.empty? || seen_bad
|
138
185
|
end
|
139
186
|
|
140
|
-
|
187
|
+
if normal then
|
188
|
+
bad = map_failures
|
141
189
|
|
142
|
-
|
143
|
-
culprits.empty? && seen_bad
|
190
|
+
time_it "verifying...", build_methods_cmd(cmd, [], bad)
|
144
191
|
|
145
|
-
|
192
|
+
new_bad = map_failures
|
146
193
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
warn "Tests fail by themselves. This may not be an ordering issue."
|
194
|
+
if bad == new_bad then
|
195
|
+
warn "Tests fail by themselves. This may not be an ordering issue."
|
196
|
+
end
|
151
197
|
end
|
152
198
|
|
199
|
+
idx = culprits.index bad.first
|
200
|
+
self.culprits = culprits.take idx+1 if idx # cull tests after bad
|
201
|
+
|
153
202
|
# culprits populated by initial reproduction via minitest/server
|
154
203
|
found, count = culprits.find_minimal_combination_and_count do |test|
|
155
204
|
prompt = "# of culprit methods: #{test.size}"
|
156
205
|
|
157
206
|
time_it prompt, build_methods_cmd(cmd, test, bad)
|
158
207
|
|
159
|
-
|
208
|
+
normal == tainted? # either normal and failed, or inverse and passed
|
160
209
|
end
|
161
210
|
|
162
211
|
puts
|
163
212
|
puts "Minimal methods found in #{count} steps:"
|
164
213
|
puts
|
165
|
-
puts "Culprit methods: %p" % [found]
|
214
|
+
puts "Culprit methods: %p" % [found + bad]
|
166
215
|
puts
|
167
216
|
cmd = build_methods_cmd cmd, found, bad
|
168
217
|
puts cmd.sub(/--server \d+/, "")
|
@@ -170,14 +219,14 @@ class Minitest::Bisect
|
|
170
219
|
cmd
|
171
220
|
end
|
172
221
|
|
173
|
-
def time_it prompt, cmd
|
222
|
+
def time_it prompt, cmd # :nodoc:
|
174
223
|
print prompt
|
175
224
|
t0 = Time.now
|
176
225
|
system "#{cmd} #{SHH}"
|
177
226
|
puts " in %.2f sec" % (Time.now - t0)
|
178
227
|
end
|
179
228
|
|
180
|
-
def map_failures
|
229
|
+
def map_failures # :nodoc:
|
181
230
|
# from: {"file.rb"=>{"Class"=>["test_method1", "test_method2"]}}
|
182
231
|
# to: ["Class#test_method1", "Class#test_method2"]
|
183
232
|
failures.values.map { |h|
|
@@ -185,15 +234,31 @@ class Minitest::Bisect
|
|
185
234
|
}.flatten.sort
|
186
235
|
end
|
187
236
|
|
188
|
-
def build_files_cmd culprits, rb, mt
|
189
|
-
reset
|
190
|
-
|
237
|
+
def build_files_cmd culprits, rb, mt # :nodoc:
|
191
238
|
tests = culprits.flatten.compact.map { |f| %(require "./#{f}") }.join " ; "
|
192
239
|
|
193
240
|
%(#{RUBY} #{rb.shelljoin} -e '#{tests}' -- #{mt.map(&:to_s).shelljoin})
|
194
241
|
end
|
195
242
|
|
196
|
-
def
|
243
|
+
def build_methods_cmd cmd, culprits = [], bad = nil # :nodoc:
|
244
|
+
reset
|
245
|
+
|
246
|
+
if bad then
|
247
|
+
re = build_re culprits + bad
|
248
|
+
|
249
|
+
cmd += " -n \"#{re}\"" if bad
|
250
|
+
end
|
251
|
+
|
252
|
+
if ENV["MTB_VERBOSE"].to_i >= 1 then
|
253
|
+
puts
|
254
|
+
puts cmd
|
255
|
+
puts
|
256
|
+
end
|
257
|
+
|
258
|
+
cmd
|
259
|
+
end
|
260
|
+
|
261
|
+
def build_re bad # :nodoc:
|
197
262
|
re = []
|
198
263
|
|
199
264
|
# bad by class, you perv
|
@@ -213,44 +278,24 @@ class Minitest::Bisect
|
|
213
278
|
"/^(?:#{re})$/"
|
214
279
|
end
|
215
280
|
|
216
|
-
def re_escape str
|
281
|
+
def re_escape str # :nodoc:
|
217
282
|
str.gsub(/([`'"!?&\[\]\(\)\{\}\|\+])/, '\\\\\1')
|
218
283
|
end
|
219
284
|
|
220
|
-
def build_methods_cmd cmd, culprits = [], bad = nil
|
221
|
-
reset
|
222
|
-
|
223
|
-
if bad then
|
224
|
-
re = build_re culprits + bad
|
225
|
-
|
226
|
-
cmd += " -n \"#{re}\"" if bad
|
227
|
-
end
|
228
|
-
|
229
|
-
if ENV["MTB_VERBOSE"].to_i >= 1 then
|
230
|
-
puts
|
231
|
-
puts cmd
|
232
|
-
puts
|
233
|
-
end
|
234
|
-
|
235
|
-
cmd
|
236
|
-
end
|
237
|
-
|
238
285
|
############################################################
|
239
286
|
# Server Methods:
|
240
287
|
|
241
|
-
def minitest_start
|
288
|
+
def minitest_start # :nodoc:
|
242
289
|
self.failures.clear
|
243
290
|
end
|
244
291
|
|
245
|
-
def minitest_result file, klass, method, fails, assertions, time
|
292
|
+
def minitest_result file, klass, method, fails, assertions, time # :nodoc:
|
246
293
|
fails.reject! { |fail| Minitest::Skip === fail }
|
247
294
|
|
248
|
-
if
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
self.seen_bad = true
|
253
|
-
end
|
295
|
+
if fails.empty? then
|
296
|
+
culprits << "#{klass}##{method}" unless seen_bad # UGH
|
297
|
+
else
|
298
|
+
self.seen_bad = true
|
254
299
|
end
|
255
300
|
|
256
301
|
return if fails.empty?
|
@@ -1,10 +1,13 @@
|
|
1
1
|
#!/usr/bin/ruby -w
|
2
2
|
|
3
|
-
|
3
|
+
##
|
4
|
+
# Finds the minimal combination of a collection of items that satisfy
|
5
|
+
# +test+.
|
4
6
|
|
5
7
|
class ComboFinder
|
6
8
|
##
|
7
|
-
# Find the minimal combination of a collection of items that satisfy
|
9
|
+
# Find the minimal combination of a collection of items that satisfy
|
10
|
+
# +test+.
|
8
11
|
#
|
9
12
|
# If you think of the collection as a binary tree, this algorithm
|
10
13
|
# does a breadth first search of the combinations that satisfy
|
@@ -79,11 +82,11 @@ class ComboFinder
|
|
79
82
|
ary
|
80
83
|
end
|
81
84
|
|
82
|
-
def d s = ""
|
85
|
+
def d s = "" # :nodoc:
|
83
86
|
warn s if ENV["MTB_DEBUG"]
|
84
87
|
end
|
85
88
|
|
86
|
-
def cache_result result, data, cache
|
89
|
+
def cache_result result, data, cache # :nodoc:
|
87
90
|
cache[data] = true
|
88
91
|
|
89
92
|
return result if result
|
@@ -103,7 +106,7 @@ class ComboFinder
|
|
103
106
|
end
|
104
107
|
end
|
105
108
|
|
106
|
-
class Array
|
109
|
+
class Array # :nodoc:
|
107
110
|
##
|
108
111
|
# Find the minimal combination of a collection of items that satisfy +test+.
|
109
112
|
|
@@ -8,6 +8,7 @@ class TestMinitest::TestBisect < Minitest::Test
|
|
8
8
|
|
9
9
|
def setup
|
10
10
|
self.bisect = Minitest::Bisect.new
|
11
|
+
bisect.reset
|
11
12
|
end
|
12
13
|
|
13
14
|
def test_class_run
|
@@ -124,9 +125,6 @@ class TestMinitest::TestBisect < Minitest::Test
|
|
124
125
|
end
|
125
126
|
|
126
127
|
def test_minitest_result
|
127
|
-
bisect.mode = :methods
|
128
|
-
bisect.reset
|
129
|
-
|
130
128
|
bisect.minitest_result "file.rb", "TestClass", "test_method", [], 1, 1
|
131
129
|
|
132
130
|
assert_equal false, bisect.tainted
|
@@ -135,9 +133,6 @@ class TestMinitest::TestBisect < Minitest::Test
|
|
135
133
|
end
|
136
134
|
|
137
135
|
def test_minitest_result_skip
|
138
|
-
bisect.mode = :methods
|
139
|
-
bisect.reset
|
140
|
-
|
141
136
|
fail = Minitest::Skip.new("woot")
|
142
137
|
|
143
138
|
bisect.minitest_result "file.rb", "TestClass", "test_method", [fail], 1, 1
|
@@ -148,9 +143,6 @@ class TestMinitest::TestBisect < Minitest::Test
|
|
148
143
|
end
|
149
144
|
|
150
145
|
def test_minitest_result_fail
|
151
|
-
bisect.mode = :methods
|
152
|
-
bisect.reset
|
153
|
-
|
154
146
|
fail = Minitest::Assertion.new "msg"
|
155
147
|
|
156
148
|
bisect.minitest_result "file.rb", "TestClass", "test_method", [fail], 1, 1
|
@@ -163,9 +155,6 @@ class TestMinitest::TestBisect < Minitest::Test
|
|
163
155
|
end
|
164
156
|
|
165
157
|
def test_minitest_result_error
|
166
|
-
bisect.mode = :methods
|
167
|
-
bisect.reset
|
168
|
-
|
169
158
|
fail = Minitest::UnexpectedError.new RuntimeError.new("woot")
|
170
159
|
|
171
160
|
bisect.minitest_result "file.rb", "TestClass", "test_method", [fail], 1, 1
|
@@ -10,93 +10,99 @@ describe Array, :find_minimal_combination do
|
|
10
10
|
lambda { |sample| bad & sample == bad }
|
11
11
|
end
|
12
12
|
|
13
|
+
def record_and_check(tests, *bad)
|
14
|
+
lambda { |test| tests << test.join; bad & test == bad }
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_trials s
|
18
|
+
s.lines.map { |s| s.chomp.sub(/#.*/, '').delete " " }.reject(&:empty?)
|
19
|
+
end
|
20
|
+
|
13
21
|
def assert_steps input, bad, exp
|
14
22
|
tests = []
|
15
23
|
|
16
|
-
found = input.find_minimal_combination
|
17
|
-
tests << test
|
18
|
-
bad & test == bad
|
19
|
-
end
|
24
|
+
found = input.find_minimal_combination(&record_and_check(tests, *bad))
|
20
25
|
|
21
26
|
assert_equal bad, found, "algorithm is bad"
|
22
27
|
|
23
|
-
assert_equal exp, tests
|
28
|
+
assert_equal parse_trials(exp), tests
|
24
29
|
end
|
25
30
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
HEX = "0123456789ABCDEF".chars.to_a
|
32
|
+
|
33
|
+
# lvl collection
|
34
|
+
#
|
35
|
+
# 0 | A
|
36
|
+
# 1 | B C
|
37
|
+
# 2 | D E F G
|
38
|
+
# 3 | H I J K L M N O
|
39
|
+
# |
|
40
|
+
# 4 | 0123456789ABCDEF
|
41
|
+
|
42
|
+
def test_ordering_best_case_1
|
43
|
+
ary = HEX
|
44
|
+
bad = %w[0]
|
45
|
+
exp = <<~EOT
|
46
|
+
#123456789ABCDEF
|
47
|
+
01234567 # HIT! -- level 1 = B, C
|
48
|
+
0123 # HIT! -- level 2 = D, E
|
49
|
+
01 # HIT! -- level 3 = H, I
|
50
|
+
0 # HIT!
|
51
|
+
EOT
|
52
|
+
|
53
|
+
assert_steps ary, bad, exp
|
37
54
|
end
|
38
55
|
|
39
|
-
def
|
40
|
-
|
41
|
-
bad
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# 1 ---------------
|
53
|
-
# 1 ---------------
|
54
|
-
# 2 ------- -------
|
55
|
-
# 2 +++++++ +++++++
|
56
|
-
# 3 xxxxxxx
|
57
|
-
# 3 xxxxxxx
|
58
|
-
# 3 --- ---
|
59
|
-
# 3 +++ +++
|
60
|
-
# 4 xxx
|
61
|
-
# 4 xxx
|
62
|
-
# 4 - -
|
63
|
-
# 4 + +
|
64
|
-
#
|
65
|
-
# - = miss
|
66
|
-
# + = hit
|
67
|
-
# x = unwanted test
|
68
|
-
|
69
|
-
exp = [
|
70
|
-
# level 1 = B, C
|
71
|
-
[0, 1, 2, 3, 4, 5, 6, 7],
|
72
|
-
[8, 9, 10, 11, 12, 13, 14, 15],
|
73
|
-
|
74
|
-
# level 2 = DF, DG, EF, EG
|
75
|
-
[0, 1, 2, 3, 8, 9, 10, 11],
|
76
|
-
[0, 1, 2, 3, 12, 13, 14, 15],
|
77
|
-
|
78
|
-
# level 3
|
79
|
-
# [0, 1, 2, 3], # I think this is bad, we've tested B
|
80
|
-
# [12, 13, 14, 15], # again, bad, we've tested C
|
81
|
-
[0, 1, 12, 13],
|
82
|
-
[0, 1, 14, 15],
|
83
|
-
|
84
|
-
# level 4
|
85
|
-
# [0, 1],
|
86
|
-
# [14, 15],
|
87
|
-
[0, 14],
|
88
|
-
[0, 15],
|
89
|
-
]
|
90
|
-
|
91
|
-
assert_steps a, bad, exp
|
56
|
+
def test_ordering_best_case_2
|
57
|
+
ary = HEX
|
58
|
+
bad = %w[0 1]
|
59
|
+
exp = <<~EOT
|
60
|
+
01234567 # HIT! -- level 1 = B, C
|
61
|
+
0123 # HIT! -- level 2 = D, E
|
62
|
+
01 # HIT! -- level 3 = H, I
|
63
|
+
0 # miss -- level 4 = 0, 1, n_combos = 1
|
64
|
+
1 # miss
|
65
|
+
01 # HIT! -- level 3 = H, n_combos = 2
|
66
|
+
EOT
|
67
|
+
|
68
|
+
assert_steps ary, bad, exp
|
92
69
|
end
|
93
70
|
|
94
|
-
|
71
|
+
def test_ordering
|
72
|
+
ary = HEX
|
73
|
+
bad = %w[1 F]
|
74
|
+
exp = <<~EOT
|
75
|
+
01234567 # miss -- level 1 = B, C
|
76
|
+
89ABCDEF # miss
|
77
|
+
0123 89AB # miss -- level 2 = DF, DG, EF, EG
|
78
|
+
0123 CDEF # HIT!
|
79
|
+
01 CD # miss -- level 3 = HN, HO
|
80
|
+
01 EF # HIT!
|
81
|
+
0 E # miss -- level 4 = 0E, 0F, 1E, 1F
|
82
|
+
0 F # miss
|
83
|
+
1 E # miss
|
84
|
+
1 F # HIT!
|
85
|
+
EOT
|
86
|
+
|
87
|
+
assert_steps ary, bad, exp
|
88
|
+
end
|
95
89
|
|
96
90
|
def self.test_find_minimal_combination max, *bad
|
97
|
-
define_method "
|
91
|
+
define_method "%s_%s_%s" % [__method__, max, bad.join("_")] do
|
98
92
|
a = (1..max).to_a
|
99
|
-
|
93
|
+
|
94
|
+
assert_equal bad, a.find_minimal_combination(&check(*bad))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.test_find_minimal_combination_and_count max, nsteps, *bad
|
99
|
+
define_method "%s_%s_%s_%s" % [__method__, max, nsteps, bad.join("_")] do
|
100
|
+
a = (1..max).to_a
|
101
|
+
|
102
|
+
found, count = a.find_minimal_combination_and_count(&check(*bad))
|
103
|
+
|
104
|
+
assert_equal bad, found
|
105
|
+
assert_equal nsteps, count
|
100
106
|
end
|
101
107
|
end
|
102
108
|
|
@@ -119,7 +125,14 @@ describe Array, :find_minimal_combination do
|
|
119
125
|
test_find_minimal_combination 1023, 7, 15, 166, 1001
|
120
126
|
test_find_minimal_combination 1023, 1000, 1001, 1002
|
121
127
|
test_find_minimal_combination 1023, 1001, 1003, 1005, 1007
|
128
|
+
test_find_minimal_combination 1024, 1001, 1003, 1005, 1007
|
129
|
+
test_find_minimal_combination 1024, 1, 1024
|
130
|
+
|
131
|
+
test_find_minimal_combination_and_count 1024, 12, 1, 2
|
132
|
+
test_find_minimal_combination_and_count 1024, 23, 1, 1023
|
133
|
+
test_find_minimal_combination_and_count 1024, 24, 1, 1024
|
134
|
+
test_find_minimal_combination_and_count 1023, 26, 1, 1023
|
122
135
|
|
123
|
-
|
124
|
-
|
136
|
+
test_find_minimal_combination_and_count 1024, 93, 1001, 1003, 1005, 1007
|
137
|
+
test_find_minimal_combination_and_count 1023, 93, 1001, 1003, 1005, 1007
|
125
138
|
end
|
data.tar.gz.sig
CHANGED
@@ -1,3 +1 @@
|
|
1
|
-
|
2
|
-
��U$�P،�3��]�1j*�&��d��7ta���]��ޫ��D��Q��-���-��SnT������U2��ӗ�����6y
|
3
|
-
|
1
|
+
�;+�K�>]@+��^c�T�Zɫ;�Mag��R[�0q*q钮�O������Gge�|�NC�5~͚**�4�!'���w9����?SV<��wq�=?,��Obx��05{�5�r�ThG:�ﶉ�6{��'�T�VJ͌��;ꉂ�J4>r��~����Ű�]�G<J�f����G���¯.> �.vg�=���2�_�֝�#WI���|�A�L((l] B������F��9⻛u
|
metadata
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minitest-bisect
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Davis
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain:
|
11
11
|
- |
|
12
12
|
-----BEGIN CERTIFICATE-----
|
13
|
-
|
13
|
+
MIIDPjCCAiagAwIBAgIBBzANBgkqhkiG9w0BAQsFADBFMRMwEQYDVQQDDApyeWFu
|
14
14
|
ZC1ydWJ5MRkwFwYKCZImiZPyLGQBGRYJemVuc3BpZGVyMRMwEQYKCZImiZPyLGQB
|
15
|
-
|
15
|
+
GRYDY29tMB4XDTIzMDEwMTA3NTExN1oXDTI0MDEwMTA3NTExN1owRTETMBEGA1UE
|
16
16
|
AwwKcnlhbmQtcnVieTEZMBcGCgmSJomT8ixkARkWCXplbnNwaWRlcjETMBEGCgmS
|
17
17
|
JomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALda
|
18
18
|
b9DCgK+627gPJkB6XfjZ1itoOQvpqH1EXScSaba9/S2VF22VYQbXU1xQXL/WzCkx
|
@@ -22,14 +22,14 @@ cert_chain:
|
|
22
22
|
qhtV7HJxNKuPj/JFH0D2cswvzznE/a5FOYO68g+YCuFi5L8wZuuM8zzdwjrWHqSV
|
23
23
|
gBEfoTEGr7Zii72cx+sCAwEAAaM5MDcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAw
|
24
24
|
HQYDVR0OBBYEFEfFe9md/r/tj/Wmwpy+MI8d9k/hMA0GCSqGSIb3DQEBCwUAA4IB
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
AQAkg3y+PBnBAPWdxxITm5sPHqdWQgSyCpRA20o4LTuWr8BWhSXBkfQNa7cY6fOn
|
26
|
+
xyM34VPzBFbExv6XOGDfOMFBVaYTHuN9peC/5/umL7kLl+nflXzL2QA7K6LYj5Bg
|
27
|
+
sM574Onr0dZDM6Vn69bzQ7rBIFDfK/OhlPzqKZad4nsdcsVH8ODCiT+ATMIZyz5K
|
28
|
+
WCnNtqlyiWXI8tdTpahDgcUwfcN/oN7v4K8iU5IbLJX6HQ5DKgmKjfb6XyMth16k
|
29
|
+
ROfWo9Uyp8ba/j9eVG14KkYRaLydAY1MNQk2yd3R5CGfeOpD1kttxjoypoUJ2dOG
|
30
|
+
nsNBRuQJ1UfiCG97a6DNm+Fr
|
31
31
|
-----END CERTIFICATE-----
|
32
|
-
date:
|
32
|
+
date: 2023-07-06 00:00:00.000000000 Z
|
33
33
|
dependencies:
|
34
34
|
- !ruby/object:Gem::Dependency
|
35
35
|
name: minitest-server
|
@@ -99,14 +99,14 @@ dependencies:
|
|
99
99
|
requirements:
|
100
100
|
- - "~>"
|
101
101
|
- !ruby/object:Gem::Version
|
102
|
-
version: '
|
102
|
+
version: '4.0'
|
103
103
|
type: :development
|
104
104
|
prerelease: false
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
106
106
|
requirements:
|
107
107
|
- - "~>"
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version: '
|
109
|
+
version: '4.0'
|
110
110
|
description: |-
|
111
111
|
Hunting down random test failures can be very very difficult,
|
112
112
|
sometimes impossible, but minitest-bisect makes it easy.
|
@@ -135,24 +135,10 @@ files:
|
|
135
135
|
- README.rdoc
|
136
136
|
- Rakefile
|
137
137
|
- bin/minitest_bisect
|
138
|
-
- example
|
139
|
-
-
|
140
|
-
-
|
141
|
-
-
|
142
|
-
- example-many/test_bad4.rb
|
143
|
-
- example-many/test_bad5.rb
|
144
|
-
- example-many/test_bad6.rb
|
145
|
-
- example-many/test_bad7.rb
|
146
|
-
- example-many/test_bad8.rb
|
147
|
-
- example/helper.rb
|
148
|
-
- example/test_bad1.rb
|
149
|
-
- example/test_bad2.rb
|
150
|
-
- example/test_bad3.rb
|
151
|
-
- example/test_bad4.rb
|
152
|
-
- example/test_bad5.rb
|
153
|
-
- example/test_bad6.rb
|
154
|
-
- example/test_bad7.rb
|
155
|
-
- example/test_bad8.rb
|
138
|
+
- example.rb
|
139
|
+
- example_helper.rb
|
140
|
+
- example_inverse.rb
|
141
|
+
- example_many.rb
|
156
142
|
- lib/minitest/bisect.rb
|
157
143
|
- lib/minitest/find_minimal_combination.rb
|
158
144
|
- test/minitest/test_bisect.rb
|
@@ -160,8 +146,9 @@ files:
|
|
160
146
|
homepage: https://github.com/seattlerb/minitest-bisect
|
161
147
|
licenses:
|
162
148
|
- MIT
|
163
|
-
metadata:
|
164
|
-
|
149
|
+
metadata:
|
150
|
+
homepage_uri: https://github.com/seattlerb/minitest-bisect
|
151
|
+
post_install_message:
|
165
152
|
rdoc_options:
|
166
153
|
- "--main"
|
167
154
|
- README.rdoc
|
@@ -178,8 +165,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
165
|
- !ruby/object:Gem::Version
|
179
166
|
version: '0'
|
180
167
|
requirements: []
|
181
|
-
rubygems_version: 3.
|
182
|
-
signing_key:
|
168
|
+
rubygems_version: 3.4.10
|
169
|
+
signing_key:
|
183
170
|
specification_version: 4
|
184
171
|
summary: Hunting down random test failures can be very very difficult, sometimes impossible,
|
185
172
|
but minitest-bisect makes it easy
|
metadata.gz.sig
CHANGED
Binary file
|
data/example/helper.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
$hosed ||= false
|
2
|
-
|
3
|
-
def create_test suffix, n_methods, bad_methods = {}
|
4
|
-
raise ArgumentError, "Bad args" if Hash === n_methods
|
5
|
-
|
6
|
-
delay = (ENV["SLEEP"] || 0.01).to_f
|
7
|
-
|
8
|
-
Class.new(Minitest::Test) do
|
9
|
-
n_methods.times do |n|
|
10
|
-
n += 1
|
11
|
-
define_method "test_bad#{suffix}_#{n}" do
|
12
|
-
sleep delay if delay > 0
|
13
|
-
|
14
|
-
case bad_methods[n]
|
15
|
-
when true then
|
16
|
-
flunk "muahahaha order dependency bug!" if $hosed
|
17
|
-
when false then
|
18
|
-
$hosed = true
|
19
|
-
else
|
20
|
-
assert true
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
data/example/test_bad1.rb
DELETED
data/example/test_bad2.rb
DELETED
data/example/test_bad3.rb
DELETED
data/example/test_bad4.rb
DELETED
data/example/test_bad5.rb
DELETED
data/example/test_bad6.rb
DELETED
data/example/test_bad7.rb
DELETED
data/example/test_bad8.rb
DELETED
data/example-many/test_bad1.rb
DELETED
data/example-many/test_bad2.rb
DELETED
data/example-many/test_bad3.rb
DELETED
data/example-many/test_bad4.rb
DELETED
data/example-many/test_bad5.rb
DELETED
data/example-many/test_bad6.rb
DELETED
data/example-many/test_bad7.rb
DELETED
data/example-many/test_bad8.rb
DELETED