minitest 5.27.0 → 6.0.0.a1
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 +80 -0
- data/Manifest.txt +13 -4
- data/README.rdoc +5 -88
- data/Rakefile +5 -16
- data/bin/minitest +5 -0
- data/lib/minitest/assertions.rb +24 -56
- data/lib/minitest/autorun.rb +0 -1
- data/lib/minitest/bisect.rb +306 -0
- data/lib/minitest/complete.rb +56 -0
- data/lib/minitest/find_minimal_combination.rb +127 -0
- data/lib/minitest/manual_plugins.rb +4 -16
- data/lib/minitest/parallel.rb +3 -3
- data/lib/minitest/path_expander.rb +418 -0
- data/lib/minitest/pride.rb +1 -1
- data/lib/minitest/server.rb +45 -0
- data/lib/minitest/server_plugin.rb +84 -0
- data/lib/minitest/spec.rb +2 -31
- data/lib/minitest/sprint.rb +104 -0
- data/lib/minitest/sprint_plugin.rb +39 -0
- data/lib/minitest/test.rb +6 -11
- data/lib/minitest/test_task.rb +4 -6
- data/lib/minitest.rb +56 -84
- data/test/minitest/metametameta.rb +1 -1
- data/test/minitest/test_bisect.rb +235 -0
- data/test/minitest/test_find_minimal_combination.rb +138 -0
- data/test/minitest/test_minitest_assertions.rb +33 -47
- data/test/minitest/test_minitest_spec.rb +38 -102
- data/test/minitest/test_minitest_test.rb +20 -99
- data/test/minitest/test_path_expander.rb +229 -0
- data/test/minitest/test_server.rb +149 -0
- data.tar.gz.sig +0 -0
- metadata +47 -27
- metadata.gz.sig +0 -0
- data/.autotest +0 -34
- data/lib/minitest/mock.rb +0 -327
- data/lib/minitest/unit.rb +0 -42
- data/test/minitest/test_minitest_mock.rb +0 -1213
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
require_relative "find_minimal_combination"
|
|
2
|
+
require_relative "server"
|
|
3
|
+
require "shellwords"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
require_relative "path_expander" # this is gonna break some shit?
|
|
6
|
+
|
|
7
|
+
module Minitest; end # :nodoc:
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Minitest::Bisect helps you isolate and debug random test failures.
|
|
11
|
+
|
|
12
|
+
class Minitest::Bisect
|
|
13
|
+
VERSION = "1.8.0" # :nodoc:
|
|
14
|
+
|
|
15
|
+
class PathExpander < Minitest::VendoredPathExpander # :nodoc:
|
|
16
|
+
TEST_GLOB = "**/{test_*,*_test,spec_*,*_spec}.rb" # :nodoc:
|
|
17
|
+
|
|
18
|
+
attr_accessor :rb_flags
|
|
19
|
+
|
|
20
|
+
def initialize args = ARGV # :nodoc:
|
|
21
|
+
super args, TEST_GLOB, "test"
|
|
22
|
+
self.rb_flags = %w[-Itest:lib]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Overrides PathExpander#process_flags to filter out ruby flags
|
|
27
|
+
# from minitest flags. Only supports -I<paths>, -d, and -w for
|
|
28
|
+
# ruby.
|
|
29
|
+
|
|
30
|
+
def process_flags flags
|
|
31
|
+
flags.reject { |flag| # all hits are truthy, so this works out well
|
|
32
|
+
case flag
|
|
33
|
+
when /^-I(.*)/ then
|
|
34
|
+
rb_flags << flag
|
|
35
|
+
when /^-d/ then
|
|
36
|
+
rb_flags << flag
|
|
37
|
+
when /^-w/ then
|
|
38
|
+
rb_flags << flag
|
|
39
|
+
else
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
mtbv = ENV["MTB_VERBOSE"].to_i
|
|
47
|
+
SHH = case # :nodoc:
|
|
48
|
+
when mtbv == 1 then " > /dev/null"
|
|
49
|
+
when mtbv >= 2 then nil
|
|
50
|
+
else " > /dev/null 2>&1"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Borrowed from rake
|
|
54
|
+
RUBY = ENV['RUBY'] ||
|
|
55
|
+
File.join(RbConfig::CONFIG['bindir'],
|
|
56
|
+
RbConfig::CONFIG['ruby_install_name'] +
|
|
57
|
+
RbConfig::CONFIG['EXEEXT']).sub(/.*\s.*/m, '"\&"')
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# True if this run has seen a failure.
|
|
61
|
+
|
|
62
|
+
attr_accessor :tainted
|
|
63
|
+
alias :tainted? :tainted
|
|
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
|
+
|
|
82
|
+
def self.run files
|
|
83
|
+
new.run files
|
|
84
|
+
rescue => e
|
|
85
|
+
warn e.message
|
|
86
|
+
warn "Try running with MTB_VERBOSE=2 to verify."
|
|
87
|
+
exit 1
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
##
|
|
91
|
+
# Instantiate a new Bisect.
|
|
92
|
+
|
|
93
|
+
def initialize
|
|
94
|
+
self.culprits = []
|
|
95
|
+
self.failures = Hash.new { |h, k| h[k] = Hash.new { |h2, k2| h2[k2] = [] } }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
##
|
|
99
|
+
# Reset per-bisect-run variables.
|
|
100
|
+
|
|
101
|
+
def reset
|
|
102
|
+
self.seen_bad = false
|
|
103
|
+
self.tainted = false
|
|
104
|
+
failures.clear
|
|
105
|
+
# not clearing culprits on purpose
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
##
|
|
109
|
+
# Instance-level runner. Handles Minitest::Server, argument
|
|
110
|
+
# processing, and invoking +bisect_methods+.
|
|
111
|
+
|
|
112
|
+
def run args
|
|
113
|
+
Minitest::Server.run self
|
|
114
|
+
|
|
115
|
+
cmd = nil
|
|
116
|
+
|
|
117
|
+
mt_flags = args.dup
|
|
118
|
+
expander = Minitest::Bisect::PathExpander.new mt_flags
|
|
119
|
+
|
|
120
|
+
files = expander.process.to_a
|
|
121
|
+
rb_flags = expander.rb_flags
|
|
122
|
+
mt_flags += ["--server", $$.to_s]
|
|
123
|
+
|
|
124
|
+
cmd = bisect_methods files, rb_flags, mt_flags
|
|
125
|
+
|
|
126
|
+
puts "Final reproduction:"
|
|
127
|
+
puts
|
|
128
|
+
|
|
129
|
+
system cmd.sub(/--server \d+/, "")
|
|
130
|
+
ensure
|
|
131
|
+
Minitest::Server.stop
|
|
132
|
+
end
|
|
133
|
+
|
|
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
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
cmd = build_files_cmd(files, rb_flags, mt_flags)
|
|
175
|
+
|
|
176
|
+
msg = normal ? "reproducing..." : "reproducing false positive..."
|
|
177
|
+
time_it msg, build_methods_cmd(cmd)
|
|
178
|
+
|
|
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
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
if normal then
|
|
188
|
+
bad = map_failures
|
|
189
|
+
|
|
190
|
+
time_it "verifying...", build_methods_cmd(cmd, [], bad)
|
|
191
|
+
|
|
192
|
+
new_bad = map_failures
|
|
193
|
+
|
|
194
|
+
if bad == new_bad then
|
|
195
|
+
warn "Tests fail by themselves. This may not be an ordering issue."
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
idx = culprits.index bad.first
|
|
200
|
+
self.culprits = culprits.take idx+1 if idx # cull tests after bad
|
|
201
|
+
|
|
202
|
+
# culprits populated by initial reproduction via minitest/server
|
|
203
|
+
found, count = culprits.find_minimal_combination_and_count do |test|
|
|
204
|
+
prompt = "# of culprit methods: #{test.size}"
|
|
205
|
+
|
|
206
|
+
time_it prompt, build_methods_cmd(cmd, test, bad)
|
|
207
|
+
|
|
208
|
+
normal == tainted? # either normal and failed, or inverse and passed
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
puts
|
|
212
|
+
puts "Minimal methods found in #{count} steps:"
|
|
213
|
+
puts
|
|
214
|
+
puts "Culprit methods: %p" % [found + bad]
|
|
215
|
+
puts
|
|
216
|
+
cmd = build_methods_cmd cmd, found, bad
|
|
217
|
+
puts cmd.sub(/--server \d+/, "")
|
|
218
|
+
puts
|
|
219
|
+
cmd
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def time_it prompt, cmd # :nodoc:
|
|
223
|
+
print prompt
|
|
224
|
+
t0 = Time.now
|
|
225
|
+
system "#{cmd} #{SHH}"
|
|
226
|
+
puts " in %.2f sec" % (Time.now - t0)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def map_failures # :nodoc:
|
|
230
|
+
# from: {"file.rb"=>{"Class"=>["test_method1", "test_method2"]}}
|
|
231
|
+
# to: ["Class#test_method1", "Class#test_method2"]
|
|
232
|
+
failures.values.map { |h|
|
|
233
|
+
h.map { |k,vs| vs.map { |v| "#{k}##{v}" } }
|
|
234
|
+
}.flatten.sort
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def build_files_cmd culprits, rb, mt # :nodoc:
|
|
238
|
+
tests = culprits.flatten.compact.map { |f| %(require "./#{f}") }.join " ; "
|
|
239
|
+
|
|
240
|
+
%(#{RUBY} #{rb.shelljoin} -e '#{tests}' -- #{mt.map(&:to_s).shelljoin})
|
|
241
|
+
end
|
|
242
|
+
|
|
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:
|
|
262
|
+
re = []
|
|
263
|
+
|
|
264
|
+
# bad by class, you perv
|
|
265
|
+
bbc = bad.map { |s| s.split(/#/, 2) }.group_by(&:first)
|
|
266
|
+
|
|
267
|
+
bbc.each do |klass, methods|
|
|
268
|
+
methods = methods.map(&:last).flatten.uniq.map { |method|
|
|
269
|
+
re_escape method
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
methods = methods.join "|"
|
|
273
|
+
re << /#{re_escape klass}#(?:#{methods})/.to_s[7..-2] # (?-mix:...)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
re = re.join("|").to_s.gsub(/-mix/, "")
|
|
277
|
+
|
|
278
|
+
"/^(?:#{re})$/"
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def re_escape str # :nodoc:
|
|
282
|
+
str.gsub(/([`'"!?&\[\]\(\)\{\}\|\+])/, '\\\\\1')
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
############################################################
|
|
286
|
+
# Server Methods:
|
|
287
|
+
|
|
288
|
+
def minitest_start # :nodoc:
|
|
289
|
+
self.failures.clear
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def minitest_result file, klass, method, fails, assertions, time # :nodoc:
|
|
293
|
+
fails.reject! { |fail| Minitest::Skip === fail }
|
|
294
|
+
|
|
295
|
+
if fails.empty? then
|
|
296
|
+
culprits << "#{klass}##{method}" unless seen_bad # UGH
|
|
297
|
+
else
|
|
298
|
+
self.seen_bad = true
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
return if fails.empty?
|
|
302
|
+
|
|
303
|
+
self.tainted = true
|
|
304
|
+
self.failures[file][klass] << method
|
|
305
|
+
end
|
|
306
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env -S ruby
|
|
2
|
+
|
|
3
|
+
# :stopdoc:
|
|
4
|
+
|
|
5
|
+
require "optparse"
|
|
6
|
+
require "shellwords"
|
|
7
|
+
|
|
8
|
+
# complete -o bashdefault -f -C 'ruby lib/minitest/complete.rb' minitest
|
|
9
|
+
# using eg:
|
|
10
|
+
# COMP_LINE="blah test/test_file.rb -n test_pattern"
|
|
11
|
+
# or test directly with:
|
|
12
|
+
# ./lib/minitest/complete.rb test/test_file.rb -n test_pattern
|
|
13
|
+
|
|
14
|
+
argv = Shellwords.split ENV["COMP_LINE"] || ARGV.join(" ")
|
|
15
|
+
comp_re = nil
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
OptionParser.new do |opts|
|
|
19
|
+
# part of my unofficial embedded gem "makeoptparseworkwell"
|
|
20
|
+
def opts.topdict(name) = (name.length > 1 ? top.long : top.short)
|
|
21
|
+
def opts.alias(from, to) = (dict = topdict(from) ; dict[to] = dict[from])
|
|
22
|
+
|
|
23
|
+
opts.on "-n", "--name [METHOD]", "minitest option" do |m|
|
|
24
|
+
comp_re = Regexp.new m
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
opts.alias "name", "include"
|
|
28
|
+
opts.alias "name", "exclude"
|
|
29
|
+
opts.alias "n", "i"
|
|
30
|
+
opts.alias "n", "e"
|
|
31
|
+
opts.alias "n", "x"
|
|
32
|
+
end.parse! argv
|
|
33
|
+
rescue
|
|
34
|
+
retry # ignore options passed to Ruby
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
path = argv.find_all { |f| File.file? f }.last
|
|
38
|
+
|
|
39
|
+
exit unless comp_re && path
|
|
40
|
+
|
|
41
|
+
require "prism"
|
|
42
|
+
|
|
43
|
+
names, queue = [], [Prism.parse_file(path).value]
|
|
44
|
+
|
|
45
|
+
while node = queue.shift do
|
|
46
|
+
if node.type == :def_node then
|
|
47
|
+
name = node.name
|
|
48
|
+
names << name if name =~ comp_re
|
|
49
|
+
else
|
|
50
|
+
queue.concat node.compact_child_nodes # no need to process def body
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
puts names.sort
|
|
55
|
+
|
|
56
|
+
# :startdoc:
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/ruby -w
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Finds the minimal combination of a collection of items that satisfy
|
|
5
|
+
# +test+.
|
|
6
|
+
|
|
7
|
+
class ComboFinder
|
|
8
|
+
##
|
|
9
|
+
# Find the minimal combination of a collection of items that satisfy
|
|
10
|
+
# +test+.
|
|
11
|
+
#
|
|
12
|
+
# If you think of the collection as a binary tree, this algorithm
|
|
13
|
+
# does a breadth first search of the combinations that satisfy
|
|
14
|
+
# +test+.
|
|
15
|
+
#--
|
|
16
|
+
# level collection
|
|
17
|
+
#
|
|
18
|
+
# 0 A
|
|
19
|
+
# 1 B C
|
|
20
|
+
# 2 D E F G
|
|
21
|
+
# 3 1 2 3 4 5 6 7 8
|
|
22
|
+
#
|
|
23
|
+
# This assumes that A has already been tested and you're now trying
|
|
24
|
+
# to reduce the match. Starting at level 1, test B & C separately.
|
|
25
|
+
# If either test positive, reduce the search space accordingly. If
|
|
26
|
+
# not, step down to level 2 and search w/ finer granularity (ie, DF,
|
|
27
|
+
# DG, EF--DE and FG were already tested as B & C). Repeat until a
|
|
28
|
+
# minimal combination is found.
|
|
29
|
+
|
|
30
|
+
def find_minimal_combination ary
|
|
31
|
+
level, n_combos = 1, 1
|
|
32
|
+
seen = {}
|
|
33
|
+
|
|
34
|
+
d "Total number of culprits: #{ary.size}"
|
|
35
|
+
|
|
36
|
+
loop do
|
|
37
|
+
size = 2 ** (Math.log(ary.size) / Math.log(2)).round
|
|
38
|
+
divs = 2 ** level
|
|
39
|
+
done = divs >= size
|
|
40
|
+
divs = size if done
|
|
41
|
+
|
|
42
|
+
subsections = ary.each_slice(size/divs).to_a.combination(n_combos)
|
|
43
|
+
|
|
44
|
+
d
|
|
45
|
+
d "# new round!"
|
|
46
|
+
d "# of subsections in this round: #{subsections.to_a.size}"
|
|
47
|
+
d
|
|
48
|
+
|
|
49
|
+
found = subsections.find { |a|
|
|
50
|
+
b = a.flatten
|
|
51
|
+
|
|
52
|
+
next if seen[b]
|
|
53
|
+
|
|
54
|
+
d "# trying #{b.size} at level #{level} / combo #{n_combos}"
|
|
55
|
+
cache_result yield(b), b, seen
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if found then
|
|
59
|
+
ary = found.flatten
|
|
60
|
+
break if done
|
|
61
|
+
|
|
62
|
+
seen.delete ary
|
|
63
|
+
|
|
64
|
+
d "# FOUND!"
|
|
65
|
+
d "# search space size = #{ary.size}"
|
|
66
|
+
d "# resetting level and n_combos to 1"
|
|
67
|
+
|
|
68
|
+
level = n_combos = 1
|
|
69
|
+
else
|
|
70
|
+
if done then
|
|
71
|
+
n_combos += 1
|
|
72
|
+
d "# increasing n_combos to #{n_combos}"
|
|
73
|
+
break if n_combos > size
|
|
74
|
+
else
|
|
75
|
+
level += 1
|
|
76
|
+
n_combos = level
|
|
77
|
+
d "# setting level to #{level} and n_combos to #{n_combos}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
ary
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def d s = "" # :nodoc:
|
|
86
|
+
warn s if ENV["MTB_DEBUG"]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def cache_result result, data, cache # :nodoc:
|
|
90
|
+
cache[data] = true
|
|
91
|
+
|
|
92
|
+
return result if result
|
|
93
|
+
|
|
94
|
+
unless result or data.size > 128 then
|
|
95
|
+
max = data.size
|
|
96
|
+
subdiv = 2
|
|
97
|
+
until subdiv >= max do
|
|
98
|
+
data.each_slice(max / subdiv) do |sub_data|
|
|
99
|
+
cache[sub_data] = true
|
|
100
|
+
end
|
|
101
|
+
subdiv *= 2
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
result
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class Array # :nodoc:
|
|
110
|
+
##
|
|
111
|
+
# Find the minimal combination of a collection of items that satisfy +test+.
|
|
112
|
+
|
|
113
|
+
def find_minimal_combination &test
|
|
114
|
+
ComboFinder.new.find_minimal_combination(self, &test)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def find_minimal_combination_and_count
|
|
118
|
+
count = 0
|
|
119
|
+
|
|
120
|
+
found = self.find_minimal_combination do |ary|
|
|
121
|
+
count += 1
|
|
122
|
+
yield ary
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
return found, count
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
module Minitest
|
|
6
|
-
##
|
|
7
|
-
# Manually load plugins by name.
|
|
8
|
-
|
|
9
|
-
def self.load *names
|
|
10
|
-
names.each do |name|
|
|
11
|
-
require "minitest/#{name}_plugin"
|
|
12
|
-
|
|
13
|
-
self.extensions << name.to_s
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
1
|
+
#
|
|
2
|
+
# See the functionality in Minitest#load
|
|
3
|
+
#
|
|
4
|
+
warn "This file is no longer necessary. Called from #{caller.first}"
|
data/lib/minitest/parallel.rb
CHANGED
|
@@ -32,7 +32,7 @@ module Minitest
|
|
|
32
32
|
while job = queue.pop do
|
|
33
33
|
klass, method, reporter = job
|
|
34
34
|
reporter.synchronize { reporter.prerecord klass, method }
|
|
35
|
-
result =
|
|
35
|
+
result = klass.new(method).run
|
|
36
36
|
reporter.synchronize { reporter.record result }
|
|
37
37
|
end
|
|
38
38
|
end
|
|
@@ -59,11 +59,11 @@ module Minitest
|
|
|
59
59
|
def _synchronize; Minitest::Test.io_lock.synchronize { yield }; end # :nodoc:
|
|
60
60
|
|
|
61
61
|
module ClassMethods # :nodoc:
|
|
62
|
-
def
|
|
62
|
+
def run klass, method_name, reporter
|
|
63
63
|
Minitest.parallel_executor << [klass, method_name, reporter]
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
def
|
|
66
|
+
def run_order
|
|
67
67
|
:parallel
|
|
68
68
|
end
|
|
69
69
|
end
|