minitest 5.14.4 → 5.20.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/minitest/mock.rb CHANGED
@@ -28,11 +28,19 @@ module Minitest # :nodoc:
28
28
  end
29
29
 
30
30
  overridden_methods.map(&:to_sym).each do |method_id|
31
- define_method method_id do |*args, &b|
31
+ define_method method_id do |*args, **kwargs, &b|
32
32
  if @expected_calls.key? method_id then
33
- method_missing(method_id, *args, &b)
33
+ if kwargs.empty? then # FIX: drop this after 2.7 dead
34
+ method_missing(method_id, *args, &b)
35
+ else
36
+ method_missing(method_id, *args, **kwargs, &b)
37
+ end
34
38
  else
35
- super(*args, &b)
39
+ if kwargs.empty? then # FIX: drop this after 2.7 dead
40
+ super(*args, &b)
41
+ else
42
+ super(*args, **kwargs, &b)
43
+ end
36
44
  end
37
45
  end
38
46
  end
@@ -43,9 +51,11 @@ module Minitest # :nodoc:
43
51
  @actual_calls = Hash.new { |calls, name| calls[name] = [] }
44
52
  end
45
53
 
54
+ @@KW_WARNED = false # :nodoc:
55
+
46
56
  ##
47
- # Expect that method +name+ is called, optionally with +args+ or a
48
- # +blk+, and returns +retval+.
57
+ # Expect that method +name+ is called, optionally with +args+ (and
58
+ # +kwargs+ or a +blk+), and returns +retval+.
49
59
  #
50
60
  # @mock.expect(:meaning_of_life, 42)
51
61
  # @mock.meaning_of_life # => 42
@@ -78,15 +88,31 @@ module Minitest # :nodoc:
78
88
  # @mock.ordinal_increment # => raises MockExpectationError "No more expects available for :ordinal_increment"
79
89
  #
80
90
 
81
- def expect name, retval, args = [], &blk
91
+ def expect name, retval, args = [], **kwargs, &blk
82
92
  name = name.to_sym
83
93
 
84
94
  if block_given?
85
95
  raise ArgumentError, "args ignored when block given" unless args.empty?
96
+ raise ArgumentError, "kwargs ignored when block given" unless kwargs.empty?
86
97
  @expected_calls[name] << { :retval => retval, :block => blk }
87
98
  else
88
99
  raise ArgumentError, "args must be an array" unless Array === args
89
- @expected_calls[name] << { :retval => retval, :args => args }
100
+
101
+ if ENV["MT_KWARGS_HAC\K"] && (Hash === args.last ||
102
+ Hash == args.last) then
103
+ if kwargs.empty? then
104
+ kwargs = args.pop
105
+ else
106
+ unless @@KW_WARNED then
107
+ from = caller.first
108
+ warn "Using MT_KWARGS_HAC\K yet passing kwargs. From #{from}"
109
+ @@KW_WARNED = true
110
+ end
111
+ end
112
+ end
113
+
114
+ @expected_calls[name] <<
115
+ { :retval => retval, :args => args, :kwargs => kwargs }
90
116
  end
91
117
  self
92
118
  end
@@ -94,7 +120,13 @@ module Minitest # :nodoc:
94
120
  def __call name, data # :nodoc:
95
121
  case data
96
122
  when Hash then
97
- "#{name}(#{data[:args].inspect[1..-2]}) => #{data[:retval].inspect}"
123
+ args = data[:args].inspect[1..-2]
124
+ kwargs = data[:kwargs]
125
+ if kwargs && !kwargs.empty? then
126
+ args << ", " unless args.empty?
127
+ args << kwargs.inspect[1..-2]
128
+ end
129
+ "#{name}(#{args}) => #{data[:retval].inspect}"
98
130
  else
99
131
  data.map { |d| __call name, d }.join ", "
100
132
  end
@@ -115,10 +147,14 @@ module Minitest # :nodoc:
115
147
  true
116
148
  end
117
149
 
118
- def method_missing sym, *args, &block # :nodoc:
150
+ def method_missing sym, *args, **kwargs, &block # :nodoc:
119
151
  unless @expected_calls.key?(sym) then
120
152
  if @delegator && @delegator.respond_to?(sym)
121
- return @delegator.public_send(sym, *args, &block)
153
+ if kwargs.empty? then # FIX: drop this after 2.7 dead
154
+ return @delegator.public_send(sym, *args, &block)
155
+ else
156
+ return @delegator.public_send(sym, *args, **kwargs, &block)
157
+ end
122
158
  else
123
159
  raise NoMethodError, "unmocked method %p, expected one of %p" %
124
160
  [sym, @expected_calls.keys.sort_by(&:to_s)]
@@ -129,26 +165,34 @@ module Minitest # :nodoc:
129
165
  expected_call = @expected_calls[sym][index]
130
166
 
131
167
  unless expected_call then
132
- raise MockExpectationError, "No more expects available for %p: %p" %
133
- [sym, args]
168
+ raise MockExpectationError, "No more expects available for %p: %p %p" %
169
+ [sym, args, kwargs]
134
170
  end
135
171
 
136
- expected_args, retval, val_block =
137
- expected_call.values_at(:args, :retval, :block)
172
+ expected_args, expected_kwargs, retval, val_block =
173
+ expected_call.values_at(:args, :kwargs, :retval, :block)
174
+
175
+ expected_kwargs = kwargs.map { |ak, av| [ak, Object] }.to_h if
176
+ Hash == expected_kwargs
138
177
 
139
178
  if val_block then
140
179
  # keep "verify" happy
141
180
  @actual_calls[sym] << expected_call
142
181
 
143
- raise MockExpectationError, "mocked method %p failed block w/ %p" %
144
- [sym, args] unless val_block.call(*args, &block)
182
+ raise MockExpectationError, "mocked method %p failed block w/ %p %p" %
183
+ [sym, args, kwargs] unless val_block.call(*args, **kwargs, &block)
145
184
 
146
185
  return retval
147
186
  end
148
187
 
149
188
  if expected_args.size != args.size then
150
- raise ArgumentError, "mocked method %p expects %d arguments, got %d" %
151
- [sym, expected_args.size, args.size]
189
+ raise ArgumentError, "mocked method %p expects %d arguments, got %p" %
190
+ [sym, expected_args.size, args]
191
+ end
192
+
193
+ if expected_kwargs.size != kwargs.size then
194
+ raise ArgumentError, "mocked method %p expects %d keyword arguments, got %p" %
195
+ [sym, expected_kwargs.size, kwargs]
152
196
  end
153
197
 
154
198
  zipped_args = expected_args.zip(args)
@@ -157,13 +201,33 @@ module Minitest # :nodoc:
157
201
  }
158
202
 
159
203
  unless fully_matched then
160
- raise MockExpectationError, "mocked method %p called with unexpected arguments %p" %
161
- [sym, args]
204
+ fmt = "mocked method %p called with unexpected arguments %p"
205
+ raise MockExpectationError, fmt % [sym, args]
206
+ end
207
+
208
+ unless expected_kwargs.keys.sort == kwargs.keys.sort then
209
+ fmt = "mocked method %p called with unexpected keywords %p vs %p"
210
+ raise MockExpectationError, fmt % [sym, expected_kwargs.keys, kwargs.keys]
211
+ end
212
+
213
+ zipped_kwargs = expected_kwargs.map { |ek, ev|
214
+ av = kwargs[ek]
215
+ [ek, [ev, av]]
216
+ }.to_h
217
+
218
+ fully_matched = zipped_kwargs.all? { |ek, (ev, av)|
219
+ ev === av or ev == av
220
+ }
221
+
222
+ unless fully_matched then
223
+ fmt = "mocked method %p called with unexpected keyword arguments %p vs %p"
224
+ raise MockExpectationError, fmt % [sym, expected_kwargs, kwargs]
162
225
  end
163
226
 
164
227
  @actual_calls[sym] << {
165
228
  :retval => retval,
166
- :args => zipped_args.map! { |mod, a| mod === a ? mod : a },
229
+ :args => zipped_args.map { |e, a| e === a ? e : a },
230
+ :kwargs => zipped_kwargs.map { |k, (e, a)| [k, e === a ? e : a] }.to_h,
167
231
  }
168
232
 
169
233
  retval
@@ -211,31 +275,50 @@ class Object
211
275
  # NOTE: keyword args in callables are NOT checked for correctness
212
276
  # against the existing method. Too many edge cases to be worth it.
213
277
 
214
- def stub name, val_or_callable, *block_args
278
+ def stub name, val_or_callable, *block_args, **block_kwargs, &block
215
279
  new_name = "__minitest_stub__#{name}"
216
280
 
217
281
  metaclass = class << self; self; end
218
282
 
219
283
  if respond_to? name and not methods.map(&:to_s).include? name.to_s then
220
- metaclass.send :define_method, name do |*args|
221
- super(*args)
284
+ metaclass.send :define_method, name do |*args, **kwargs|
285
+ super(*args, **kwargs)
222
286
  end
223
287
  end
224
288
 
225
289
  metaclass.send :alias_method, new_name, name
226
290
 
227
- metaclass.send :define_method, name do |*args, &blk|
228
- if val_or_callable.respond_to? :call then
229
- val_or_callable.call(*args, &blk)
230
- else
231
- blk.call(*block_args) if blk
232
- val_or_callable
291
+ if ENV["MT_KWARGS_HAC\K"] then
292
+ metaclass.send :define_method, name do |*args, &blk|
293
+ if val_or_callable.respond_to? :call then
294
+ val_or_callable.call(*args, &blk)
295
+ else
296
+ blk.call(*block_args, **block_kwargs) if blk
297
+ val_or_callable
298
+ end
299
+ end
300
+ else
301
+ metaclass.send :define_method, name do |*args, **kwargs, &blk|
302
+ if val_or_callable.respond_to? :call then
303
+ if kwargs.empty? then # FIX: drop this after 2.7 dead
304
+ val_or_callable.call(*args, &blk)
305
+ else
306
+ val_or_callable.call(*args, **kwargs, &blk)
307
+ end
308
+ else
309
+ if blk then
310
+ if block_kwargs.empty? then # FIX: drop this after 2.7 dead
311
+ blk.call(*block_args)
312
+ else
313
+ blk.call(*block_args, **block_kwargs)
314
+ end
315
+ end
316
+ val_or_callable
317
+ end
233
318
  end
234
319
  end
235
320
 
236
- metaclass.send(:ruby2_keywords, name) if metaclass.respond_to?(:ruby2_keywords, true)
237
-
238
- yield self
321
+ block[self]
239
322
  ensure
240
323
  metaclass.send :undef_method, name
241
324
  metaclass.send :alias_method, name, new_name
@@ -48,7 +48,7 @@ module Minitest
48
48
  def initialize io # :nodoc:
49
49
  @io = io
50
50
  # stolen from /System/Library/Perl/5.10.0/Term/ANSIColor.pm
51
- # also reference http://en.wikipedia.org/wiki/ANSI_escape_code
51
+ # also reference https://en.wikipedia.org/wiki/ANSI_escape_code
52
52
  @colors ||= (31..36).to_a
53
53
  @size = @colors.size
54
54
  @index = 0
data/lib/minitest/spec.rb CHANGED
@@ -4,15 +4,21 @@ class Module # :nodoc:
4
4
  def infect_an_assertion meth, new_name, dont_flip = false # :nodoc:
5
5
  block = dont_flip == :block
6
6
  dont_flip = false if block
7
+ target_obj = block ? '_{obj.method}' : '_(obj)'
8
+
9
+ # https://eregon.me/blog/2021/02/13/correct-delegation-in-ruby-2-27-3.html
10
+ # Drop this when we can drop ruby 2.6 (aka after rails 6.1 EOL, ~2024-06)
11
+ kw_extra = "ruby2_keywords %p" % [new_name] if respond_to?(:ruby2_keywords, true)
7
12
 
8
13
  # warn "%-22p -> %p %p" % [meth, new_name, dont_flip]
9
14
  self.class_eval <<-EOM, __FILE__, __LINE__ + 1
10
15
  def #{new_name} *args
11
16
  where = Minitest.filter_backtrace(caller).first
12
17
  where = where.split(/:in /, 2).first # clean up noise
13
- warn "DEPRECATED: global use of #{new_name} from #\{where}. Use _(obj).#{new_name} instead. This will fail in Minitest 6."
18
+ Kernel.warn "DEPRECATED: global use of #{new_name} from #\{where}. Use #{target_obj}.#{new_name} instead. This will fail in Minitest 6."
14
19
  Minitest::Expectation.new(self, Minitest::Spec.current).#{new_name}(*args)
15
20
  end
21
+ #{kw_extra}
16
22
  EOM
17
23
 
18
24
  Minitest::Expectation.class_eval <<-EOM, __FILE__, __LINE__ + 1
@@ -27,6 +33,7 @@ class Module # :nodoc:
27
33
  ctx.#{meth}(args.first, target, *args[1..-1])
28
34
  end
29
35
  end
36
+ #{kw_extra}
30
37
  EOM
31
38
  end
32
39
  end
@@ -65,7 +72,7 @@ module Kernel
65
72
  #
66
73
  # For some suggestions on how to improve your specs, try:
67
74
  #
68
- # http://betterspecs.org
75
+ # https://betterspecs.org
69
76
  #
70
77
  # but do note that several items there are debatable or specific to
71
78
  # rspec.
data/lib/minitest/test.rb CHANGED
@@ -18,6 +18,10 @@ module Minitest
18
18
 
19
19
  PASSTHROUGH_EXCEPTIONS = [NoMemoryError, SignalException, SystemExit] # :nodoc:
20
20
 
21
+ SETUP_METHODS = %w[ before_setup setup after_setup ] # :nodoc:
22
+
23
+ TEARDOWN_METHODS = %w[ before_teardown teardown after_teardown ] # :nodoc:
24
+
21
25
  # :stopdoc:
22
26
  class << self; attr_accessor :io_lock; end
23
27
  self.io_lock = Mutex.new
@@ -67,8 +71,8 @@ module Minitest
67
71
 
68
72
  case self.test_order
69
73
  when :random, :parallel then
70
- max = methods.size
71
- methods.sort.sort_by { rand max }
74
+ srand Minitest.seed
75
+ methods.sort.shuffle
72
76
  when :alpha, :sorted then
73
77
  methods.sort
74
78
  else
@@ -76,16 +80,6 @@ module Minitest
76
80
  end
77
81
  end
78
82
 
79
- ##
80
- # Defines the order to run tests (:random by default). Override
81
- # this or use a convenience method to change it for your tests.
82
-
83
- def self.test_order
84
- :random
85
- end
86
-
87
- TEARDOWN_METHODS = %w[ before_teardown teardown after_teardown ] # :nodoc:
88
-
89
83
  ##
90
84
  # Runs a single test with setup/teardown hooks.
91
85
 
@@ -93,7 +87,9 @@ module Minitest
93
87
  with_info_handler do
94
88
  time_it do
95
89
  capture_exceptions do
96
- before_setup; setup; after_setup
90
+ SETUP_METHODS.each do |hook|
91
+ self.send hook
92
+ end
97
93
 
98
94
  self.send self.name
99
95
  end
@@ -145,7 +141,7 @@ module Minitest
145
141
  # end
146
142
  # end
147
143
  #
148
- # class MiniTest::Test
144
+ # class Minitest::Test
149
145
  # include MyMinitestPlugin
150
146
  # end
151
147
 
@@ -198,7 +194,39 @@ module Minitest
198
194
  rescue Assertion => e
199
195
  self.failures << e
200
196
  rescue Exception => e
201
- self.failures << UnexpectedError.new(e)
197
+ self.failures << UnexpectedError.new(sanitize_exception e)
198
+ end
199
+
200
+ def sanitize_exception e # :nodoc:
201
+ Marshal.dump e
202
+ e # good: use as-is
203
+ rescue
204
+ neuter_exception e
205
+ end
206
+
207
+ def neuter_exception e # :nodoc:
208
+ bt = e.backtrace
209
+ msg = e.message.dup
210
+
211
+ new_exception e.class, msg, bt # e.class can be a problem...
212
+ rescue
213
+ msg.prepend "Neutered Exception #{e.class}: "
214
+
215
+ new_exception RuntimeError, msg, bt, true # but if this raises, we die
216
+ end
217
+
218
+ def new_exception klass, msg, bt, kill = false # :nodoc:
219
+ ne = klass.new msg
220
+ ne.set_backtrace bt
221
+
222
+ if kill then
223
+ ne.instance_variables.each do |v|
224
+ ne.remove_instance_variable v
225
+ end
226
+ end
227
+
228
+ Marshal.dump ne # can raise TypeError
229
+ ne
202
230
  end
203
231
 
204
232
  def with_info_handler &block # :nodoc:
@@ -217,4 +245,4 @@ module Minitest
217
245
  end # Test
218
246
  end
219
247
 
220
- require "minitest/unit" unless defined?(MiniTest) # compatibility layer only
248
+ require "minitest/unit" if ENV["MT_COMPAT"] # compatibility layer only
@@ -0,0 +1,302 @@
1
+ require "shellwords"
2
+ require "rbconfig"
3
+ require "rake/tasklib"
4
+
5
+ module Minitest # :nodoc:
6
+
7
+ ##
8
+ # Minitest::TestTask is a rake helper that generates several rake
9
+ # tasks under the main test task's name-space.
10
+ #
11
+ # task <name> :: the main test task
12
+ # task <name>:cmd :: prints the command to use
13
+ # task <name>:deps :: runs each test file by itself to find dependency errors
14
+ # task <name>:slow :: runs the tests and reports the slowest 25 tests.
15
+ #
16
+ # Examples:
17
+ #
18
+ # Minitest::TestTask.create
19
+ #
20
+ # The most basic and default setup.
21
+ #
22
+ # Minitest::TestTask.create :my_tests
23
+ #
24
+ # The most basic/default setup, but with a custom name
25
+ #
26
+ # Minitest::TestTask.create :unit do |t|
27
+ # t.test_globs = ["test/unit/**/*_test.rb"]
28
+ # t.warning = false
29
+ # end
30
+ #
31
+ # Customize the name and only run unit tests.
32
+ #
33
+ # NOTE: To hook this task up to the default, make it a dependency:
34
+ #
35
+ # task default: :unit
36
+
37
+ class TestTask < Rake::TaskLib
38
+ WINDOWS = RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ # :nodoc:
39
+
40
+ ##
41
+ # Create several test-oriented tasks under +name+. Takes an
42
+ # optional block to customize variables.
43
+
44
+ def self.create name = :test, &block
45
+ task = new name
46
+ task.instance_eval(&block) if block
47
+ task.process_env
48
+ task.define
49
+ task
50
+ end
51
+
52
+ ##
53
+ # Extra arguments to pass to the tests. Defaults empty but gets
54
+ # populated by a number of enviroment variables:
55
+ #
56
+ # N (-n flag) :: a string or regexp of tests to run.
57
+ # X (-e flag) :: a string or regexp of tests to exclude.
58
+ # A (arg) :: quick way to inject an arbitrary argument (eg A=--help).
59
+ #
60
+ # See #process_env
61
+
62
+ attr_accessor :extra_args
63
+
64
+ ##
65
+ # The code to load the framework. Defaults to requiring
66
+ # minitest/autorun...
67
+ #
68
+ # Why do I have this as an option?
69
+
70
+ attr_accessor :framework
71
+
72
+ ##
73
+ # Extra library directories to include. Defaults to %w[lib test
74
+ # .]. Also uses $MT_LIB_EXTRAS allowing you to dynamically
75
+ # override/inject directories for custom runs.
76
+
77
+ attr_accessor :libs
78
+
79
+ ##
80
+ # The name of the task and base name for the other tasks generated.
81
+
82
+ attr_accessor :name
83
+
84
+ ##
85
+ # File globs to find test files. Defaults to something sensible to
86
+ # find test files under the test directory.
87
+
88
+ attr_accessor :test_globs
89
+
90
+ ##
91
+ # Turn on ruby warnings (-w flag). Defaults to true.
92
+
93
+ attr_accessor :warning
94
+
95
+ ##
96
+ # Optional: Additional ruby to run before the test framework is loaded.
97
+
98
+ attr_accessor :test_prelude
99
+
100
+ ##
101
+ # Print out commands as they run. Defaults to Rake's +trace+ (-t
102
+ # flag) option.
103
+
104
+ attr_accessor :verbose
105
+
106
+ ##
107
+ # Use TestTask.create instead.
108
+
109
+ def initialize name = :test # :nodoc:
110
+ self.extra_args = []
111
+ self.framework = %(require "minitest/autorun")
112
+ self.libs = %w[lib test .]
113
+ self.name = name
114
+ self.test_globs = ["test/**/test_*.rb",
115
+ "test/**/*_test.rb"]
116
+ self.test_prelude = nil
117
+ self.verbose = Rake.application.options.trace
118
+ self.warning = true
119
+ end
120
+
121
+ ##
122
+ # Extract variables from the environment and convert them to
123
+ # command line arguments. See #extra_args.
124
+ #
125
+ # Environment Variables:
126
+ #
127
+ # MT_LIB_EXTRAS :: Extra libs to dynamically override/inject for custom runs.
128
+ # N :: Tests to run (string or /regexp/).
129
+ # X :: Tests to exclude (string or /regexp/).
130
+ # A :: Any extra arguments. Honors shell quoting.
131
+ #
132
+ # Deprecated:
133
+ #
134
+ # TESTOPTS :: For argument passing, use +A+.
135
+ # N :: For parallel testing, use +MT_CPU+.
136
+ # FILTER :: Same as +TESTOPTS+.
137
+
138
+ def process_env
139
+ warn "TESTOPTS is deprecated in Minitest::TestTask. Use A instead" if
140
+ ENV["TESTOPTS"]
141
+ warn "FILTER is deprecated in Minitest::TestTask. Use A instead" if
142
+ ENV["FILTER"]
143
+ warn "N is deprecated in Minitest::TestTask. Use MT_CPU instead" if
144
+ ENV["N"] && ENV["N"].to_i > 0
145
+
146
+ lib_extras = (ENV["MT_LIB_EXTRAS"] || "").split File::PATH_SEPARATOR
147
+ self.libs[0,0] = lib_extras
148
+
149
+ extra_args << "-n" << ENV["N"] if ENV["N"]
150
+ extra_args << "-e" << ENV["X"] if ENV["X"]
151
+ extra_args.concat Shellwords.split(ENV["TESTOPTS"]) if ENV["TESTOPTS"]
152
+ extra_args.concat Shellwords.split(ENV["FILTER"]) if ENV["FILTER"]
153
+ extra_args.concat Shellwords.split(ENV["A"]) if ENV["A"]
154
+
155
+ ENV.delete "N" if ENV["N"]
156
+
157
+ # TODO? RUBY_DEBUG = ENV["RUBY_DEBUG"]
158
+ # TODO? ENV["RUBY_FLAGS"]
159
+
160
+ extra_args.compact!
161
+ end
162
+
163
+ def define # :nodoc:
164
+ desc "Run the test suite. Use N, X, A, and TESTOPTS to add flags/args."
165
+ task name do
166
+ ruby make_test_cmd, verbose:verbose
167
+ end
168
+
169
+ desc "Print out the test command. Good for profiling and other tools."
170
+ task "#{name}:cmd" do
171
+ puts "ruby #{make_test_cmd}"
172
+ end
173
+
174
+ desc "Show which test files fail when run in isolation."
175
+ task "#{name}:isolated" do
176
+ tests = Dir[*self.test_globs].uniq
177
+
178
+ # 3 seems to be the magic number... (tho not by that much)
179
+ bad, good, n = {}, [], (ENV.delete("K") || 3).to_i
180
+ file = ENV.delete("F")
181
+ times = {}
182
+
183
+ tt0 = Time.now
184
+
185
+ n.threads_do tests.sort do |path|
186
+ t0 = Time.now
187
+ output = `#{Gem.ruby} #{make_test_cmd path} 2>&1`
188
+ t1 = Time.now - t0
189
+
190
+ times[path] = t1
191
+
192
+ if $?.success?
193
+ $stderr.print "."
194
+ good << path
195
+ else
196
+ $stderr.print "x"
197
+ bad[path] = output
198
+ end
199
+ end
200
+
201
+ puts "done"
202
+ puts "Ran in %.2f seconds" % [ Time.now - tt0 ]
203
+
204
+ if file then
205
+ require "json"
206
+ File.open file, "w" do |io|
207
+ io.puts JSON.pretty_generate times
208
+ end
209
+ end
210
+
211
+ unless good.empty?
212
+ puts
213
+ puts "# Good tests:"
214
+ puts
215
+ good.sort.each do |path|
216
+ puts "%.2fs: %s" % [times[path], path]
217
+ end
218
+ end
219
+
220
+ unless bad.empty?
221
+ puts
222
+ puts "# Bad tests:"
223
+ puts
224
+ bad.keys.sort.each do |path|
225
+ puts "%.2fs: %s" % [times[path], path]
226
+ end
227
+ puts
228
+ puts "# Bad Test Output:"
229
+ puts
230
+ bad.sort.each do |path, output|
231
+ puts
232
+ puts "# #{path}:"
233
+ puts output
234
+ end
235
+ exit 1
236
+ end
237
+ end
238
+
239
+ task "#{name}:deps" => "#{name}:isolated" # now just an alias
240
+
241
+ desc "Show bottom 25 tests wrt time."
242
+ task "#{name}:slow" do
243
+ sh ["rake #{name} A=-v",
244
+ "egrep '#test_.* s = .'",
245
+ "sort -n -k2 -t=",
246
+ "tail -25"].join " | "
247
+ end
248
+ end
249
+
250
+ ##
251
+ # Generate the test command-line.
252
+
253
+ def make_test_cmd globs = test_globs
254
+ tests = []
255
+ tests.concat Dir[*globs].sort.shuffle # TODO: SEED -> srand first?
256
+ tests.map! { |f| %(require "#{f}") }
257
+
258
+ runner = []
259
+ runner << test_prelude if test_prelude
260
+ runner << framework
261
+ runner.concat tests
262
+ runner = runner.join "; "
263
+
264
+ args = []
265
+ args << "-I#{libs.join(File::PATH_SEPARATOR)}" unless libs.empty?
266
+ args << "-w" if warning
267
+ args << '-e'
268
+ args << "'#{runner}'"
269
+ args << '--'
270
+ args << extra_args.map(&:shellescape)
271
+
272
+ args.join " "
273
+ end
274
+ end
275
+ end
276
+
277
+ class Work < Queue # :nodoc:
278
+ def initialize jobs = [] # :nodoc:
279
+ super()
280
+
281
+ jobs.each do |job|
282
+ self << job
283
+ end
284
+
285
+ close
286
+ end
287
+ end
288
+
289
+ class Integer # :nodoc:
290
+ def threads_do(jobs) # :nodoc:
291
+ require "thread"
292
+ q = Work.new jobs
293
+
294
+ self.times.map {
295
+ Thread.new do
296
+ while job = q.pop # go until quit value
297
+ yield job
298
+ end
299
+ end
300
+ }.each(&:join)
301
+ end
302
+ end