test-unit-minitest 0.9.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 +7 -0
- data/LICENSE +56 -0
- data/README.md +29 -0
- data/bin/testrb +3 -0
- data/lib/test/unit.rb +876 -0
- data/lib/test/unit/assertions.rb +403 -0
- data/lib/test/unit/parallel.rb +189 -0
- data/lib/test/unit/testcase.rb +34 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6650ecbb2b27cc3923b098b2696c09b5ff8c354b
|
4
|
+
data.tar.gz: dbd05d852ce49a65deeecc3a13afe2e01874428c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 12dc3d8a7789fa414da3050974857b53b2969663901d8ece1c50472c25ca51ba9bceb0d206b734415499aaff06d1983a88492765d8230a3791110fd50b7531b3
|
7
|
+
data.tar.gz: 98b9b4c9fe1da1bb1e736d7c38313ae8890ed40b4403ec8f7066f6b87ccf1821e7285f08fe776293063bac64c80bf497a339c3816589e77c6330ee95c51bc0e4
|
data/LICENSE
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
|
2
|
+
You can redistribute it and/or modify it under either the terms of the
|
3
|
+
2-clause BSDL (see the file BSDL), or the conditions below:
|
4
|
+
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
6
|
+
software without restriction, provided that you duplicate all of the
|
7
|
+
original copyright notices and associated disclaimers.
|
8
|
+
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
10
|
+
you do at least ONE of the following:
|
11
|
+
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
13
|
+
make them Freely Available, such as by posting said
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
15
|
+
the author to include your modifications in the software.
|
16
|
+
|
17
|
+
b) use the modified software only within your corporation or
|
18
|
+
organization.
|
19
|
+
|
20
|
+
c) give non-standard binaries non-standard names, with
|
21
|
+
instructions on where to get the original software distribution.
|
22
|
+
|
23
|
+
d) make other distribution arrangements with the author.
|
24
|
+
|
25
|
+
3. You may distribute the software in object code or binary form,
|
26
|
+
provided that you do at least ONE of the following:
|
27
|
+
|
28
|
+
a) distribute the binaries and library files of the software,
|
29
|
+
together with instructions (in the manual page or equivalent)
|
30
|
+
on where to get the original distribution.
|
31
|
+
|
32
|
+
b) accompany the distribution with the machine-readable source of
|
33
|
+
the software.
|
34
|
+
|
35
|
+
c) give non-standard binaries non-standard names, with
|
36
|
+
instructions on where to get the original software distribution.
|
37
|
+
|
38
|
+
d) make other distribution arrangements with the author.
|
39
|
+
|
40
|
+
4. You may modify and include the part of the software into any other
|
41
|
+
software (possibly commercial). But some files in the distribution
|
42
|
+
are not written by the author, so that they are not under these terms.
|
43
|
+
|
44
|
+
For the list of those files and their copying conditions, see the
|
45
|
+
file LEGAL.
|
46
|
+
|
47
|
+
5. The scripts and library files supplied as input to or produced as
|
48
|
+
output from the software do not automatically fall under the
|
49
|
+
copyright of the software, but belong to whomever generated them,
|
50
|
+
and may be sold commercially, and may be aggregated with this
|
51
|
+
software.
|
52
|
+
|
53
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
54
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
55
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
56
|
+
PURPOSE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
[](https://travis-ci.org/iGEL/test-unit-minitest)
|
2
|
+
# minitest wrapper for Test::Unit
|
3
|
+
|
4
|
+
Between Ruby 1.9 and 2.1, Ruby came with a wrapper around minitest to
|
5
|
+
provide the same interface of Test::Unit that was available with Ruby
|
6
|
+
1.8. Ruby 2.2 comes with bundled gems of Test::Unit, but this is not
|
7
|
+
the wrapper. Some tests for the wrapped Test::Unit actually used
|
8
|
+
minitest features which are not available in the real thing, for
|
9
|
+
example some assertions of Rails 4.0.
|
10
|
+
|
11
|
+
This is a copy of the Wrapper from Ruby 2.1 as written by Shota
|
12
|
+
Fukumori. The License is same as Ruby's.
|
13
|
+
|
14
|
+
**I don't plan to maintain this for a long time. If possible, use
|
15
|
+
minitest directly.**
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
If the tests require the correct files from Test::Unit, just drop this
|
20
|
+
in your `Gemfile`:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem 'test-unit-minitest', require: nil
|
24
|
+
```
|
25
|
+
|
26
|
+
Otherwise this:
|
27
|
+
```ruby
|
28
|
+
gem 'test-unit-minitest', require: 'test/unit'
|
29
|
+
```
|
data/bin/testrb
ADDED
data/lib/test/unit.rb
ADDED
@@ -0,0 +1,876 @@
|
|
1
|
+
require 'minitest/unit'
|
2
|
+
require 'test/unit/assertions'
|
3
|
+
require 'test/unit/testcase'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
# See Test::Unit
|
7
|
+
module Test
|
8
|
+
##
|
9
|
+
# Test::Unit is an implementation of the xUnit testing framework for Ruby.
|
10
|
+
#
|
11
|
+
# If you are writing new test code, please use MiniTest instead of Test::Unit.
|
12
|
+
#
|
13
|
+
# Test::Unit has been left in the standard library to support legacy test
|
14
|
+
# suites.
|
15
|
+
module Unit
|
16
|
+
TEST_UNIT_IMPLEMENTATION = 'test/unit compatibility layer using minitest' # :nodoc:
|
17
|
+
|
18
|
+
module RunCount # :nodoc: all
|
19
|
+
@@run_count = 0
|
20
|
+
|
21
|
+
def self.have_run?
|
22
|
+
@@run_count.nonzero?
|
23
|
+
end
|
24
|
+
|
25
|
+
def run(*)
|
26
|
+
@@run_count += 1
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def run_once
|
31
|
+
return if have_run?
|
32
|
+
return if $! # don't run if there was an exception
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
module_function :run_once
|
36
|
+
end
|
37
|
+
|
38
|
+
module Options # :nodoc: all
|
39
|
+
def initialize(*, &block)
|
40
|
+
@init_hook = block
|
41
|
+
@options = nil
|
42
|
+
super(&nil)
|
43
|
+
end
|
44
|
+
|
45
|
+
def option_parser
|
46
|
+
@option_parser ||= OptionParser.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_args(args = [])
|
50
|
+
return @options if @options
|
51
|
+
orig_args = args.dup
|
52
|
+
options = {}
|
53
|
+
opts = option_parser
|
54
|
+
setup_options(opts, options)
|
55
|
+
opts.parse!(args)
|
56
|
+
orig_args -= args
|
57
|
+
args = @init_hook.call(args, options) if @init_hook
|
58
|
+
non_options(args, options)
|
59
|
+
@help = orig_args.map { |s| s =~ /[\s|&<>$()]/ ? s.inspect : s }.join " "
|
60
|
+
@options = options
|
61
|
+
if @options[:parallel]
|
62
|
+
@files = args
|
63
|
+
@args = orig_args
|
64
|
+
end
|
65
|
+
options
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def setup_options(opts, options)
|
70
|
+
opts.separator 'minitest options:'
|
71
|
+
opts.version = MiniTest::Unit::VERSION
|
72
|
+
|
73
|
+
options[:retry] = true
|
74
|
+
options[:job_status] = nil
|
75
|
+
|
76
|
+
opts.on '-h', '--help', 'Display this help.' do
|
77
|
+
puts opts
|
78
|
+
exit
|
79
|
+
end
|
80
|
+
|
81
|
+
opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m|
|
82
|
+
options[:seed] = m
|
83
|
+
end
|
84
|
+
|
85
|
+
opts.on '-v', '--verbose', "Verbose. Show progress processing files." do
|
86
|
+
options[:verbose] = true
|
87
|
+
self.verbose = options[:verbose]
|
88
|
+
end
|
89
|
+
|
90
|
+
opts.on '-n', '--name PATTERN', "Filter test names on pattern." do |a|
|
91
|
+
options[:filter] = a
|
92
|
+
end
|
93
|
+
|
94
|
+
opts.on '--jobs-status [TYPE]', [:normal, :replace],
|
95
|
+
"Show status of jobs every file; Disabled when --jobs isn't specified." do |type|
|
96
|
+
options[:job_status] = type || :normal
|
97
|
+
end
|
98
|
+
|
99
|
+
opts.on '-j N', '--jobs N', "Allow run tests with N jobs at once" do |a|
|
100
|
+
if /^t/ =~ a
|
101
|
+
options[:testing] = true # For testing
|
102
|
+
options[:parallel] = a[1..-1].to_i
|
103
|
+
else
|
104
|
+
options[:parallel] = a.to_i
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
opts.on '--separate', "Restart job process after one testcase has done" do
|
109
|
+
options[:parallel] ||= 1
|
110
|
+
options[:separate] = true
|
111
|
+
end
|
112
|
+
|
113
|
+
opts.on '--retry', "Retry running testcase when --jobs specified" do
|
114
|
+
options[:retry] = true
|
115
|
+
end
|
116
|
+
|
117
|
+
opts.on '--no-retry', "Disable --retry" do
|
118
|
+
options[:retry] = false
|
119
|
+
end
|
120
|
+
|
121
|
+
opts.on '--ruby VAL', "Path to ruby; It'll have used at -j option" do |a|
|
122
|
+
options[:ruby] = a.split(/ /).reject(&:empty?)
|
123
|
+
end
|
124
|
+
|
125
|
+
opts.on '-q', '--hide-skip', 'Hide skipped tests' do
|
126
|
+
options[:hide_skip] = true
|
127
|
+
end
|
128
|
+
|
129
|
+
opts.on '--show-skip', 'Show skipped tests' do
|
130
|
+
options[:hide_skip] = false
|
131
|
+
end
|
132
|
+
|
133
|
+
opts.on '--color[=WHEN]',
|
134
|
+
[:always, :never, :auto],
|
135
|
+
"colorize the output. WHEN defaults to 'always'", "or can be 'never' or 'auto'." do |c|
|
136
|
+
options[:color] = c || :always
|
137
|
+
end
|
138
|
+
|
139
|
+
opts.on '--tty[=WHEN]',
|
140
|
+
[:yes, :no],
|
141
|
+
"force to output tty control. WHEN defaults to 'yes'", "or can be 'no'." do |c|
|
142
|
+
@tty = c != :no
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def non_options(files, options)
|
147
|
+
begin
|
148
|
+
require "rbconfig"
|
149
|
+
rescue LoadError
|
150
|
+
warn "#{caller(1)[0]}: warning: Parallel running disabled because can't get path to ruby; run specify with --ruby argument"
|
151
|
+
options[:parallel] = nil
|
152
|
+
else
|
153
|
+
options[:ruby] ||= [RbConfig.ruby]
|
154
|
+
end
|
155
|
+
|
156
|
+
true
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
module GlobOption # :nodoc: all
|
161
|
+
@@testfile_prefix = "test"
|
162
|
+
|
163
|
+
def setup_options(parser, options)
|
164
|
+
super
|
165
|
+
parser.on '-b', '--basedir=DIR', 'Base directory of test suites.' do |dir|
|
166
|
+
options[:base_directory] = dir
|
167
|
+
end
|
168
|
+
parser.on '-x', '--exclude PATTERN', 'Exclude test files on pattern.' do |pattern|
|
169
|
+
(options[:reject] ||= []) << pattern
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def non_options(files, options)
|
174
|
+
paths = [options.delete(:base_directory), nil].uniq
|
175
|
+
if reject = options.delete(:reject)
|
176
|
+
reject_pat = Regexp.union(reject.map {|r| /#{r}/ })
|
177
|
+
end
|
178
|
+
files.map! {|f|
|
179
|
+
f = f.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
|
180
|
+
((paths if /\A\.\.?(?:\z|\/)/ !~ f) || [nil]).any? do |prefix|
|
181
|
+
if prefix
|
182
|
+
path = f.empty? ? prefix : "#{prefix}/#{f}"
|
183
|
+
else
|
184
|
+
next if f.empty?
|
185
|
+
path = f
|
186
|
+
end
|
187
|
+
if !(match = Dir["#{path}/**/#{@@testfile_prefix}_*.rb"]).empty?
|
188
|
+
if reject
|
189
|
+
match.reject! {|n|
|
190
|
+
n[(prefix.length+1)..-1] if prefix
|
191
|
+
reject_pat =~ n
|
192
|
+
}
|
193
|
+
end
|
194
|
+
break match
|
195
|
+
elsif !reject or reject_pat !~ f and File.exist? path
|
196
|
+
break path
|
197
|
+
end
|
198
|
+
end or
|
199
|
+
raise ArgumentError, "file not found: #{f}"
|
200
|
+
}
|
201
|
+
files.flatten!
|
202
|
+
super(files, options)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
module LoadPathOption # :nodoc: all
|
207
|
+
def setup_options(parser, options)
|
208
|
+
super
|
209
|
+
parser.on '-Idirectory', 'Add library load path' do |dirs|
|
210
|
+
dirs.split(':').each { |d| $LOAD_PATH.unshift d }
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
module GCStressOption # :nodoc: all
|
216
|
+
def setup_options(parser, options)
|
217
|
+
super
|
218
|
+
parser.on '--[no-]gc-stress', 'Set GC.stress as true' do |flag|
|
219
|
+
options[:gc_stress] = flag
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def non_options(files, options)
|
224
|
+
if options.delete(:gc_stress)
|
225
|
+
MiniTest::Unit::TestCase.class_eval do
|
226
|
+
oldrun = instance_method(:run)
|
227
|
+
define_method(:run) do |runner|
|
228
|
+
begin
|
229
|
+
gc_stress, GC.stress = GC.stress, true
|
230
|
+
oldrun.bind(self).call(runner)
|
231
|
+
ensure
|
232
|
+
GC.stress = gc_stress
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
super
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
module RequireFiles # :nodoc: all
|
242
|
+
def non_options(files, options)
|
243
|
+
return false if !super
|
244
|
+
result = false
|
245
|
+
files.each {|f|
|
246
|
+
d = File.dirname(path = File.realpath(f))
|
247
|
+
unless $:.include? d
|
248
|
+
$: << d
|
249
|
+
end
|
250
|
+
begin
|
251
|
+
require path unless options[:parallel]
|
252
|
+
result = true
|
253
|
+
rescue LoadError
|
254
|
+
puts "#{f}: #{$!}"
|
255
|
+
end
|
256
|
+
}
|
257
|
+
result
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
class Runner < MiniTest::Unit # :nodoc: all
|
262
|
+
include Test::Unit::Options
|
263
|
+
include Test::Unit::GlobOption
|
264
|
+
include Test::Unit::LoadPathOption
|
265
|
+
include Test::Unit::GCStressOption
|
266
|
+
include Test::Unit::RunCount
|
267
|
+
|
268
|
+
class Worker
|
269
|
+
def self.launch(ruby,args=[])
|
270
|
+
io = IO.popen([*ruby,
|
271
|
+
"#{File.dirname(__FILE__)}/unit/parallel.rb",
|
272
|
+
*args], "rb+")
|
273
|
+
new(io, io.pid, :waiting)
|
274
|
+
end
|
275
|
+
|
276
|
+
attr_reader :quit_called
|
277
|
+
|
278
|
+
def initialize(io, pid, status)
|
279
|
+
@io = io
|
280
|
+
@pid = pid
|
281
|
+
@status = status
|
282
|
+
@file = nil
|
283
|
+
@real_file = nil
|
284
|
+
@loadpath = []
|
285
|
+
@hooks = {}
|
286
|
+
@quit_called = false
|
287
|
+
end
|
288
|
+
|
289
|
+
def puts(*args)
|
290
|
+
@io.puts(*args)
|
291
|
+
end
|
292
|
+
|
293
|
+
def run(task,type)
|
294
|
+
@file = File.basename(task, ".rb")
|
295
|
+
@real_file = task
|
296
|
+
begin
|
297
|
+
puts "loadpath #{[Marshal.dump($:-@loadpath)].pack("m0")}"
|
298
|
+
@loadpath = $:.dup
|
299
|
+
puts "run #{task} #{type}"
|
300
|
+
@status = :prepare
|
301
|
+
rescue Errno::EPIPE
|
302
|
+
died
|
303
|
+
rescue IOError
|
304
|
+
raise unless ["stream closed","closed stream"].include? $!.message
|
305
|
+
died
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def hook(id,&block)
|
310
|
+
@hooks[id] ||= []
|
311
|
+
@hooks[id] << block
|
312
|
+
self
|
313
|
+
end
|
314
|
+
|
315
|
+
def read
|
316
|
+
res = (@status == :quit) ? @io.read : @io.gets
|
317
|
+
res && res.chomp
|
318
|
+
end
|
319
|
+
|
320
|
+
def close
|
321
|
+
@io.close unless @io.closed?
|
322
|
+
self
|
323
|
+
rescue IOError
|
324
|
+
end
|
325
|
+
|
326
|
+
def quit
|
327
|
+
return if @io.closed?
|
328
|
+
@quit_called = true
|
329
|
+
@io.puts "quit"
|
330
|
+
@io.close
|
331
|
+
end
|
332
|
+
|
333
|
+
def kill
|
334
|
+
Process.kill(:KILL, @pid)
|
335
|
+
rescue Errno::ESRCH
|
336
|
+
end
|
337
|
+
|
338
|
+
def died(*additional)
|
339
|
+
@status = :quit
|
340
|
+
@io.close
|
341
|
+
|
342
|
+
call_hook(:dead,*additional)
|
343
|
+
end
|
344
|
+
|
345
|
+
def to_s
|
346
|
+
if @file
|
347
|
+
"#{@pid}=#{@file}"
|
348
|
+
else
|
349
|
+
"#{@pid}:#{@status.to_s.ljust(7)}"
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
attr_reader :io, :pid
|
354
|
+
attr_accessor :status, :file, :real_file, :loadpath
|
355
|
+
|
356
|
+
private
|
357
|
+
|
358
|
+
def call_hook(id,*additional)
|
359
|
+
@hooks[id] ||= []
|
360
|
+
@hooks[id].each{|hook| hook[self,additional] }
|
361
|
+
self
|
362
|
+
end
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
class << self; undef autorun; end
|
367
|
+
|
368
|
+
@@stop_auto_run = false
|
369
|
+
def self.autorun
|
370
|
+
at_exit {
|
371
|
+
Test::Unit::RunCount.run_once {
|
372
|
+
exit(Test::Unit::Runner.new.run(ARGV) || true)
|
373
|
+
} unless @@stop_auto_run
|
374
|
+
} unless @@installed_at_exit
|
375
|
+
@@installed_at_exit = true
|
376
|
+
end
|
377
|
+
|
378
|
+
def after_worker_down(worker, e=nil, c=false)
|
379
|
+
return unless @options[:parallel]
|
380
|
+
return if @interrupt
|
381
|
+
warn e if e
|
382
|
+
@need_quit = true
|
383
|
+
warn ""
|
384
|
+
warn "Some worker was crashed. It seems ruby interpreter's bug"
|
385
|
+
warn "or, a bug of test/unit/parallel.rb. try again without -j"
|
386
|
+
warn "option."
|
387
|
+
warn ""
|
388
|
+
STDERR.flush
|
389
|
+
exit c
|
390
|
+
end
|
391
|
+
|
392
|
+
def terminal_width
|
393
|
+
unless @terminal_width ||= nil
|
394
|
+
begin
|
395
|
+
require 'io/console'
|
396
|
+
width = $stdout.winsize[1]
|
397
|
+
rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF
|
398
|
+
width = ENV["COLUMNS"].to_i.nonzero? || 80
|
399
|
+
end
|
400
|
+
width -= 1 if /mswin|mingw/ =~ RUBY_PLATFORM
|
401
|
+
@terminal_width = width
|
402
|
+
end
|
403
|
+
@terminal_width
|
404
|
+
end
|
405
|
+
|
406
|
+
def del_status_line
|
407
|
+
@status_line_size ||= 0
|
408
|
+
unless @options[:job_status] == :replace
|
409
|
+
$stdout.puts
|
410
|
+
return
|
411
|
+
end
|
412
|
+
print "\r"+" "*@status_line_size+"\r"
|
413
|
+
$stdout.flush
|
414
|
+
@status_line_size = 0
|
415
|
+
end
|
416
|
+
|
417
|
+
def put_status(line)
|
418
|
+
unless @options[:job_status] == :replace
|
419
|
+
print(line)
|
420
|
+
return
|
421
|
+
end
|
422
|
+
@status_line_size ||= 0
|
423
|
+
del_status_line
|
424
|
+
$stdout.flush
|
425
|
+
line = line[0...terminal_width]
|
426
|
+
print line
|
427
|
+
$stdout.flush
|
428
|
+
@status_line_size = line.size
|
429
|
+
end
|
430
|
+
|
431
|
+
def add_status(line)
|
432
|
+
unless @options[:job_status] == :replace
|
433
|
+
print(line)
|
434
|
+
return
|
435
|
+
end
|
436
|
+
@status_line_size ||= 0
|
437
|
+
line = line[0...(terminal_width-@status_line_size)]
|
438
|
+
print line
|
439
|
+
$stdout.flush
|
440
|
+
@status_line_size += line.size
|
441
|
+
end
|
442
|
+
|
443
|
+
def jobs_status
|
444
|
+
return unless @options[:job_status]
|
445
|
+
puts "" unless @options[:verbose] or @options[:job_status] == :replace
|
446
|
+
status_line = @workers.map(&:to_s).join(" ")
|
447
|
+
update_status(status_line) or (puts; nil)
|
448
|
+
end
|
449
|
+
|
450
|
+
def del_jobs_status
|
451
|
+
return unless @options[:job_status] == :replace && @status_line_size.nonzero?
|
452
|
+
del_status_line
|
453
|
+
end
|
454
|
+
|
455
|
+
def after_worker_quit(worker)
|
456
|
+
return unless @options[:parallel]
|
457
|
+
return if @interrupt
|
458
|
+
@workers.delete(worker)
|
459
|
+
@dead_workers << worker
|
460
|
+
@ios = @workers.map(&:io)
|
461
|
+
end
|
462
|
+
|
463
|
+
def launch_worker
|
464
|
+
begin
|
465
|
+
worker = Worker.launch(@options[:ruby],@args)
|
466
|
+
rescue => e
|
467
|
+
abort "ERROR: Failed to launch job process - #{e.class}: #{e.message}"
|
468
|
+
end
|
469
|
+
worker.hook(:dead) do |w,info|
|
470
|
+
after_worker_quit w
|
471
|
+
after_worker_down w, *info if !info.empty? && !worker.quit_called
|
472
|
+
end
|
473
|
+
@workers << worker
|
474
|
+
@ios << worker.io
|
475
|
+
@workers_hash[worker.io] = worker
|
476
|
+
worker
|
477
|
+
end
|
478
|
+
|
479
|
+
def delete_worker(worker)
|
480
|
+
@workers_hash.delete worker.io
|
481
|
+
@workers.delete worker
|
482
|
+
@ios.delete worker.io
|
483
|
+
end
|
484
|
+
|
485
|
+
def quit_workers
|
486
|
+
return if @workers.empty?
|
487
|
+
@workers.reject! do |worker|
|
488
|
+
begin
|
489
|
+
timeout(1) do
|
490
|
+
worker.quit
|
491
|
+
end
|
492
|
+
rescue Errno::EPIPE
|
493
|
+
rescue Timeout::Error
|
494
|
+
end
|
495
|
+
worker.close
|
496
|
+
end
|
497
|
+
|
498
|
+
return if @workers.empty?
|
499
|
+
begin
|
500
|
+
timeout(0.2 * @workers.size) do
|
501
|
+
Process.waitall
|
502
|
+
end
|
503
|
+
rescue Timeout::Error
|
504
|
+
@workers.each do |worker|
|
505
|
+
worker.kill
|
506
|
+
end
|
507
|
+
@worker.clear
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
def start_watchdog
|
512
|
+
Thread.new do
|
513
|
+
while stat = Process.wait2
|
514
|
+
break if @interrupt # Break when interrupt
|
515
|
+
pid, stat = stat
|
516
|
+
w = (@workers + @dead_workers).find{|x| pid == x.pid }
|
517
|
+
next unless w
|
518
|
+
w = w.dup
|
519
|
+
if w.status != :quit && !w.quit_called?
|
520
|
+
# Worker down
|
521
|
+
w.died(nil, !stat.signaled? && stat.exitstatus)
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
def deal(io, type, result, rep, shutting_down = false)
|
528
|
+
worker = @workers_hash[io]
|
529
|
+
case worker.read
|
530
|
+
when /^okay$/
|
531
|
+
worker.status = :running
|
532
|
+
jobs_status
|
533
|
+
when /^ready(!)?$/
|
534
|
+
bang = $1
|
535
|
+
worker.status = :ready
|
536
|
+
|
537
|
+
return nil unless task = @tasks.shift
|
538
|
+
if @options[:separate] and not bang
|
539
|
+
worker.quit
|
540
|
+
worker = add_worker
|
541
|
+
end
|
542
|
+
worker.run(task, type)
|
543
|
+
@test_count += 1
|
544
|
+
|
545
|
+
jobs_status
|
546
|
+
when /^done (.+?)$/
|
547
|
+
r = Marshal.load($1.unpack("m")[0])
|
548
|
+
result << r[0..1] unless r[0..1] == [nil,nil]
|
549
|
+
rep << {file: worker.real_file, report: r[2], result: r[3], testcase: r[5]}
|
550
|
+
$:.push(*r[4]).uniq!
|
551
|
+
return true
|
552
|
+
when /^p (.+?)$/
|
553
|
+
del_jobs_status
|
554
|
+
print $1.unpack("m")[0]
|
555
|
+
jobs_status if @options[:job_status] == :replace
|
556
|
+
when /^after (.+?)$/
|
557
|
+
@warnings << Marshal.load($1.unpack("m")[0])
|
558
|
+
when /^bye (.+?)$/
|
559
|
+
after_worker_down worker, Marshal.load($1.unpack("m")[0])
|
560
|
+
when /^bye$/, nil
|
561
|
+
if shutting_down || worker.quit_called
|
562
|
+
after_worker_quit worker
|
563
|
+
else
|
564
|
+
after_worker_down worker
|
565
|
+
end
|
566
|
+
end
|
567
|
+
return false
|
568
|
+
end
|
569
|
+
|
570
|
+
def _run_parallel suites, type, result
|
571
|
+
if @options[:parallel] < 1
|
572
|
+
warn "Error: parameter of -j option should be greater than 0."
|
573
|
+
return
|
574
|
+
end
|
575
|
+
|
576
|
+
# Require needed things for parallel running
|
577
|
+
require 'thread'
|
578
|
+
require 'timeout'
|
579
|
+
@tasks = @files.dup # Array of filenames.
|
580
|
+
@need_quit = false
|
581
|
+
@dead_workers = [] # Array of dead workers.
|
582
|
+
@warnings = []
|
583
|
+
@total_tests = @tasks.size.to_s(10)
|
584
|
+
rep = [] # FIXME: more good naming
|
585
|
+
|
586
|
+
@workers = [] # Array of workers.
|
587
|
+
@workers_hash = {} # out-IO => worker
|
588
|
+
@ios = [] # Array of worker IOs
|
589
|
+
begin
|
590
|
+
# Thread: watchdog
|
591
|
+
watchdog = start_watchdog
|
592
|
+
|
593
|
+
@options[:parallel].times {launch_worker}
|
594
|
+
|
595
|
+
while _io = IO.select(@ios)[0]
|
596
|
+
break if _io.any? do |io|
|
597
|
+
@need_quit or
|
598
|
+
(deal(io, type, result, rep).nil? and
|
599
|
+
!@workers.any? {|x| [:running, :prepare].include? x.status})
|
600
|
+
end
|
601
|
+
end
|
602
|
+
rescue Interrupt => ex
|
603
|
+
@interrupt = ex
|
604
|
+
return result
|
605
|
+
ensure
|
606
|
+
watchdog.kill if watchdog
|
607
|
+
if @interrupt
|
608
|
+
@ios.select!{|x| @workers_hash[x].status == :running }
|
609
|
+
while !@ios.empty? && (__io = IO.select(@ios,[],[],10))
|
610
|
+
__io[0].reject! {|io| deal(io, type, result, rep, true)}
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
quit_workers
|
615
|
+
|
616
|
+
unless @interrupt || !@options[:retry] || @need_quit
|
617
|
+
@options[:parallel] = false
|
618
|
+
suites, rep = rep.partition {|r| r[:testcase] && r[:file] && r[:report].any? {|e| !e[2].is_a?(MiniTest::Skip)}}
|
619
|
+
suites.map {|r| r[:file]}.uniq.each {|file| require file}
|
620
|
+
suites.map! {|r| eval("::"+r[:testcase])}
|
621
|
+
del_status_line or puts
|
622
|
+
unless suites.empty?
|
623
|
+
puts "Retrying..."
|
624
|
+
_run_suites(suites, type)
|
625
|
+
end
|
626
|
+
end
|
627
|
+
unless @options[:retry]
|
628
|
+
del_status_line or puts
|
629
|
+
end
|
630
|
+
unless rep.empty?
|
631
|
+
rep.each do |r|
|
632
|
+
r[:report].each do |f|
|
633
|
+
puke(*f) if f
|
634
|
+
end
|
635
|
+
end
|
636
|
+
if @options[:retry]
|
637
|
+
@errors += rep.map{|x| x[:result][0] }.inject(:+)
|
638
|
+
@failures += rep.map{|x| x[:result][1] }.inject(:+)
|
639
|
+
@skips += rep.map{|x| x[:result][2] }.inject(:+)
|
640
|
+
end
|
641
|
+
end
|
642
|
+
unless @warnings.empty?
|
643
|
+
warn ""
|
644
|
+
@warnings.uniq! {|w| w[1].message}
|
645
|
+
@warnings.each do |w|
|
646
|
+
warn "#{w[0]}: #{w[1].message} (#{w[1].class})"
|
647
|
+
end
|
648
|
+
warn ""
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
def _run_suites suites, type
|
654
|
+
_prepare_run(suites, type)
|
655
|
+
@interrupt = nil
|
656
|
+
result = []
|
657
|
+
GC.start
|
658
|
+
if @options[:parallel]
|
659
|
+
_run_parallel suites, type, result
|
660
|
+
else
|
661
|
+
suites.each {|suite|
|
662
|
+
begin
|
663
|
+
result << _run_suite(suite, type)
|
664
|
+
rescue Interrupt => e
|
665
|
+
@interrupt = e
|
666
|
+
break
|
667
|
+
end
|
668
|
+
}
|
669
|
+
end
|
670
|
+
report.reject!{|r| r.start_with? "Skipped:" } if @options[:hide_skip]
|
671
|
+
report.sort_by!{|r| r.start_with?("Skipped:") ? 0 : \
|
672
|
+
(r.start_with?("Failure:") ? 1 : 2) }
|
673
|
+
result
|
674
|
+
end
|
675
|
+
|
676
|
+
alias mini_run_suite _run_suite
|
677
|
+
|
678
|
+
def output
|
679
|
+
(@output ||= nil) || super
|
680
|
+
end
|
681
|
+
|
682
|
+
def _prepare_run(suites, type)
|
683
|
+
options[:job_status] ||= :replace if @tty && !@verbose
|
684
|
+
case options[:color]
|
685
|
+
when :always
|
686
|
+
color = true
|
687
|
+
when :auto, nil
|
688
|
+
color = @options[:job_status] == :replace && /dumb/ !~ ENV["TERM"]
|
689
|
+
else
|
690
|
+
color = false
|
691
|
+
end
|
692
|
+
if color
|
693
|
+
# dircolors-like style
|
694
|
+
colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:]*)/)] : {}
|
695
|
+
@passed_color = "\e[#{colors["pass"] || "32"}m"
|
696
|
+
@failed_color = "\e[#{colors["fail"] || "31"}m"
|
697
|
+
@skipped_color = "\e[#{colors["skip"] || "33"}m"
|
698
|
+
@reset_color = "\e[m"
|
699
|
+
else
|
700
|
+
@passed_color = @failed_color = @skipped_color = @reset_color = ""
|
701
|
+
end
|
702
|
+
if color or @options[:job_status] == :replace
|
703
|
+
@verbose = !options[:parallel]
|
704
|
+
@output = StatusLineOutput.new(self)
|
705
|
+
end
|
706
|
+
if /\A\/(.*)\/\z/ =~ (filter = options[:filter])
|
707
|
+
filter = Regexp.new($1)
|
708
|
+
end
|
709
|
+
type = "#{type}_methods"
|
710
|
+
total = if filter
|
711
|
+
suites.inject(0) {|n, suite| n + suite.send(type).grep(filter).size}
|
712
|
+
else
|
713
|
+
suites.inject(0) {|n, suite| n + suite.send(type).size}
|
714
|
+
end
|
715
|
+
@test_count = 0
|
716
|
+
@total_tests = total.to_s(10)
|
717
|
+
end
|
718
|
+
|
719
|
+
def new_test(s)
|
720
|
+
@test_count += 1
|
721
|
+
update_status(s)
|
722
|
+
end
|
723
|
+
|
724
|
+
def update_status(s)
|
725
|
+
count = @test_count.to_s(10).rjust(@total_tests.size)
|
726
|
+
put_status("#{@passed_color}[#{count}/#{@total_tests}]#{@reset_color} #{s}")
|
727
|
+
end
|
728
|
+
|
729
|
+
def _print(s); $stdout.print(s); end
|
730
|
+
def succeed; del_status_line; end
|
731
|
+
|
732
|
+
def failed(s)
|
733
|
+
sep = "\n"
|
734
|
+
@report_count ||= 0
|
735
|
+
report.each do |msg|
|
736
|
+
if msg.start_with? "Skipped:"
|
737
|
+
if @options[:hide_skip]
|
738
|
+
del_status_line
|
739
|
+
next
|
740
|
+
end
|
741
|
+
color = @skipped_color
|
742
|
+
else
|
743
|
+
color = @failed_color
|
744
|
+
end
|
745
|
+
msg = msg.split(/$/, 2)
|
746
|
+
$stdout.printf("%s%s%3d) %s%s%s\n",
|
747
|
+
sep, color, @report_count += 1,
|
748
|
+
msg[0], @reset_color, msg[1])
|
749
|
+
sep = nil
|
750
|
+
end
|
751
|
+
report.clear
|
752
|
+
end
|
753
|
+
|
754
|
+
# Overriding of MiniTest::Unit#puke
|
755
|
+
def puke klass, meth, e
|
756
|
+
# TODO:
|
757
|
+
# this overriding is for minitest feature that skip messages are
|
758
|
+
# hidden when not verbose (-v), note this is temporally.
|
759
|
+
n = report.size
|
760
|
+
rep = super
|
761
|
+
if MiniTest::Skip === e and /no message given\z/ =~ e.message
|
762
|
+
report.slice!(n..-1)
|
763
|
+
rep = "."
|
764
|
+
end
|
765
|
+
rep
|
766
|
+
end
|
767
|
+
|
768
|
+
def initialize
|
769
|
+
super
|
770
|
+
@tty = $stdout.tty?
|
771
|
+
end
|
772
|
+
|
773
|
+
def status(*args)
|
774
|
+
result = super
|
775
|
+
raise @interrupt if @interrupt
|
776
|
+
result
|
777
|
+
end
|
778
|
+
|
779
|
+
def run(*args)
|
780
|
+
result = super
|
781
|
+
puts "\nruby -v: #{RUBY_DESCRIPTION}"
|
782
|
+
result
|
783
|
+
end
|
784
|
+
end
|
785
|
+
|
786
|
+
class StatusLineOutput < Struct.new(:runner) # :nodoc: all
|
787
|
+
def puts(*a) $stdout.puts(*a) unless a.empty? end
|
788
|
+
def respond_to_missing?(*a) $stdout.respond_to?(*a) end
|
789
|
+
def method_missing(*a, &b) $stdout.__send__(*a, &b) end
|
790
|
+
|
791
|
+
def print(s)
|
792
|
+
case s
|
793
|
+
when /\A(.*\#.*) = \z/
|
794
|
+
runner.new_test($1)
|
795
|
+
when /\A(.* s) = \z/
|
796
|
+
runner.add_status(" = "+$1.chomp)
|
797
|
+
when /\A\.+\z/
|
798
|
+
runner.succeed
|
799
|
+
when /\A[EFS]\z/
|
800
|
+
runner.failed(s)
|
801
|
+
else
|
802
|
+
$stdout.print(s)
|
803
|
+
end
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
class AutoRunner # :nodoc: all
|
808
|
+
class Runner < Test::Unit::Runner
|
809
|
+
include Test::Unit::RequireFiles
|
810
|
+
end
|
811
|
+
|
812
|
+
attr_accessor :to_run, :options
|
813
|
+
|
814
|
+
def initialize(force_standalone = false, default_dir = nil, argv = ARGV)
|
815
|
+
@force_standalone = force_standalone
|
816
|
+
@runner = Runner.new do |files, options|
|
817
|
+
options[:base_directory] ||= default_dir
|
818
|
+
files << default_dir if files.empty? and default_dir
|
819
|
+
@to_run = files
|
820
|
+
yield self if block_given?
|
821
|
+
files
|
822
|
+
end
|
823
|
+
Runner.runner = @runner
|
824
|
+
@options = @runner.option_parser
|
825
|
+
if @force_standalone
|
826
|
+
@options.banner.sub!(/\[options\]/, '\& tests...')
|
827
|
+
end
|
828
|
+
@argv = argv
|
829
|
+
end
|
830
|
+
|
831
|
+
def process_args(*args)
|
832
|
+
@runner.process_args(*args)
|
833
|
+
!@to_run.empty?
|
834
|
+
end
|
835
|
+
|
836
|
+
def run
|
837
|
+
if @force_standalone and not process_args(@argv)
|
838
|
+
abort @options.banner
|
839
|
+
end
|
840
|
+
@runner.run(@argv) || true
|
841
|
+
end
|
842
|
+
|
843
|
+
def self.run(*args)
|
844
|
+
new(*args).run
|
845
|
+
end
|
846
|
+
end
|
847
|
+
|
848
|
+
class ProxyError < StandardError # :nodoc: all
|
849
|
+
def initialize(ex)
|
850
|
+
@message = ex.message
|
851
|
+
@backtrace = ex.backtrace
|
852
|
+
end
|
853
|
+
|
854
|
+
attr_accessor :message, :backtrace
|
855
|
+
end
|
856
|
+
end
|
857
|
+
end
|
858
|
+
|
859
|
+
module MiniTest # :nodoc: all
|
860
|
+
class Unit
|
861
|
+
end
|
862
|
+
end
|
863
|
+
|
864
|
+
class MiniTest::Unit::TestCase # :nodoc: all
|
865
|
+
undef run_test
|
866
|
+
RUN_TEST_TRACE = "#{__FILE__}:#{__LINE__+3}:in `run_test'".freeze
|
867
|
+
def run_test(name)
|
868
|
+
progname, $0 = $0, "#{$0}: #{self.class}##{name}"
|
869
|
+
self.__send__(name)
|
870
|
+
ensure
|
871
|
+
$@.delete(RUN_TEST_TRACE) if $@
|
872
|
+
$0 = progname
|
873
|
+
end
|
874
|
+
end
|
875
|
+
|
876
|
+
Test::Unit::Runner.autorun
|