test-unit-minitest 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](http://img.shields.io/travis/iGEL/test-unit-minitest/master.svg?style=flat)](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
|