rubysl-test-unit 1.0.1 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -2
- data/lib/rubysl/test/unit/unit.rb +863 -267
- data/lib/rubysl/test/unit/version.rb +1 -1
- data/lib/test/unit/assertions.rb +281 -531
- data/lib/test/unit/parallel.rb +184 -0
- data/lib/test/unit/testcase.rb +17 -143
- data/rubysl-test-unit.gemspec +3 -1
- metadata +22 -39
- data/lib/test/unit/assertionfailederror.rb +0 -14
- data/lib/test/unit/autorunner.rb +0 -220
- data/lib/test/unit/collector.rb +0 -43
- data/lib/test/unit/collector/dir.rb +0 -107
- data/lib/test/unit/collector/objectspace.rb +0 -34
- data/lib/test/unit/error.rb +0 -56
- data/lib/test/unit/failure.rb +0 -51
- data/lib/test/unit/testresult.rb +0 -80
- data/lib/test/unit/testsuite.rb +0 -76
- data/lib/test/unit/ui/console/testrunner.rb +0 -127
- data/lib/test/unit/ui/fox/testrunner.rb +0 -268
- data/lib/test/unit/ui/gtk/testrunner.rb +0 -416
- data/lib/test/unit/ui/gtk2/testrunner.rb +0 -465
- data/lib/test/unit/ui/testrunnermediator.rb +0 -68
- data/lib/test/unit/ui/testrunnerutilities.rb +0 -46
- data/lib/test/unit/ui/tk/testrunner.rb +0 -260
- data/lib/test/unit/util/backtracefilter.rb +0 -40
- data/lib/test/unit/util/observable.rb +0 -90
- data/lib/test/unit/util/procwrapper.rb +0 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f409bfb09799e670824d4277954bc9980ef6103
|
4
|
+
data.tar.gz: 9d2da62b2d1e3a513cdb347c7dcfb7c771ba979c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c93bf2d11e1f39b3ba7bfa923d7ba9ead6ef780e7baf5fe50ef624df335b389c7f10e8572b010158aa43289afd280ee1f8377db609237ce75c1fc80c461d1796
|
7
|
+
data.tar.gz: 64075cb13ea6f9914dc6065aa93320c72e49d69857195a715f9b38090a408d6e82eb774455e9037a3ae4ab6f136a97e7fb990f98ef4463f0c1068a6a6a51386c
|
data/.travis.yml
CHANGED
@@ -1,280 +1,876 @@
|
|
1
|
+
require 'minitest/unit'
|
2
|
+
require 'test/unit/assertions'
|
1
3
|
require 'test/unit/testcase'
|
2
|
-
require '
|
4
|
+
require 'optparse'
|
3
5
|
|
4
|
-
|
6
|
+
# See Test::Unit
|
7
|
+
module Test
|
8
|
+
##
|
9
|
+
# Test::Unit is an implementation of the xUnit testing framework for Ruby.
|
5
10
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# == Introduction
|
9
|
-
#
|
10
|
-
# Unit testing is making waves all over the place, largely due to the
|
11
|
-
# fact that it is a core practice of XP. While XP is great, unit testing
|
12
|
-
# has been around for a long time and has always been a good idea. One
|
13
|
-
# of the keys to good unit testing, though, is not just writing tests,
|
14
|
-
# but having tests. What's the difference? Well, if you just _write_ a
|
15
|
-
# test and throw it away, you have no guarantee that something won't
|
16
|
-
# change later which breaks your code. If, on the other hand, you _have_
|
17
|
-
# tests (obviously you have to write them first), and run them as often
|
18
|
-
# as possible, you slowly build up a wall of things that cannot break
|
19
|
-
# without you immediately knowing about it. This is when unit testing
|
20
|
-
# hits its peak usefulness.
|
21
|
-
#
|
22
|
-
# Enter Test::Unit, a framework for unit testing in Ruby, helping you to
|
23
|
-
# design, debug and evaluate your code by making it easy to write and
|
24
|
-
# have tests for it.
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# == Notes
|
28
|
-
#
|
29
|
-
# Test::Unit has grown out of and superceded Lapidary.
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# == Feedback
|
33
|
-
#
|
34
|
-
# I like (and do my best to practice) XP, so I value early releases,
|
35
|
-
# user feedback, and clean, simple, expressive code. There is always
|
36
|
-
# room for improvement in everything I do, and Test::Unit is no
|
37
|
-
# exception. Please, let me know what you think of Test::Unit as it
|
38
|
-
# stands, and what you'd like to see expanded/changed/improved/etc. If
|
39
|
-
# you find a bug, let me know ASAP; one good way to let me know what the
|
40
|
-
# bug is is to submit a new test that catches it :-) Also, I'd love to
|
41
|
-
# hear about any successes you have with Test::Unit, and any
|
42
|
-
# documentation you might add will be greatly appreciated. My contact
|
43
|
-
# info is below.
|
44
|
-
#
|
45
|
-
#
|
46
|
-
# == Contact Information
|
47
|
-
#
|
48
|
-
# A lot of discussion happens about Ruby in general on the ruby-talk
|
49
|
-
# mailing list (http://www.ruby-lang.org/en/ml.html), and you can ask
|
50
|
-
# any questions you might have there. I monitor the list, as do many
|
51
|
-
# other helpful Rubyists, and you're sure to get a quick answer. Of
|
52
|
-
# course, you're also welcome to email me (Nathaniel Talbott) directly
|
53
|
-
# at mailto:testunit@talbott.ws, and I'll do my best to help you out.
|
54
|
-
#
|
55
|
-
#
|
56
|
-
# == Credits
|
57
|
-
#
|
58
|
-
# I'd like to thank...
|
59
|
-
#
|
60
|
-
# Matz, for a great language!
|
61
|
-
#
|
62
|
-
# Masaki Suketa, for his work on RubyUnit, which filled a vital need in
|
63
|
-
# the Ruby world for a very long time. I'm also grateful for his help in
|
64
|
-
# polishing Test::Unit and getting the RubyUnit compatibility layer
|
65
|
-
# right. His graciousness in allowing Test::Unit to supercede RubyUnit
|
66
|
-
# continues to be a challenge to me to be more willing to defer my own
|
67
|
-
# rights.
|
68
|
-
#
|
69
|
-
# Ken McKinlay, for his interest and work on unit testing, and for his
|
70
|
-
# willingness to dialog about it. He was also a great help in pointing
|
71
|
-
# out some of the holes in the RubyUnit compatibility layer.
|
72
|
-
#
|
73
|
-
# Dave Thomas, for the original idea that led to the extremely simple
|
74
|
-
# "require 'test/unit'", plus his code to improve it even more by
|
75
|
-
# allowing the selection of tests from the command-line. Also, without
|
76
|
-
# RDoc, the documentation for Test::Unit would stink a lot more than it
|
77
|
-
# does now.
|
78
|
-
#
|
79
|
-
# Everyone who's helped out with bug reports, feature ideas,
|
80
|
-
# encouragement to continue, etc. It's a real privilege to be a part of
|
81
|
-
# the Ruby community.
|
82
|
-
#
|
83
|
-
# The guys at RoleModel Software, for putting up with me repeating, "But
|
84
|
-
# this would be so much easier in Ruby!" whenever we're coding in Java.
|
85
|
-
#
|
86
|
-
# My Creator, for giving me life, and giving it more abundantly.
|
87
|
-
#
|
88
|
-
#
|
89
|
-
# == License
|
90
|
-
#
|
91
|
-
# Test::Unit is copyright (c) 2000-2003 Nathaniel Talbott. It is free
|
92
|
-
# software, and is distributed under the Ruby license. See the COPYING
|
93
|
-
# file in the standard Ruby distribution for details.
|
94
|
-
#
|
95
|
-
#
|
96
|
-
# == Warranty
|
97
|
-
#
|
98
|
-
# This software is provided "as is" and without any express or
|
99
|
-
# implied warranties, including, without limitation, the implied
|
100
|
-
# warranties of merchantibility and fitness for a particular
|
101
|
-
# purpose.
|
102
|
-
#
|
103
|
-
#
|
104
|
-
# == Author
|
105
|
-
#
|
106
|
-
# Nathaniel Talbott.
|
107
|
-
# Copyright (c) 2000-2003, Nathaniel Talbott
|
11
|
+
# If you are writing new test code, please use MiniTest instead of Test::Unit.
|
108
12
|
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
# = Usage
|
112
|
-
#
|
113
|
-
# The general idea behind unit testing is that you write a _test_
|
114
|
-
# _method_ that makes certain _assertions_ about your code, working
|
115
|
-
# against a _test_ _fixture_. A bunch of these _test_ _methods_ are
|
116
|
-
# bundled up into a _test_ _suite_ and can be run any time the
|
117
|
-
# developer wants. The results of a run are gathered in a _test_
|
118
|
-
# _result_ and displayed to the user through some UI. So, lets break
|
119
|
-
# this down and see how Test::Unit provides each of these necessary
|
120
|
-
# pieces.
|
121
|
-
#
|
122
|
-
#
|
123
|
-
# == Assertions
|
124
|
-
#
|
125
|
-
# These are the heart of the framework. Think of an assertion as a
|
126
|
-
# statement of expected outcome, i.e. "I assert that x should be equal
|
127
|
-
# to y". If, when the assertion is executed, it turns out to be
|
128
|
-
# correct, nothing happens, and life is good. If, on the other hand,
|
129
|
-
# your assertion turns out to be false, an error is propagated with
|
130
|
-
# pertinent information so that you can go back and make your
|
131
|
-
# assertion succeed, and, once again, life is good. For an explanation
|
132
|
-
# of the current assertions, see Test::Unit::Assertions.
|
133
|
-
#
|
134
|
-
#
|
135
|
-
# == Test Method & Test Fixture
|
136
|
-
#
|
137
|
-
# Obviously, these assertions have to be called within a context that
|
138
|
-
# knows about them and can do something meaningful with their
|
139
|
-
# pass/fail value. Also, it's handy to collect a bunch of related
|
140
|
-
# tests, each test represented by a method, into a common test class
|
141
|
-
# that knows how to run them. The tests will be in a separate class
|
142
|
-
# from the code they're testing for a couple of reasons. First of all,
|
143
|
-
# it allows your code to stay uncluttered with test code, making it
|
144
|
-
# easier to maintain. Second, it allows the tests to be stripped out
|
145
|
-
# for deployment, since they're really there for you, the developer,
|
146
|
-
# and your users don't need them. Third, and most importantly, it
|
147
|
-
# allows you to set up a common test fixture for your tests to run
|
148
|
-
# against.
|
149
|
-
#
|
150
|
-
# What's a test fixture? Well, tests do not live in a vacuum; rather,
|
151
|
-
# they're run against the code they are testing. Often, a collection
|
152
|
-
# of tests will run against a common set of data, also called a
|
153
|
-
# fixture. If they're all bundled into the same test class, they can
|
154
|
-
# all share the setting up and tearing down of that data, eliminating
|
155
|
-
# unnecessary duplication and making it much easier to add related
|
156
|
-
# tests.
|
157
|
-
#
|
158
|
-
# Test::Unit::TestCase wraps up a collection of test methods together
|
159
|
-
# and allows you to easily set up and tear down the same test fixture
|
160
|
-
# for each test. This is done by overriding #setup and/or #teardown,
|
161
|
-
# which will be called before and after each test method that is
|
162
|
-
# run. The TestCase also knows how to collect the results of your
|
163
|
-
# assertions into a Test::Unit::TestResult, which can then be reported
|
164
|
-
# back to you... but I'm getting ahead of myself. To write a test,
|
165
|
-
# follow these steps:
|
166
|
-
#
|
167
|
-
# * Make sure Test::Unit is in your library path.
|
168
|
-
# * require 'test/unit' in your test script.
|
169
|
-
# * Create a class that subclasses Test::Unit::TestCase.
|
170
|
-
# * Add a method that begins with "test" to your class.
|
171
|
-
# * Make assertions in your test method.
|
172
|
-
# * Optionally define #setup and/or #teardown to set up and/or tear
|
173
|
-
# down your common test fixture.
|
174
|
-
# * You can now run your test as you would any other Ruby
|
175
|
-
# script... try it and see!
|
176
|
-
#
|
177
|
-
# A really simple test might look like this (#setup and #teardown are
|
178
|
-
# commented out to indicate that they are completely optional):
|
179
|
-
#
|
180
|
-
# require 'test/unit'
|
181
|
-
#
|
182
|
-
# class TC_MyTest < Test::Unit::TestCase
|
183
|
-
# # def setup
|
184
|
-
# # end
|
185
|
-
#
|
186
|
-
# # def teardown
|
187
|
-
# # end
|
188
|
-
#
|
189
|
-
# def test_fail
|
190
|
-
# assert(false, 'Assertion was false.')
|
191
|
-
# end
|
192
|
-
# end
|
193
|
-
#
|
194
|
-
#
|
195
|
-
# == Test Runners
|
196
|
-
#
|
197
|
-
# So, now you have this great test class, but you still need a way to
|
198
|
-
# run it and view any failures that occur during the run. This is
|
199
|
-
# where Test::Unit::UI::Console::TestRunner (and others, such as
|
200
|
-
# Test::Unit::UI::GTK::TestRunner) comes into play. The console test
|
201
|
-
# runner is automatically invoked for you if you require 'test/unit'
|
202
|
-
# and simply run the file. To use another runner, or to manually
|
203
|
-
# invoke a runner, simply call its run class method and pass in an
|
204
|
-
# object that responds to the suite message with a
|
205
|
-
# Test::Unit::TestSuite. This can be as simple as passing in your
|
206
|
-
# TestCase class (which has a class suite method). It might look
|
207
|
-
# something like this:
|
208
|
-
#
|
209
|
-
# require 'test/unit/ui/console/testrunner'
|
210
|
-
# Test::Unit::UI::Console::TestRunner.run(TC_MyTest)
|
211
|
-
#
|
212
|
-
#
|
213
|
-
# == Test Suite
|
214
|
-
#
|
215
|
-
# As more and more unit tests accumulate for a given project, it
|
216
|
-
# becomes a real drag running them one at a time, and it also
|
217
|
-
# introduces the potential to overlook a failing test because you
|
218
|
-
# forget to run it. Suddenly it becomes very handy that the
|
219
|
-
# TestRunners can take any object that returns a Test::Unit::TestSuite
|
220
|
-
# in response to a suite method. The TestSuite can, in turn, contain
|
221
|
-
# other TestSuites or individual tests (typically created by a
|
222
|
-
# TestCase). In other words, you can easily wrap up a group of
|
223
|
-
# TestCases and TestSuites like this:
|
224
|
-
#
|
225
|
-
# require 'test/unit/testsuite'
|
226
|
-
# require 'tc_myfirsttests'
|
227
|
-
# require 'tc_moretestsbyme'
|
228
|
-
# require 'ts_anothersetoftests'
|
229
|
-
#
|
230
|
-
# class TS_MyTests
|
231
|
-
# def self.suite
|
232
|
-
# suite = Test::Unit::TestSuite.new
|
233
|
-
# suite << TC_MyFirstTests.suite
|
234
|
-
# suite << TC_MoreTestsByMe.suite
|
235
|
-
# suite << TS_AnotherSetOfTests.suite
|
236
|
-
# return suite
|
237
|
-
# end
|
238
|
-
# end
|
239
|
-
# Test::Unit::UI::Console::TestRunner.run(TS_MyTests)
|
240
|
-
#
|
241
|
-
# Now, this is a bit cumbersome, so Test::Unit does a little bit more
|
242
|
-
# for you, by wrapping these up automatically when you require
|
243
|
-
# 'test/unit'. What does this mean? It means you could write the above
|
244
|
-
# test case like this instead:
|
245
|
-
#
|
246
|
-
# require 'test/unit'
|
247
|
-
# require 'tc_myfirsttests'
|
248
|
-
# require 'tc_moretestsbyme'
|
249
|
-
# require 'ts_anothersetoftests'
|
250
|
-
#
|
251
|
-
# Test::Unit is smart enough to find all the test cases existing in
|
252
|
-
# the ObjectSpace and wrap them up into a suite for you. It then runs
|
253
|
-
# the dynamic suite using the console TestRunner.
|
254
|
-
#
|
255
|
-
#
|
256
|
-
# == Questions?
|
257
|
-
#
|
258
|
-
# I'd really like to get feedback from all levels of Ruby
|
259
|
-
# practitioners about typos, grammatical errors, unclear statements,
|
260
|
-
# missing points, etc., in this document (or any other).
|
261
|
-
#
|
262
|
-
|
13
|
+
# Test::Unit has been left in the standard library to support legacy test
|
14
|
+
# suites.
|
263
15
|
module Unit
|
264
|
-
|
265
|
-
|
266
|
-
|
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
|
267
784
|
end
|
268
785
|
|
269
|
-
|
270
|
-
|
271
|
-
|
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
|
272
855
|
end
|
273
856
|
end
|
274
857
|
end
|
275
858
|
|
276
|
-
|
277
|
-
|
278
|
-
Kernel.exit Test::Unit::AutoRunner.run
|
859
|
+
module MiniTest # :nodoc: all
|
860
|
+
class Unit
|
279
861
|
end
|
280
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
|