minitest 5.14.4 → 5.20.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 +121 -1
- data/Manifest.txt +2 -0
- data/README.rdoc +67 -35
- data/Rakefile +2 -1
- data/lib/hoe/minitest.rb +0 -4
- data/lib/minitest/assertions.rb +57 -6
- data/lib/minitest/benchmark.rb +5 -5
- data/lib/minitest/expectations.rb +18 -0
- data/lib/minitest/mock.rb +116 -33
- data/lib/minitest/pride_plugin.rb +1 -1
- data/lib/minitest/spec.rb +9 -2
- data/lib/minitest/test.rb +44 -16
- data/lib/minitest/test_task.rb +302 -0
- data/lib/minitest/unit.rb +5 -8
- data/lib/minitest.rb +101 -36
- data/test/minitest/metametameta.rb +1 -1
- data/test/minitest/test_minitest_assertions.rb +142 -16
- data/test/minitest/test_minitest_benchmark.rb +2 -2
- data/test/minitest/test_minitest_mock.rb +275 -13
- data/test/minitest/test_minitest_reporter.rb +59 -17
- data/test/minitest/test_minitest_spec.rb +101 -16
- data/test/minitest/test_minitest_test.rb +230 -32
- data/test/minitest/test_minitest_test_task.rb +46 -0
- data.tar.gz.sig +0 -0
- metadata +19 -17
- metadata.gz.sig +0 -0
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
|
-
|
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
|
-
|
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+
|
48
|
-
# +blk
|
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
|
-
|
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
|
-
|
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
|
-
|
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 %
|
151
|
-
[sym, expected_args.size, args
|
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
|
-
|
161
|
-
|
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
|
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
|
-
|
228
|
-
|
229
|
-
val_or_callable.call
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
-
|
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
|
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
|
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
|
-
#
|
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
|
-
|
71
|
-
methods.sort.
|
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
|
-
|
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
|
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"
|
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
|