minitest-bisect 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6a2f0e5308b623a3bd5ba94ef584fec183e7dd51
4
+ data.tar.gz: 62eb672d93534aee515afcbf939b01d01fff7ba3
5
+ SHA512:
6
+ metadata.gz: ded844dfc9c7dffd7a598b6a2a5fc7a3dd2de11d7091856b508e303a3f1f366797df0dcbe9e1aaa1fa8eec0002f6d460a682878463624ca9b80656671bc0fdac
7
+ data.tar.gz: bffd131f915c60ed1e2185f957ffb6ac523008690b49c2a03786f9ba29c0c20a2720aad6c5090f2a8dd3d99a17ee8acd4b663f79718486dff902360d2f82d22a
checksums.yaml.gz.sig ADDED
Binary file
data.tar.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ ���
2
+ �ۗgEc�m1��x���wN���H��$��*Y���j���� W����N.���S`{J��\��:���X7���r�u���Z�vѵ��'���`~�ESu铱�j9顅6�2�S������=ȸ��[k�j�vd�u+�n����
- ��n��9;`� n���'=x�e�7<\�K������]��6����4y �cP�]�(?>��|Qq����&jo�;7���L��WU��8\i��@3UMuM
data/.autotest ADDED
@@ -0,0 +1,26 @@
1
+ # -*- ruby -*-
2
+
3
+ require "autotest/restart"
4
+
5
+ Autotest.add_hook :initialize do |at|
6
+ at.testlib = "minitest/autorun"
7
+ at.add_exception "tmp"
8
+
9
+ # at.extra_files << "../some/external/dependency.rb"
10
+ #
11
+ # at.libs << ":../some/external"
12
+ #
13
+ # at.add_exception "vendor"
14
+ #
15
+ # at.add_mapping(/dependency.rb/) do |f, _|
16
+ # at.files_matching(/test_.*rb$/)
17
+ # end
18
+ #
19
+ # %w(TestA TestB).each do |klass|
20
+ # at.extra_class_map[klass] = "test/test_misc.rb"
21
+ # end
22
+ end
23
+
24
+ # Autotest.add_hook :run_command do |at|
25
+ # system "rake build"
26
+ # end
data/.gemtest ADDED
File without changes
data/History.rdoc ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2014-07-16
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,30 @@
1
+ .autotest
2
+ History.rdoc
3
+ Manifest.txt
4
+ README.rdoc
5
+ Rakefile
6
+ bin/minitest_bisect
7
+ example-many/helper.rb
8
+ example-many/test_bad1.rb
9
+ example-many/test_bad2.rb
10
+ example-many/test_bad3.rb
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
25
+ lib/minitest/bisect.rb
26
+ lib/minitest/find_minimal_combination.rb
27
+ lib/minitest/server.rb
28
+ lib/minitest/server_plugin.rb
29
+ test/minitest/test_bisect.rb
30
+ test/minitest/test_find_minimal_combination.rb
data/README.rdoc ADDED
@@ -0,0 +1,166 @@
1
+ = minitest-bisect
2
+
3
+ home :: https://github.com/seattlerb/minitest-bisect
4
+ rdoc :: http://docs.seattlerb.org/minitest-bisect
5
+
6
+ == DESCRIPTION:
7
+
8
+ Hunting down random test failures can be very very difficult,
9
+ sometimes impossible, but minitest-bisect makes it easy.
10
+
11
+ minitest-bisect helps you isolate and debug random test failures.
12
+
13
+ If your tests only fail randomly, you can reproduce the error
14
+ consistently by using `--seed <num>`, but what then? How do you figure
15
+ out which combination of tests out of hundreds are responsible for the
16
+ failure? You know which test is failing, but what others are causing
17
+ it to fail or were helping it succeed in a different order? That's
18
+ what minitest-bisect does best.
19
+
20
+ == FEATURES/PROBLEMS:
21
+
22
+ * minitest_bisect first runs your tests on a per-file basis to
23
+ minimize the number of tests you need to sift through.
24
+ * minitest_bisect next runs the minimized files and figures out your
25
+ exact failure reproduction.
26
+
27
+ == SYNOPSIS:
28
+
29
+ Let's say you have a bunch of test files and they fail sometimes, but
30
+ not consistently. You get a run that fails, so you record the
31
+ randomization seed that minitest displays at the top of every run.
32
+
33
+ === Original Failure:
34
+
35
+ Here's an example run that fails randomly:
36
+
37
+ $ rake
38
+ ruby -I.: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"'
39
+ Run options: --seed 3911
40
+
41
+ # Running:
42
+
43
+ ..............................................................................
44
+ ..............................................................................
45
+ ..............................................................................
46
+ ..............................................................................
47
+ .........................................F....................................
48
+ ..............................................................................
49
+ ..............................................................................
50
+ ..............................................................................
51
+ ..............................................................................
52
+ ..............................................................................
53
+ ....................
54
+
55
+ Finished in 200.836561s, 3.9833 runs/s, 3.9784 assertions/s.
56
+
57
+ 1) Failure:
58
+ TestBad4#test_bad4_4 [example/helper.rb:16]:
59
+ muahahaha order dependency bug!
60
+
61
+ 800 runs, 799 assertions, 1 failures, 0 errors, 0 skips
62
+
63
+ === Minimization and Isolation:
64
+
65
+ The problem is about how to efficiently figure out the minimal
66
+ reproduction of the random failure so you can actually focus on the
67
+ problem and not all the noise.
68
+
69
+ So, you run the tests again, but this time with minitest_bisect.
70
+ Provide the seed given in the failure so the tests always run in the
71
+ same order and reproduce every time.
72
+
73
+ minitest_bisect with first minimize the number of files, then it will
74
+ turn around and minimize the number of methods.
75
+
76
+ $ minitest_bisect --seed 3911 example/test*.rb
77
+ reproducing...
78
+ reproduced
79
+ # of culprit files: 4
80
+ # of culprit files: 2
81
+ # of culprit files: 2
82
+ # of culprit files: 2
83
+ # of culprit files: 2
84
+
85
+ Minimal files found in 5 steps:
86
+
87
+ ruby -Ilib -e 'require "./example/test_bad1.rb" ; require "./example/test_bad4.rb"' -- --seed 3911 -s 48222
88
+
89
+ reproducing...
90
+ reproduced
91
+ # of culprit methods: 64
92
+ # of culprit methods: 64
93
+ # of culprit methods: 32
94
+ # of culprit methods: 16
95
+ # of culprit methods: 8
96
+ # of culprit methods: 8
97
+ # of culprit methods: 4
98
+ # of culprit methods: 2
99
+ # of culprit methods: 2
100
+ # of culprit methods: 1
101
+
102
+ Minimal methods found in 10 steps:
103
+
104
+ ruby -Ilib -e 'require "./example/test_bad1.rb" ; require "./example/test_bad4.rb"' -- --seed 3911 -s 48222 -n '/^(?:TestBad1\#test_bad1_1|TestBad4\#test_bad4_4)$/'
105
+
106
+ Final reproduction:
107
+
108
+ Run options: --seed 3911 -s 48222 -n "/^(?:TestBad1\\#test_bad1_1|TestBad4\\#test_bad4_4)$/"
109
+
110
+ # Running:
111
+
112
+ .F
113
+
114
+ Finished in 0.505776s, 3.9543 runs/s, 1.9772 assertions/s.
115
+
116
+ 1) Failure:
117
+ TestBad4#test_bad4_4 [/Users/ryan/Work/p4/zss/src/minitest-bisect/dev/example/helper.rb:16]:
118
+ muahahaha order dependency bug!
119
+
120
+ 2 runs, 1 assertions, 1 failures, 0 errors, 0 skips
121
+
122
+ Voila! This reduced it from 800 tests across 8 files down to 2 tests
123
+ across 2 files. Note how we went from a 200 second test run to a 0.5
124
+ second test run. Debugging that will be much easier.
125
+
126
+ It is now up to you to look at the source of both of those tests to
127
+ determine what side-effects (or lack thereof) are causing your test
128
+ failure when run in this specific order.
129
+
130
+ This happens in a single run. Depending on how many files / tests you
131
+ have and how long they take, the first phase might take a long time.
132
+ The second phase might also take a while, but each iteration should
133
+ reduce the number of tests so it should get quicker with each step.
134
+
135
+ == REQUIREMENTS:
136
+
137
+ * minitest 5
138
+
139
+ == INSTALL:
140
+
141
+ * sudo gem install minitest-bisect
142
+
143
+ == LICENSE:
144
+
145
+ (The MIT License)
146
+
147
+ Copyright (c) Ryan Davis, seattle.rb
148
+
149
+ Permission is hereby granted, free of charge, to any person obtaining
150
+ a copy of this software and associated documentation files (the
151
+ 'Software'), to deal in the Software without restriction, including
152
+ without limitation the rights to use, copy, modify, merge, publish,
153
+ distribute, sublicense, and/or sell copies of the Software, and to
154
+ permit persons to whom the Software is furnished to do so, subject to
155
+ the following conditions:
156
+
157
+ The above copyright notice and this permission notice shall be
158
+ included in all copies or substantial portions of the Software.
159
+
160
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
161
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
162
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
163
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
164
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
165
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
166
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,72 @@
1
+ # -*- ruby -*-
2
+
3
+ require "rubygems"
4
+ require "hoe"
5
+
6
+ Hoe.plugin :isolate
7
+ Hoe.plugin :seattlerb
8
+ Hoe.plugin :rdoc
9
+
10
+ Hoe.spec "minitest-bisect" do
11
+ developer "Ryan Davis", "ryand-ruby@zenspider.com"
12
+ license "MIT"
13
+ end
14
+
15
+ require "rake/testtask"
16
+
17
+ Rake::TestTask.new(:badtest) do |t|
18
+ t.test_files = Dir["badtest/test*.rb"]
19
+ end
20
+
21
+ def banner text
22
+ puts
23
+ puts "#" * 70
24
+ puts "# #{text} ::"
25
+ puts "#" * 70
26
+ puts
27
+ unless ENV["SLEEP"] == "0" then
28
+ print "Press return to continue "
29
+ $stdin.gets
30
+ puts
31
+ end
32
+ end
33
+
34
+ def run cmd
35
+ sh cmd do end
36
+ end
37
+
38
+ def req glob
39
+ Dir["#{glob}.rb"].map { |s| "require #{s.inspect}" }.join ";"
40
+ end
41
+
42
+ task :repro do
43
+ unless ENV.has_key? "SLEEP" then
44
+ warn "NOTE: Defaulting to sleeping 0.01 seconds per test."
45
+ warn "NOTE: Use SLEEP=0 to disable or any other value to simulate your tests."
46
+ end
47
+
48
+ ruby = "ruby -I.:lib"
49
+
50
+ banner "Original run that causes the test order dependency bug"
51
+ run "#{ruby} -e '#{req "example/test*"}' -- --seed 3911"
52
+
53
+ banner "Reduce the problem down to the minimal reproduction"
54
+ run "#{ruby} bin/minitest_bisect -Ilib --seed 3911 example/test*.rb"
55
+ end
56
+
57
+ task :many do
58
+ unless ENV.has_key? "SLEEP" then
59
+ warn "NOTE: Defaulting to sleeping 0.01 seconds per test."
60
+ warn "NOTE: Use SLEEP=0 to disable or any other value to simulate your tests."
61
+ end
62
+
63
+ ruby = "ruby -I.:lib"
64
+
65
+ banner "Original run that causes the test order dependency bug"
66
+ run "#{ruby} -e '#{req "example-many/test*"}' -- --seed 27083"
67
+
68
+ banner "Reduce the problem down to the minimal reproduction"
69
+ run "#{ruby} bin/minitest_bisect -Ilib --seed 27083 example-many/test*.rb"
70
+ end
71
+
72
+ # vim: syntax=ruby
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ require "minitest/bisect"
4
+
5
+ Minitest::Bisect.run ARGV
@@ -0,0 +1,25 @@
1
+ $hosed ||= 0
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 = 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
+ $hosed += 1
17
+ when Fixnum then
18
+ flunk "muahahaha order dependency bug!" if $hosed >= bad_methods[n]
19
+ else
20
+ assert true
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad1 = create_test 1, 100, 1 => true
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad2 = create_test 2, 100
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad3 = create_test 3, 100, 72 => true
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad4 = create_test 4, 100
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad5 = create_test 5, 100, 17 => true
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad6 = create_test 6, 100
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad7 = create_test 7, 100
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad8 = create_test 8, 100, 43 => 3
data/example/helper.rb ADDED
@@ -0,0 +1,25 @@
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 = 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
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad1 = create_test 1, 100, 1 => false
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad2 = create_test 2, 100
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad3 = create_test 3, 100
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad4 = create_test 4, 100, 4 => true
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad5 = create_test 5, 100
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad6 = create_test 6, 100
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad7 = create_test 7, 100
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require_relative "helper"
3
+
4
+ TestBad8 = create_test 8, 100
@@ -0,0 +1,150 @@
1
+ require "minitest/find_minimal_combination"
2
+ require "minitest/server"
3
+ require "shellwords"
4
+
5
+ class Minitest::Bisect
6
+ VERSION = "1.0.0"
7
+ SHH = " &> /dev/null"
8
+
9
+ attr_accessor :tainted, :failures, :culprits, :mode, :seen_bad
10
+ alias :tainted? :tainted
11
+
12
+ def self.run files
13
+ new.run files
14
+ end
15
+
16
+ def initialize
17
+ self.culprits = []
18
+ self.failures = Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } }
19
+ end
20
+
21
+ def reset
22
+ self.seen_bad = false
23
+ self.tainted = false
24
+ failures.clear
25
+ # not clearing culprits on purpose
26
+ end
27
+
28
+ def run files
29
+ Minitest::Server.run self
30
+
31
+ cmd = nil
32
+
33
+ if :until_I_have_negative_filtering_in_minitest then
34
+ files, flags = files.partition { |arg| File.file? arg }
35
+ rb_flags, mt_flags = flags.partition { |arg| arg =~ /^-I/ }
36
+ mt_flags += ["-s", $$]
37
+
38
+ cmd = bisect_methods build_files_cmd(files, rb_flags, mt_flags)
39
+ else
40
+ cmd = bisect_methods bisect_files files
41
+ end
42
+
43
+ puts "Final reproduction:"
44
+ puts
45
+
46
+ system cmd
47
+ ensure
48
+ Minitest::Server.stop
49
+ end
50
+
51
+ def bisect_files files
52
+ self.mode = :files
53
+
54
+ files, flags = files.partition { |arg| File.file? arg }
55
+ rb_flags, mt_flags = flags.partition { |arg| arg =~ /^-I/ }
56
+ mt_flags += ["-s", $$]
57
+
58
+ puts "reproducing..."
59
+ system "#{build_files_cmd files, rb_flags, mt_flags} #{SHH}"
60
+ abort "Reproduction run passed? Aborting." unless tainted?
61
+ puts "reproduced"
62
+
63
+ found, count = files.find_minimal_combination_and_count do |test|
64
+ puts "# of culprit files: #{test.size}"
65
+
66
+ system "#{build_files_cmd test, rb_flags, mt_flags} #{SHH}"
67
+
68
+ self.tainted?
69
+ end
70
+
71
+ puts
72
+ puts "Minimal files found in #{count} steps:"
73
+ puts
74
+ cmd = build_files_cmd found, rb_flags, mt_flags
75
+ puts cmd
76
+ cmd
77
+ end
78
+
79
+ def bisect_methods cmd
80
+ self.mode = :methods
81
+
82
+ puts "reproducing..."
83
+ system "#{build_methods_cmd cmd} #{SHH}"
84
+ abort "Reproduction run passed? Aborting." unless tainted?
85
+ puts "reproduced"
86
+
87
+ # from: {"file.rb"=>{"Class"=>["test_method"]}} to: "Class#test_method"
88
+ bad = failures.values.first.to_a.join "#"
89
+
90
+ found, count = culprits.find_minimal_combination_and_count do |test|
91
+ puts "# of culprit methods: #{test.size}"
92
+
93
+ system "#{build_methods_cmd cmd, test, bad} #{SHH}"
94
+
95
+ self.tainted?
96
+ end
97
+
98
+ puts
99
+ puts "Minimal methods found in #{count} steps:"
100
+ puts
101
+ cmd = build_methods_cmd cmd, found, bad
102
+ puts cmd
103
+ puts
104
+ cmd
105
+ end
106
+
107
+ def build_files_cmd culprits, rb, mt
108
+ reset
109
+
110
+ tests = culprits.flatten.compact.map {|f| %(require "./#{f}")}.join " ; "
111
+
112
+ %(ruby #{rb.shelljoin} -e '#{tests}' -- #{mt.shelljoin})
113
+ end
114
+
115
+ def build_methods_cmd cmd, culprits = [], bad = nil
116
+ reset
117
+
118
+ if bad then
119
+ re = []
120
+
121
+ bbc = (culprits + [bad]).map { |s| s.split(/#/) }.group_by(&:first)
122
+ bbc.each do |klass, methods|
123
+ methods = methods.map(&:last).flatten
124
+
125
+ re << /#{klass}##{Regexp.union(methods)}/
126
+ end
127
+
128
+ re = Regexp.union(re).to_s.gsub(/-mix/, "")
129
+
130
+ cmd += " -n '/^#{re}$/'" if bad
131
+ end
132
+
133
+ cmd
134
+ end
135
+
136
+ def result file, klass, method, fails, assertions, time
137
+ if mode == :methods then
138
+ if fails.empty? then
139
+ culprits << "#{klass}##{method}" unless seen_bad # UGH
140
+ else
141
+ self.seen_bad = true
142
+ end
143
+ end
144
+
145
+ unless fails.empty?
146
+ self.tainted = true
147
+ self.failures[file][klass] << method
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ module Minitest; end
4
+
5
+ class ComboFinder
6
+ ##
7
+ # Find the minimal combination of a collection of items that satisfy +test+.
8
+ #
9
+ # If you think of the collection as a binary tree, this algorithm
10
+ # does a breadth first search of the combinations that satisfy
11
+ # +test+.
12
+ #--
13
+ # level collection
14
+ #
15
+ # 0 A
16
+ # 1 B C
17
+ # 2 D E F G
18
+ # 3 1 2 3 4 5 6 7 8
19
+ #
20
+ # This assumes that A has already been tested and you're now trying
21
+ # to reduce the match. Starting at level 1, test B & C separately.
22
+ # If either test positive, reduce the search space accordingly. If
23
+ # not, step down to level 2 and search w/ finer granularity (ie, DF,
24
+ # DG, EF--DE and FG were already tested as B & C). Repeat until a
25
+ # minimal combination is found.
26
+
27
+ def find_minimal_combination ary
28
+ level, n_combos = 1, 1
29
+ seen = {}
30
+
31
+ loop do
32
+ size = 2 ** (Math.log(ary.size) / Math.log(2)).round
33
+ divs = 2 ** level
34
+ done = divs >= size
35
+ divs = size if done
36
+
37
+ subsections = ary.each_slice(size/divs).to_a.combination(n_combos)
38
+
39
+ found = subsections.find { |a|
40
+ b = a.flatten
41
+
42
+ next if seen[b]
43
+
44
+ cache_result yield(b), b, seen
45
+ }
46
+
47
+ if found then
48
+ ary = found.flatten
49
+ break if done
50
+
51
+ seen.delete ary
52
+
53
+ level = n_combos = 1
54
+ else
55
+ if done then
56
+ n_combos += 1
57
+ break if n_combos > size
58
+ else
59
+ level += 1
60
+ n_combos = level
61
+ end
62
+ end
63
+ end
64
+
65
+ ary
66
+ end
67
+
68
+ def cache_result result, data, cache
69
+ cache[data] = true
70
+
71
+ return result if result
72
+
73
+ unless result or data.size > 128 then
74
+ max = data.size
75
+ subdiv = 2
76
+ until subdiv >= max do
77
+ data.each_slice(max / subdiv) do |sub_data|
78
+ cache[sub_data] = true
79
+ end
80
+ subdiv *= 2
81
+ end
82
+ end
83
+
84
+ result
85
+ end
86
+ end
87
+
88
+ class Array
89
+ ##
90
+ # Find the minimal combination of a collection of items that satisfy +test+.
91
+
92
+ def find_minimal_combination &test
93
+ ComboFinder.new.find_minimal_combination(self, &test)
94
+ end
95
+
96
+ def find_minimal_combination_and_count
97
+ count = 0
98
+
99
+ found = self.find_minimal_combination do |ary|
100
+ count += 1
101
+ yield ary
102
+ end
103
+
104
+ return found, count
105
+ end
106
+ end
@@ -0,0 +1,45 @@
1
+ require "drb"
2
+ require "tmpdir"
3
+ require "minitest"
4
+
5
+ module Minitest
6
+ class Server
7
+ TOPDIR = Dir.pwd + "/"
8
+
9
+ def self.path pid = $$
10
+ "drbunix:#{Dir.tmpdir}/minitest.#{pid}"
11
+ end
12
+
13
+ def self.run client
14
+ DRb.start_service path, new(client)
15
+ end
16
+
17
+ def self.stop
18
+ DRb.stop_service
19
+ end
20
+
21
+ attr_accessor :client
22
+
23
+ def initialize client
24
+ self.client = client
25
+ end
26
+
27
+ def quit
28
+ self.class.stop
29
+ end
30
+
31
+ def start
32
+ client.failures.clear # TODO: push down to subclass
33
+ end
34
+
35
+ def result file, klass, method, fails, assertions, time
36
+ file = file.sub(/^#{TOPDIR}/, "")
37
+
38
+ client.result file, klass, method, fails, assertions, time
39
+ end
40
+
41
+ def report
42
+ # do nothing
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,44 @@
1
+ require "minitest"
2
+
3
+ module Minitest
4
+ @server = false
5
+
6
+ def self.plugin_server_options opts, options # :nodoc:
7
+ opts.on "-s", "--server=pid", Integer, "Connect to minitest server w/ pid." do |s|
8
+ @server = s
9
+ end
10
+ end
11
+
12
+ def self.plugin_server_init options
13
+ if @server then
14
+ require "minitest/server"
15
+ self.reporter << Minitest::ServerReporter.new(@server)
16
+ end
17
+ end
18
+ end
19
+
20
+ module Minitest
21
+ class ServerReporter < Minitest::AbstractReporter
22
+ def initialize pid
23
+ DRb.start_service
24
+ uri = Minitest::Server.path(pid)
25
+ @mt_server = DRbObject.new_with_uri uri
26
+ super()
27
+ end
28
+
29
+ def start
30
+ @mt_server.start
31
+ end
32
+
33
+ def record result
34
+ r = result
35
+ c = r.class
36
+ file, = c.instance_method(r.name).source_location
37
+ @mt_server.result file, c.name, r.name, r.failures, r.assertions, r.time
38
+ end
39
+
40
+ def report
41
+ @mt_server.report
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,10 @@
1
+ require "minitest/autorun"
2
+ require "minitest/bisect"
3
+
4
+ module TestMinitest; end
5
+
6
+ class TestMinitest::TestBisect < Minitest::Test
7
+ def test_sanity
8
+ flunk "write tests or I will kneecap you"
9
+ end
10
+ end
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ $: << "." << "lib"
4
+
5
+ gem "minitest"
6
+ require "minitest/autorun"
7
+ require "minitest/find_minimal_combination"
8
+
9
+ describe Array, :find_minimal_combination do
10
+ def check(*bad)
11
+ lambda { |sample| bad & sample == bad }
12
+ end
13
+
14
+ def assert_steps input, bad, exp
15
+ tests = []
16
+
17
+ found = input.find_minimal_combination do |test|
18
+ tests << test
19
+ bad & test == bad
20
+ end
21
+
22
+ assert_equal bad, found, "algorithm is bad"
23
+
24
+ assert_equal exp, tests
25
+ end
26
+
27
+ def test_ordering_best_case
28
+ a = (0..15).to_a
29
+ bad = [0, 1]
30
+ exp = [[0, 1, 2, 3, 4, 5, 6, 7],
31
+ [0, 1, 2, 3],
32
+ [0, 1],
33
+ [0],
34
+ [1],
35
+ [0, 1]]
36
+
37
+ assert_steps a, bad, exp
38
+ end
39
+
40
+ def test_ordering
41
+ a = (0..15).to_a
42
+ bad = [0, 15]
43
+
44
+ # lvl collection
45
+ #
46
+ # 0 | A
47
+ # 1 | B C
48
+ # 2 | D E F G
49
+ # 3 | H I J K L M N O
50
+ # 4 | 0 1 2 3 4 5 6 7 8 9 A B C D E F
51
+ #
52
+ # 0 +++++++++++++++++++++++++++++++
53
+ # 1 ---------------
54
+ # 1 ---------------
55
+ # 2 ------- -------
56
+ # 2 +++++++ +++++++
57
+ # 3 xxxxxxx
58
+ # 3 xxxxxxx
59
+ # 3 --- ---
60
+ # 3 +++ +++
61
+ # 4 xxx
62
+ # 4 xxx
63
+ # 4 - -
64
+ # 4 + +
65
+ #
66
+ # - = miss
67
+ # + = hit
68
+ # x = unwanted test
69
+
70
+ exp = [
71
+ # level 1 = B, C
72
+ [0, 1, 2, 3, 4, 5, 6, 7],
73
+ [8, 9, 10, 11, 12, 13, 14, 15],
74
+
75
+ # level 2 = DF, DG, EF, EG
76
+ [0, 1, 2, 3, 8, 9, 10, 11],
77
+ [0, 1, 2, 3, 12, 13, 14, 15],
78
+
79
+ # level 3
80
+ # [0, 1, 2, 3], # I think this is bad, we've tested B
81
+ # [12, 13, 14, 15], # again, bad, we've tested C
82
+ [0, 1, 12, 13],
83
+ [0, 1, 14, 15],
84
+
85
+ # level 4
86
+ # [0, 1],
87
+ # [14, 15],
88
+ [0, 14],
89
+ [0, 15],
90
+ ]
91
+
92
+ assert_steps a, bad, exp
93
+ end
94
+
95
+ make_my_diffs_pretty!
96
+
97
+ def self.test_find_minimal_combination max, *bad
98
+ define_method "test_find_minimal_combination_#{max}_#{bad.join "_"}" do
99
+ a = (1..max).to_a
100
+ a.find_minimal_combination(&check(*bad)).must_equal bad
101
+ end
102
+ end
103
+
104
+ test_find_minimal_combination 8, 5
105
+ test_find_minimal_combination 8, 2, 7
106
+ test_find_minimal_combination 8, 1, 2, 7
107
+ test_find_minimal_combination 8, 1, 4, 7
108
+ test_find_minimal_combination 8, 1, 3, 5, 7
109
+
110
+ test_find_minimal_combination 9, 5
111
+ test_find_minimal_combination 9, 9
112
+ test_find_minimal_combination 9, 2, 7
113
+ test_find_minimal_combination 9, 1, 2, 7
114
+ test_find_minimal_combination 9, 1, 4, 7
115
+ test_find_minimal_combination 9, 1, 3, 5, 7
116
+
117
+ test_find_minimal_combination 1023, 5
118
+ test_find_minimal_combination 1023, 1005
119
+ test_find_minimal_combination 1023, 802, 907
120
+ test_find_minimal_combination 1023, 7, 15, 166, 1001
121
+ test_find_minimal_combination 1023, 1000, 1001, 1002
122
+ test_find_minimal_combination 1023, 1001, 1003, 1005, 1007
123
+
124
+ # test_find_minimal_combination 1024, 1001, 1003, 1005, 1007
125
+ # test_find_minimal_combination 1023, 1001, 1003, 1005, 1007
126
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minitest-bisect
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Davis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDPjCCAiagAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMRMwEQYDVQQDDApyeWFu
14
+ ZC1ydWJ5MRkwFwYKCZImiZPyLGQBGRYJemVuc3BpZGVyMRMwEQYKCZImiZPyLGQB
15
+ GRYDY29tMB4XDTEzMDkxNjIzMDQxMloXDTE0MDkxNjIzMDQxMlowRTETMBEGA1UE
16
+ AwwKcnlhbmQtcnVieTEZMBcGCgmSJomT8ixkARkWCXplbnNwaWRlcjETMBEGCgmS
17
+ JomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALda
18
+ b9DCgK+627gPJkB6XfjZ1itoOQvpqH1EXScSaba9/S2VF22VYQbXU1xQXL/WzCkx
19
+ taCPaLmfYIaFcHHCSY4hYDJijRQkLxPeB3xbOfzfLoBDbjvx5JxgJxUjmGa7xhcT
20
+ oOvjtt5P8+GSK9zLzxQP0gVLS/D0FmoE44XuDr3iQkVS2ujU5zZL84mMNqNB1znh
21
+ GiadM9GHRaDiaxuX0cIUBj19T01mVE2iymf9I6bEsiayK/n6QujtyCbTWsAS9Rqt
22
+ qhtV7HJxNKuPj/JFH0D2cswvzznE/a5FOYO68g+YCuFi5L8wZuuM8zzdwjrWHqSV
23
+ gBEfoTEGr7Zii72cx+sCAwEAAaM5MDcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAw
24
+ HQYDVR0OBBYEFEfFe9md/r/tj/Wmwpy+MI8d9k/hMA0GCSqGSIb3DQEBBQUAA4IB
25
+ AQCFZ7JTzoy1gcG4d8A6dmOJy7ygtO5MFpRIz8HuKCF5566nOvpy7aHhDDzFmQuu
26
+ FX3zDU6ghx5cQIueDhf2SGOncyBmmJRRYawm3wI0o1MeN6LZJ/3cRaOTjSFy6+S6
27
+ zqDmHBp8fVA2TGJtO0BLNkbGVrBJjh0UPmSoGzWlRhEVnYC33TpDAbNA+u39UrQI
28
+ ynwhNN7YbnmSR7+JU2cUjBFv2iPBO+TGuWC+9L2zn3NHjuc6tnmSYipA9y8Hv+As
29
+ Y4evBVezr3SjXz08vPqRO5YRdO3zfeMT8gBjRqZjWJGMZ2lD4XNfrs7eky74CyZw
30
+ xx3n58i0lQkBE1EpKE0lFu/y
31
+ -----END CERTIFICATE-----
32
+ date: 2014-08-30 00:00:00.000000000 Z
33
+ dependencies:
34
+ - !ruby/object:Gem::Dependency
35
+ name: minitest
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '5.4'
41
+ type: :development
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '5.4'
48
+ - !ruby/object:Gem::Dependency
49
+ name: rdoc
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '4.0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '4.0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: hoe
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '3.12'
69
+ type: :development
70
+ prerelease: false
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '3.12'
76
+ description: |-
77
+ Hunting down random test failures can be very very difficult,
78
+ sometimes impossible, but minitest-bisect makes it easy.
79
+
80
+ minitest-bisect helps you isolate and debug random test failures.
81
+
82
+ If your tests only fail randomly, you can reproduce the error
83
+ consistently by using `--seed <num>`, but what then? How do you figure
84
+ out which combination of tests out of hundreds are responsible for the
85
+ failure? You know which test is failing, but what others are causing
86
+ it to fail or were helping it succeed in a different order? That's
87
+ what minitest-bisect does best.
88
+ email:
89
+ - ryand-ruby@zenspider.com
90
+ executables:
91
+ - minitest_bisect
92
+ extensions: []
93
+ extra_rdoc_files:
94
+ - History.rdoc
95
+ - Manifest.txt
96
+ - README.rdoc
97
+ files:
98
+ - .autotest
99
+ - .gemtest
100
+ - History.rdoc
101
+ - Manifest.txt
102
+ - README.rdoc
103
+ - Rakefile
104
+ - bin/minitest_bisect
105
+ - example-many/helper.rb
106
+ - example-many/test_bad1.rb
107
+ - example-many/test_bad2.rb
108
+ - example-many/test_bad3.rb
109
+ - example-many/test_bad4.rb
110
+ - example-many/test_bad5.rb
111
+ - example-many/test_bad6.rb
112
+ - example-many/test_bad7.rb
113
+ - example-many/test_bad8.rb
114
+ - example/helper.rb
115
+ - example/test_bad1.rb
116
+ - example/test_bad2.rb
117
+ - example/test_bad3.rb
118
+ - example/test_bad4.rb
119
+ - example/test_bad5.rb
120
+ - example/test_bad6.rb
121
+ - example/test_bad7.rb
122
+ - example/test_bad8.rb
123
+ - lib/minitest/bisect.rb
124
+ - lib/minitest/find_minimal_combination.rb
125
+ - lib/minitest/server.rb
126
+ - lib/minitest/server_plugin.rb
127
+ - test/minitest/test_bisect.rb
128
+ - test/minitest/test_find_minimal_combination.rb
129
+ homepage: https://github.com/seattlerb/minitest-bisect
130
+ licenses:
131
+ - MIT
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options:
135
+ - --main
136
+ - README.rdoc
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - '>='
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - '>='
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubyforge_project:
151
+ rubygems_version: 2.2.1
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: Hunting down random test failures can be very very difficult, sometimes impossible,
155
+ but minitest-bisect makes it easy
156
+ test_files:
157
+ - test/minitest/test_bisect.rb
158
+ - test/minitest/test_find_minimal_combination.rb
metadata.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ �O��
2
+ �����26I����>W3�����a1\�kI@�{4�:�,"M�����{�]ò��W9dgG�pKM��`��J���$�4����C��8`��S���i���!�0�Df�uܨ��.�����ً[������26���4'rXV��qL#ݟ��:�]�ӈ���\=g<�*�7�l�@�Zg�#�\oR�Qs�e+KG�w�~0%��<�i��ˬ0{*C��(��1�I�<��OֶF���b@���u���