1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

* test/runner.rb: remove dependency test-unit and minitest

from stdlib when running with test-all.
  [Feature #9711][ruby-core:61890]
* test/testunit/*.rb: ditto.
* test/lib: ditto.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@45970 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
hsbt 2014-05-17 06:26:51 +00:00
parent c466dcc05b
commit f8c6a5dc02
23 changed files with 4899 additions and 4 deletions

View file

@ -1,3 +1,11 @@
Sat May 17 15:06:40 2014 SHIBATA Hiroshi <shibata.hiroshi@gmail.com>
* test/runner.rb: remove dependency test-unit and minitest
from stdlib when running with test-all.
[Feature #9711][ruby-core:61890]
* test/testunit/*.rb: ditto.
* test/lib: ditto.
Sat May 17 11:02:49 2014 Nobuyoshi Nakada <nobu@ruby-lang.org>
* dir.c (glob_helper): try match PLAIN as well as ALPHA, which are

View file

@ -0,0 +1,2 @@
# Ignore README.txt, it is included in the minitest documentation.
*.rb

View file

@ -0,0 +1,457 @@
= minitest/{unit,spec,mock,benchmark}
home :: https://github.com/seattlerb/minitest
rdoc :: http://docs.seattlerb.org/minitest
vim :: https://github.com/sunaku/vim-ruby-minitest
== DESCRIPTION:
minitest provides a complete suite of testing facilities supporting
TDD, BDD, mocking, and benchmarking.
"I had a class with Jim Weirich on testing last week and we were
allowed to choose our testing frameworks. Kirk Haines and I were
paired up and we cracked open the code for a few test
frameworks...
I MUST say that minitest is *very* readable / understandable
compared to the 'other two' options we looked at. Nicely done and
thank you for helping us keep our mental sanity."
-- Wayne E. Seguin
minitest/unit is a small and incredibly fast unit testing framework.
It provides a rich set of assertions to make your tests clean and
readable.
minitest/spec is a functionally complete spec engine. It hooks onto
minitest/unit and seamlessly bridges test assertions over to spec
expectations.
minitest/benchmark is an awesome way to assert the performance of your
algorithms in a repeatable manner. Now you can assert that your newb
co-worker doesn't replace your linear algorithm with an exponential
one!
minitest/mock by Steven Baker, is a beautifully tiny mock (and stub)
object framework.
minitest/pride shows pride in testing and adds coloring to your test
output. I guess it is an example of how to write IO pipes too. :P
minitest/unit is meant to have a clean implementation for language
implementors that need a minimal set of methods to bootstrap a working
test suite. For example, there is no magic involved for test-case
discovery.
"Again, I can't praise enough the idea of a testing/specing
framework that I can actually read in full in one sitting!"
-- Piotr Szotkowski
Comparing to rspec:
rspec is a testing DSL. minitest is ruby.
-- Adam Hawkins, "Bow Before MiniTest"
minitest doesn't reinvent anything that ruby already provides, like:
classes, modules, inheritance, methods. This means you only have to
learn ruby to use minitest and all of your regular OO practices like
extract-method refactorings still apply.
== FEATURES/PROBLEMS:
* minitest/autorun - the easy and explicit way to run all your tests.
* minitest/unit - a very fast, simple, and clean test system.
* minitest/spec - a very fast, simple, and clean spec system.
* minitest/mock - a simple and clean mock/stub system.
* minitest/benchmark - an awesome way to assert your algorithm's performance.
* minitest/pride - show your pride in testing!
* Incredibly small and fast runner, but no bells and whistles.
== RATIONALE:
See design_rationale.rb to see how specs and tests work in minitest.
== SYNOPSIS:
Given that you'd like to test the following class:
class Meme
def i_can_has_cheezburger?
"OHAI!"
end
def will_it_blend?
"YES!"
end
end
=== Unit tests
require 'minitest/autorun'
class TestMeme < MiniTest::Unit::TestCase
def setup
@meme = Meme.new
end
def test_that_kitty_can_eat
assert_equal "OHAI!", @meme.i_can_has_cheezburger?
end
def test_that_it_will_not_blend
refute_match /^no/i, @meme.will_it_blend?
end
def test_that_will_be_skipped
skip "test this later"
end
end
=== Specs
require 'minitest/autorun'
describe Meme do
before do
@meme = Meme.new
end
describe "when asked about cheeseburgers" do
it "must respond positively" do
@meme.i_can_has_cheezburger?.must_equal "OHAI!"
end
end
describe "when asked about blending possibilities" do
it "won't say no" do
@meme.will_it_blend?.wont_match /^no/i
end
end
end
For matchers support check out:
https://github.com/zenspider/minitest-matchers
=== Benchmarks
Add benchmarks to your regular unit tests. If the unit tests fail, the
benchmarks won't run.
# optionally run benchmarks, good for CI-only work!
require 'minitest/benchmark' if ENV["BENCH"]
class TestMeme < MiniTest::Unit::TestCase
# Override self.bench_range or default range is [1, 10, 100, 1_000, 10_000]
def bench_my_algorithm
assert_performance_linear 0.9999 do |n| # n is a range value
@obj.my_algorithm(n)
end
end
end
Or add them to your specs. If you make benchmarks optional, you'll
need to wrap your benchmarks in a conditional since the methods won't
be defined.
describe Meme do
if ENV["BENCH"] then
bench_performance_linear "my_algorithm", 0.9999 do |n|
100.times do
@obj.my_algorithm(n)
end
end
end
end
outputs something like:
# Running benchmarks:
TestBlah 100 1000 10000
bench_my_algorithm 0.006167 0.079279 0.786993
bench_other_algorithm 0.061679 0.792797 7.869932
Output is tab-delimited to make it easy to paste into a spreadsheet.
=== Mocks
class MemeAsker
def initialize(meme)
@meme = meme
end
def ask(question)
method = question.tr(" ","_") + "?"
@meme.__send__(method)
end
end
require 'minitest/autorun'
describe MemeAsker do
before do
@meme = MiniTest::Mock.new
@meme_asker = MemeAsker.new @meme
end
describe "#ask" do
describe "when passed an unpunctuated question" do
it "should invoke the appropriate predicate method on the meme" do
@meme.expect :will_it_blend?, :return_value
@meme_asker.ask "will it blend"
@meme.verify
end
end
end
end
=== Stubs
def test_stale_eh
obj_under_test = Something.new
refute obj_under_test.stale?
Time.stub :now, Time.at(0) do # stub goes away once the block is done
assert obj_under_test.stale?
end
end
A note on stubbing: In order to stub a method, the method must
actually exist prior to stubbing. Use a singleton method to create a
new non-existing method:
def obj_under_test.fake_method
...
end
=== Customizable Test Runner Types:
MiniTest::Unit.runner=(runner) provides an easy way of creating custom
test runners for specialized needs. Justin Weiss provides the
following real-world example to create an alternative to regular
fixture loading:
class MiniTestWithHooks::Unit < MiniTest::Unit
def before_suites
end
def after_suites
end
def _run_suites(suites, type)
begin
before_suites
super(suites, type)
ensure
after_suites
end
end
def _run_suite(suite, type)
begin
suite.before_suite
super(suite, type)
ensure
suite.after_suite
end
end
end
module MiniTestWithTransactions
class Unit < MiniTestWithHooks::Unit
include TestSetupHelper
def before_suites
super
setup_nested_transactions
# load any data we want available for all tests
end
def after_suites
teardown_nested_transactions
super
end
end
end
MiniTest::Unit.runner = MiniTestWithTransactions::Unit.new
== FAQ
=== How to test SimpleDelegates?
The following implementation and test:
class Worker < SimpleDelegator
def work
end
end
describe Worker do
before do
@worker = Worker.new(Object.new)
end
it "must respond to work" do
@worker.must_respond_to :work
end
end
outputs a failure:
1) Failure:
Worker#test_0001_must respond to work [bug11.rb:16]:
Expected #<Object:0x007f9e7184f0a0> (Object) to respond to #work.
Worker is a SimpleDelegate which in 1.9+ is a subclass of BasicObject.
Expectations are put on Object (one level down) so the Worker
(SimpleDelegate) hits `method_missing` and delegates down to the
`Object.new` instance. That object doesn't respond to work so the test
fails.
You can bypass `SimpleDelegate#method_missing` by extending the worker
with `MiniTest::Expectations`. You can either do that in your setup at
the instance level, like:
before do
@worker = Worker.new(Object.new)
@worker.extend MiniTest::Expectations
end
or you can extend the Worker class (within the test file!), like:
class Worker
include ::MiniTest::Expectations
end
== Known Extensions:
capybara_minitest_spec :: Bridge between Capybara RSpec matchers and MiniTest::Spec expectations (e.g. page.must_have_content('Title')).
minispec-metadata :: Metadata for describe/it blocks
(e.g. `it 'requires JS driver', js: true do`)
minitest-ansi :: Colorize minitest output with ANSI colors.
minitest-around :: Around block for minitest. An alternative to setup/teardown dance.
minitest-capistrano :: Assertions and expectations for testing Capistrano recipes
minitest-capybara :: Capybara matchers support for minitest unit and spec
minitest-chef-handler :: Run Minitest suites as Chef report handlers
minitest-ci :: CI reporter plugin for MiniTest.
minitest-colorize :: Colorize MiniTest output and show failing tests instantly.
minitest-context :: Defines contexts for code reuse in MiniTest
specs that share common expectations.
minitest-debugger :: Wraps assert so failed assertions drop into
the ruby debugger.
minitest-display :: Patches MiniTest to allow for an easily configurable output.
minitest-emoji :: Print out emoji for your test passes, fails, and skips.
minitest-english :: Semantically symmetric aliases for assertions and expectations.
minitest-excludes :: Clean API for excluding certain tests you
don't want to run under certain conditions.
minitest-firemock :: Makes your MiniTest mocks more resilient.
minitest-great_expectations :: Generally useful additions to minitest's assertions and expectations
minitest-growl :: Test notifier for minitest via growl.
minitest-implicit-subject :: Implicit declaration of the test subject.
minitest-instrument :: Instrument ActiveSupport::Notifications when
test method is executed
minitest-instrument-db :: Store information about speed of test
execution provided by minitest-instrument in database
minitest-libnotify :: Test notifier for minitest via libnotify.
minitest-macruby :: Provides extensions to minitest for macruby UI testing.
minitest-matchers :: Adds support for RSpec-style matchers to minitest.
minitest-metadata :: Annotate tests with metadata (key-value).
minitest-mongoid :: Mongoid assertion matchers for MiniTest
minitest-must_not :: Provides must_not as an alias for wont in MiniTest
minitest-nc :: Test notifier for minitest via Mountain Lion's Notification Center
minitest-predicates :: Adds support for .predicate? methods
minitest-rails :: MiniTest integration for Rails 3.x
minitest-rails-capybara :: Capybara integration for MiniTest::Rails
minitest-reporters :: Create customizable MiniTest output formats
minitest-should_syntax :: RSpec-style +x.should == y+ assertions for MiniTest
minitest-shouldify :: Adding all manner of shoulds to MiniTest (bad idea)
minitest-spec-context :: Provides rspec-ish context method to MiniTest::Spec
minitest-spec-magic :: Minitest::Spec extensions for Rails and beyond
minitest-spec-rails :: Drop in MiniTest::Spec superclass for ActiveSupport::TestCase.
minitest-stub-const :: Stub constants for the duration of a block
minitest-tags :: add tags for minitest
minitest-wscolor :: Yet another test colorizer.
minitest_owrapper :: Get tests results as a TestResult object.
minitest_should :: Shoulda style syntax for minitest test::unit.
minitest_tu_shim :: minitest_tu_shim bridges between test/unit and minitest.
mongoid-minitest :: MiniTest matchers for Mongoid.
pry-rescue :: A pry plugin w/ minitest support. See pry-rescue/minitest.rb.
== Unknown Extensions:
Authors... Please send me a pull request with a description of your minitest extension.
* assay-minitest
* detroit-minitest
* em-minitest-spec
* flexmock-minitest
* guard-minitest
* guard-minitest-decisiv
* minitest-activemodel
* minitest-ar-assertions
* minitest-capybara-unit
* minitest-colorer
* minitest-deluxe
* minitest-extra-assertions
* minitest-rails-shoulda
* minitest-spec
* minitest-spec-should
* minitest-sugar
* minitest_should
* mongoid-minitest
* spork-minitest
== REQUIREMENTS:
* Ruby 1.8, maybe even 1.6 or lower. No magic is involved.
== INSTALL:
sudo gem install minitest
On 1.9, you already have it. To get newer candy you can still install
the gem, but you'll need to activate the gem explicitly to use it:
require 'rubygems'
gem 'minitest' # ensures you're using the gem, and not the built in MT
require 'minitest/autorun'
# ... usual testing stuffs ...
DO NOTE: There is a serious problem with the way that ruby 1.9/2.0
packages their own gems. They install a gem specification file, but
don't install the gem contents in the gem path. This messes up
Gem.find_files and many other things (gem which, gem contents, etc).
Just install minitest as a gem for real and you'll be happier.
== LICENSE:
(The MIT License)
Copyright (c) Ryan Davis, seattle.rb
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,19 @@
# encoding: utf-8
######################################################################
# This file is imported from the minitest project.
# DO NOT make modifications in this repo. They _will_ be reverted!
# File a patch instead and assign it to Ryan Davis.
######################################################################
begin
require 'rubygems'
gem 'minitest'
rescue Gem::LoadError
# do nothing
end
require 'minitest/unit'
require 'minitest/spec'
require 'minitest/mock'
MiniTest::Unit.autorun

View file

@ -0,0 +1,423 @@
# encoding: utf-8
######################################################################
# This file is imported from the minitest project.
# DO NOT make modifications in this repo. They _will_ be reverted!
# File a patch instead and assign it to Ryan Davis.
######################################################################
require 'minitest/unit'
require 'minitest/spec'
class MiniTest::Unit # :nodoc:
def run_benchmarks # :nodoc:
_run_anything :benchmark
end
def benchmark_suite_header suite # :nodoc:
"\n#{suite}\t#{suite.bench_range.join("\t")}"
end
class TestCase
##
# Returns a set of ranges stepped exponentially from +min+ to
# +max+ by powers of +base+. Eg:
#
# bench_exp(2, 16, 2) # => [2, 4, 8, 16]
def self.bench_exp min, max, base = 10
min = (Math.log10(min) / Math.log10(base)).to_i
max = (Math.log10(max) / Math.log10(base)).to_i
(min..max).map { |m| base ** m }.to_a
end
##
# Returns a set of ranges stepped linearly from +min+ to +max+ by
# +step+. Eg:
#
# bench_linear(20, 40, 10) # => [20, 30, 40]
def self.bench_linear min, max, step = 10
(min..max).step(step).to_a
rescue LocalJumpError # 1.8.6
r = []; (min..max).step(step) { |n| r << n }; r
end
##
# Returns the benchmark methods (methods that start with bench_)
# for that class.
def self.benchmark_methods # :nodoc:
public_instance_methods(true).grep(/^bench_/).map { |m| m.to_s }.sort
end
##
# Returns all test suites that have benchmark methods.
def self.benchmark_suites
TestCase.test_suites.reject { |s| s.benchmark_methods.empty? }
end
##
# Specifies the ranges used for benchmarking for that class.
# Defaults to exponential growth from 1 to 10k by powers of 10.
# Override if you need different ranges for your benchmarks.
#
# See also: ::bench_exp and ::bench_linear.
def self.bench_range
bench_exp 1, 10_000
end
##
# Runs the given +work+, gathering the times of each run. Range
# and times are then passed to a given +validation+ proc. Outputs
# the benchmark name and times in tab-separated format, making it
# easy to paste into a spreadsheet for graphing or further
# analysis.
#
# Ranges are specified by ::bench_range.
#
# Eg:
#
# def bench_algorithm
# validation = proc { |x, y| ... }
# assert_performance validation do |n|
# @obj.algorithm(n)
# end
# end
def assert_performance validation, &work
range = self.class.bench_range
io.print "#{__name__}"
times = []
range.each do |x|
GC.start
t0 = Time.now
instance_exec(x, &work)
t = Time.now - t0
io.print "\t%9.6f" % t
times << t
end
io.puts
validation[range, times]
end
##
# Runs the given +work+ and asserts that the times gathered fit to
# match a constant rate (eg, linear slope == 0) within a given
# +threshold+. Note: because we're testing for a slope of 0, R^2
# is not a good determining factor for the fit, so the threshold
# is applied against the slope itself. As such, you probably want
# to tighten it from the default.
#
# See http://www.graphpad.com/curvefit/goodness_of_fit.htm for
# more details.
#
# Fit is calculated by #fit_linear.
#
# Ranges are specified by ::bench_range.
#
# Eg:
#
# def bench_algorithm
# assert_performance_constant 0.9999 do |n|
# @obj.algorithm(n)
# end
# end
def assert_performance_constant threshold = 0.99, &work
validation = proc do |range, times|
a, b, rr = fit_linear range, times
assert_in_delta 0, b, 1 - threshold
[a, b, rr]
end
assert_performance validation, &work
end
##
# Runs the given +work+ and asserts that the times gathered fit to
# match a exponential curve within a given error +threshold+.
#
# Fit is calculated by #fit_exponential.
#
# Ranges are specified by ::bench_range.
#
# Eg:
#
# def bench_algorithm
# assert_performance_exponential 0.9999 do |n|
# @obj.algorithm(n)
# end
# end
def assert_performance_exponential threshold = 0.99, &work
assert_performance validation_for_fit(:exponential, threshold), &work
end
##
# Runs the given +work+ and asserts that the times gathered fit to
# match a logarithmic curve within a given error +threshold+.
#
# Fit is calculated by #fit_logarithmic.
#
# Ranges are specified by ::bench_range.
#
# Eg:
#
# def bench_algorithm
# assert_performance_logarithmic 0.9999 do |n|
# @obj.algorithm(n)
# end
# end
def assert_performance_logarithmic threshold = 0.99, &work
assert_performance validation_for_fit(:logarithmic, threshold), &work
end
##
# Runs the given +work+ and asserts that the times gathered fit to
# match a straight line within a given error +threshold+.
#
# Fit is calculated by #fit_linear.
#
# Ranges are specified by ::bench_range.
#
# Eg:
#
# def bench_algorithm
# assert_performance_linear 0.9999 do |n|
# @obj.algorithm(n)
# end
# end
def assert_performance_linear threshold = 0.99, &work
assert_performance validation_for_fit(:linear, threshold), &work
end
##
# Runs the given +work+ and asserts that the times gathered curve
# fit to match a power curve within a given error +threshold+.
#
# Fit is calculated by #fit_power.
#
# Ranges are specified by ::bench_range.
#
# Eg:
#
# def bench_algorithm
# assert_performance_power 0.9999 do |x|
# @obj.algorithm
# end
# end
def assert_performance_power threshold = 0.99, &work
assert_performance validation_for_fit(:power, threshold), &work
end
##
# Takes an array of x/y pairs and calculates the general R^2 value.
#
# See: http://en.wikipedia.org/wiki/Coefficient_of_determination
def fit_error xys
y_bar = sigma(xys) { |x, y| y } / xys.size.to_f
ss_tot = sigma(xys) { |x, y| (y - y_bar) ** 2 }
ss_err = sigma(xys) { |x, y| (yield(x) - y) ** 2 }
1 - (ss_err / ss_tot)
end
##
# To fit a functional form: y = ae^(bx).
#
# Takes x and y values and returns [a, b, r^2].
#
# See: http://mathworld.wolfram.com/LeastSquaresFittingExponential.html
def fit_exponential xs, ys
n = xs.size
xys = xs.zip(ys)
sxlny = sigma(xys) { |x,y| x * Math.log(y) }
slny = sigma(xys) { |x,y| Math.log(y) }
sx2 = sigma(xys) { |x,y| x * x }
sx = sigma xs
c = n * sx2 - sx ** 2
a = (slny * sx2 - sx * sxlny) / c
b = ( n * sxlny - sx * slny ) / c
return Math.exp(a), b, fit_error(xys) { |x| Math.exp(a + b * x) }
end
##
# To fit a functional form: y = a + b*ln(x).
#
# Takes x and y values and returns [a, b, r^2].
#
# See: http://mathworld.wolfram.com/LeastSquaresFittingLogarithmic.html
def fit_logarithmic xs, ys
n = xs.size
xys = xs.zip(ys)
slnx2 = sigma(xys) { |x,y| Math.log(x) ** 2 }
slnx = sigma(xys) { |x,y| Math.log(x) }
sylnx = sigma(xys) { |x,y| y * Math.log(x) }
sy = sigma(xys) { |x,y| y }
c = n * slnx2 - slnx ** 2
b = ( n * sylnx - sy * slnx ) / c
a = (sy - b * slnx) / n
return a, b, fit_error(xys) { |x| a + b * Math.log(x) }
end
##
# Fits the functional form: a + bx.
#
# Takes x and y values and returns [a, b, r^2].
#
# See: http://mathworld.wolfram.com/LeastSquaresFitting.html
def fit_linear xs, ys
n = xs.size
xys = xs.zip(ys)
sx = sigma xs
sy = sigma ys
sx2 = sigma(xs) { |x| x ** 2 }
sxy = sigma(xys) { |x,y| x * y }
c = n * sx2 - sx**2
a = (sy * sx2 - sx * sxy) / c
b = ( n * sxy - sx * sy ) / c
return a, b, fit_error(xys) { |x| a + b * x }
end
##
# To fit a functional form: y = ax^b.
#
# Takes x and y values and returns [a, b, r^2].
#
# See: http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
def fit_power xs, ys
n = xs.size
xys = xs.zip(ys)
slnxlny = sigma(xys) { |x, y| Math.log(x) * Math.log(y) }
slnx = sigma(xs) { |x | Math.log(x) }
slny = sigma(ys) { | y| Math.log(y) }
slnx2 = sigma(xs) { |x | Math.log(x) ** 2 }
b = (n * slnxlny - slnx * slny) / (n * slnx2 - slnx ** 2);
a = (slny - b * slnx) / n
return Math.exp(a), b, fit_error(xys) { |x| (Math.exp(a) * (x ** b)) }
end
##
# Enumerates over +enum+ mapping +block+ if given, returning the
# sum of the result. Eg:
#
# sigma([1, 2, 3]) # => 1 + 2 + 3 => 7
# sigma([1, 2, 3]) { |n| n ** 2 } # => 1 + 4 + 9 => 14
def sigma enum, &block
enum = enum.map(&block) if block
enum.inject { |sum, n| sum + n }
end
##
# Returns a proc that calls the specified fit method and asserts
# that the error is within a tolerable threshold.
def validation_for_fit msg, threshold
proc do |range, times|
a, b, rr = send "fit_#{msg}", range, times
assert_operator rr, :>=, threshold
[a, b, rr]
end
end
end
end
class MiniTest::Spec
##
# This is used to define a new benchmark method. You usually don't
# use this directly and is intended for those needing to write new
# performance curve fits (eg: you need a specific polynomial fit).
#
# See ::bench_performance_linear for an example of how to use this.
def self.bench name, &block
define_method "bench_#{name.gsub(/\W+/, '_')}", &block
end
##
# Specifies the ranges used for benchmarking for that class.
#
# bench_range do
# bench_exp(2, 16, 2)
# end
#
# See Unit::TestCase.bench_range for more details.
def self.bench_range &block
return super unless block
meta = (class << self; self; end)
meta.send :define_method, "bench_range", &block
end
##
# Create a benchmark that verifies that the performance is linear.
#
# describe "my class" do
# bench_performance_linear "fast_algorithm", 0.9999 do |n|
# @obj.fast_algorithm(n)
# end
# end
def self.bench_performance_linear name, threshold = 0.99, &work
bench name do
assert_performance_linear threshold, &work
end
end
##
# Create a benchmark that verifies that the performance is constant.
#
# describe "my class" do
# bench_performance_constant "zoom_algorithm!" do |n|
# @obj.zoom_algorithm!(n)
# end
# end
def self.bench_performance_constant name, threshold = 0.99, &work
bench name do
assert_performance_constant threshold, &work
end
end
##
# Create a benchmark that verifies that the performance is exponential.
#
# describe "my class" do
# bench_performance_exponential "algorithm" do |n|
# @obj.algorithm(n)
# end
# end
def self.bench_performance_exponential name, threshold = 0.99, &work
bench name do
assert_performance_exponential threshold, &work
end
end
end

20
test/lib/minitest/hell.rb Normal file
View file

@ -0,0 +1,20 @@
# encoding: utf-8
######################################################################
# This file is imported from the minitest project.
# DO NOT make modifications in this repo. They _will_ be reverted!
# File a patch instead and assign it to Ryan Davis.
######################################################################
require "minitest/parallel_each"
# :stopdoc:
class Minitest::Unit::TestCase
class << self
alias :old_test_order :test_order
def test_order
:parallel
end
end
end
# :startdoc:

200
test/lib/minitest/mock.rb Normal file
View file

@ -0,0 +1,200 @@
# encoding: utf-8
######################################################################
# This file is imported from the minitest project.
# DO NOT make modifications in this repo. They _will_ be reverted!
# File a patch instead and assign it to Ryan Davis.
######################################################################
class MockExpectationError < StandardError; end # :nodoc:
##
# A simple and clean mock object framework.
module MiniTest # :nodoc:
##
# All mock objects are an instance of Mock
class Mock
alias :__respond_to? :respond_to?
skip_methods = %w(object_id respond_to_missing? inspect === to_s)
instance_methods.each do |m|
undef_method m unless skip_methods.include?(m.to_s) || m =~ /^__/
end
def initialize # :nodoc:
@expected_calls = Hash.new { |calls, name| calls[name] = [] }
@actual_calls = Hash.new { |calls, name| calls[name] = [] }
end
##
# Expect that method +name+ is called, optionally with +args+ or a
# +blk+, and returns +retval+.
#
# @mock.expect(:meaning_of_life, 42)
# @mock.meaning_of_life # => 42
#
# @mock.expect(:do_something_with, true, [some_obj, true])
# @mock.do_something_with(some_obj, true) # => true
#
# @mock.expect(:do_something_else, true) do |a1, a2|
# a1 == "buggs" && a2 == :bunny
# end
#
# +args+ is compared to the expected args using case equality (ie, the
# '===' operator), allowing for less specific expectations.
#
# @mock.expect(:uses_any_string, true, [String])
# @mock.uses_any_string("foo") # => true
# @mock.verify # => true
#
# @mock.expect(:uses_one_string, true, ["foo"]
# @mock.uses_one_string("bar") # => true
# @mock.verify # => raises MockExpectationError
def expect(name, retval, args=[], &blk)
if block_given?
raise ArgumentError, "args ignored when block given" unless args.empty?
@expected_calls[name] << { :retval => retval, :block => blk }
else
raise ArgumentError, "args must be an array" unless Array === args
@expected_calls[name] << { :retval => retval, :args => args }
end
self
end
def __call name, data # :nodoc:
case data
when Hash then
"#{name}(#{data[:args].inspect[1..-2]}) => #{data[:retval].inspect}"
else
data.map { |d| __call name, d }.join ", "
end
end
##
# Verify that all methods were called as expected. Raises
# +MockExpectationError+ if the mock object was not called as
# expected.
def verify
@expected_calls.each do |name, calls|
calls.each do |expected|
msg1 = "expected #{__call name, expected}"
msg2 = "#{msg1}, got [#{__call name, @actual_calls[name]}]"
raise MockExpectationError, msg2 if
@actual_calls.has_key?(name) and
not @actual_calls[name].include?(expected)
raise MockExpectationError, msg1 unless
@actual_calls.has_key?(name) and
@actual_calls[name].include?(expected)
end
end
true
end
def method_missing(sym, *args) # :nodoc:
unless @expected_calls.has_key?(sym) then
raise NoMethodError, "unmocked method %p, expected one of %p" %
[sym, @expected_calls.keys.sort_by(&:to_s)]
end
index = @actual_calls[sym].length
expected_call = @expected_calls[sym][index]
unless expected_call then
raise MockExpectationError, "No more expects available for %p: %p" %
[sym, args]
end
expected_args, retval, val_block =
expected_call.values_at(:args, :retval, :block)
if val_block then
raise MockExpectationError, "mocked method %p failed block w/ %p" %
[sym, args] unless val_block.call(args)
# keep "verify" happy
@actual_calls[sym] << expected_call
return retval
end
if expected_args.size != args.size then
raise ArgumentError, "mocked method %p expects %d arguments, got %d" %
[sym, expected_args.size, args.size]
end
fully_matched = expected_args.zip(args).all? { |mod, a|
mod === a or mod == a
}
unless fully_matched then
raise MockExpectationError, "mocked method %p called with unexpected arguments %p" %
[sym, args]
end
@actual_calls[sym] << {
:retval => retval,
:args => expected_args.zip(args).map { |mod, a| mod === a ? mod : a }
}
retval
end
def respond_to?(sym, include_private = false) # :nodoc:
return true if @expected_calls.has_key?(sym.to_sym)
return __respond_to?(sym, include_private)
end
end
end
class Object # :nodoc:
##
# Add a temporary stubbed method replacing +name+ for the duration
# of the +block+. If +val_or_callable+ responds to #call, then it
# returns the result of calling it, otherwise returns the value
# as-is. Cleans up the stub at the end of the +block+. The method
# +name+ must exist before stubbing.
#
# def test_stale_eh
# obj_under_test = Something.new
# refute obj_under_test.stale?
#
# Time.stub :now, Time.at(0) do
# assert obj_under_test.stale?
# end
# end
def stub name, val_or_callable, &block
new_name = "__minitest_stub__#{name}"
metaclass = class << self; self; end
if respond_to? name and not methods.map(&:to_s).include? name.to_s then
metaclass.send :define_method, name do |*args|
super(*args)
end
end
metaclass.send :alias_method, new_name, name
metaclass.send :define_method, name do |*args|
if val_or_callable.respond_to? :call then
val_or_callable.call(*args)
else
val_or_callable
end
end
yield self
ensure
metaclass.send :undef_method, name
metaclass.send :alias_method, name, new_name
metaclass.send :undef_method, new_name
end
end

View file

@ -0,0 +1,80 @@
# encoding: utf-8
######################################################################
# This file is imported from the minitest project.
# DO NOT make modifications in this repo. They _will_ be reverted!
# File a patch instead and assign it to Ryan Davis.
######################################################################
##
# Provides a parallel #each that lets you enumerate using N threads.
# Use environment variable N to customize. Defaults to 2. Enumerable,
# so all the goodies come along (tho not all are wrapped yet to
# return another ParallelEach instance).
class ParallelEach
require 'thread'
include Enumerable
##
# How many Threads to use for this parallel #each.
N = (ENV['N'] || 2).to_i
##
# Create a new ParallelEach instance over +list+.
def initialize list
@queue = Queue.new # *sigh*... the Queue api sucks sooo much...
list.each { |i| @queue << i }
N.times { @queue << nil }
end
def grep pattern # :nodoc:
self.class.new super
end
def select(&block) # :nodoc:
self.class.new super
end
alias find_all select # :nodoc:
##
# Starts N threads that yield each element to your block. Joins the
# threads at the end.
def each
threads = N.times.map {
Thread.new do
Thread.current.abort_on_exception = true
while job = @queue.pop
yield job
end
end
}
threads.map(&:join)
end
def count
[@queue.size - N, 0].max
end
alias_method :size, :count
end
class MiniTest::Unit
alias _old_run_suites _run_suites
##
# Runs all the +suites+ for a given +type+. Runs suites declaring
# a test_order of +:parallel+ in parallel, and everything else
# serial.
def _run_suites suites, type
parallel, serial = suites.partition { |s| s.test_order == :parallel }
ParallelEach.new(parallel).map { |suite| _run_suite suite, type } +
serial.map { |suite| _run_suite suite, type }
end
end

119
test/lib/minitest/pride.rb Normal file
View file

@ -0,0 +1,119 @@
# encoding: utf-8
######################################################################
# This file is imported from the minitest project.
# DO NOT make modifications in this repo. They _will_ be reverted!
# File a patch instead and assign it to Ryan Davis.
######################################################################
require "minitest/unit"
##
# Show your testing pride!
class PrideIO
# Start an escape sequence
ESC = "\e["
# End the escape sequence
NND = "#{ESC}0m"
# The IO we're going to pipe through.
attr_reader :io
def initialize io # :nodoc:
@io = io
# stolen from /System/Library/Perl/5.10.0/Term/ANSIColor.pm
# also reference http://en.wikipedia.org/wiki/ANSI_escape_code
@colors ||= (31..36).to_a
@size = @colors.size
@index = 0
# io.sync = true
end
##
# Wrap print to colorize the output.
def print o
case o
when "." then
io.print pride o
when "E", "F" then
io.print "#{ESC}41m#{ESC}37m#{o}#{NND}"
else
io.print o
end
end
def puts(*o) # :nodoc:
o.map! { |s|
s.to_s.sub(/Finished tests/) {
@index = 0
'Fabulous tests'.split(//).map { |c|
pride(c)
}.join
}
}
super
end
##
# Color a string.
def pride string
string = "*" if string == "."
c = @colors[@index % @size]
@index += 1
"#{ESC}#{c}m#{string}#{NND}"
end
def method_missing msg, *args # :nodoc:
io.send(msg, *args)
end
end
##
# If you thought the PrideIO was colorful...
#
# (Inspired by lolcat, but with clean math)
class PrideLOL < PrideIO
PI_3 = Math::PI / 3 # :nodoc:
def initialize io # :nodoc:
# walk red, green, and blue around a circle separated by equal thirds.
#
# To visualize, type this into wolfram-alpha:
#
# plot (3*sin(x)+3), (3*sin(x+2*pi/3)+3), (3*sin(x+4*pi/3)+3)
# 6 has wide pretty gradients. 3 == lolcat, about half the width
@colors = (0...(6 * 7)).map { |n|
n *= 1.0 / 6
r = (3 * Math.sin(n ) + 3).to_i
g = (3 * Math.sin(n + 2 * PI_3) + 3).to_i
b = (3 * Math.sin(n + 4 * PI_3) + 3).to_i
# Then we take rgb and encode them in a single number using base 6.
# For some mysterious reason, we add 16... to clear the bottom 4 bits?
# Yes... they're ugly.
36 * r + 6 * g + b + 16
}
super
end
##
# Make the string even more colorful. Damnit.
def pride string
c = @colors[@index % @size]
@index += 1
"#{ESC}38;5;#{c}m#{string}#{NND}"
end
end
klass = ENV['TERM'] =~ /^xterm|-256color$/ ? PrideLOL : PrideIO
MiniTest::Unit.output = klass.new(MiniTest::Unit.output)

551
test/lib/minitest/spec.rb Normal file
View file

@ -0,0 +1,551 @@
# encoding: utf-8
######################################################################
# This file is imported from the minitest project.
# DO NOT make modifications in this repo. They _will_ be reverted!
# File a patch instead and assign it to Ryan Davis.
######################################################################
#!/usr/bin/ruby -w
require 'minitest/unit'
class Module # :nodoc:
def infect_an_assertion meth, new_name, dont_flip = false # :nodoc:
# warn "%-22p -> %p %p" % [meth, new_name, dont_flip]
self.class_eval <<-EOM
def #{new_name} *args
case
when Proc === self then
MiniTest::Spec.current.#{meth}(*args, &self)
when #{!!dont_flip} then
MiniTest::Spec.current.#{meth}(self, *args)
else
MiniTest::Spec.current.#{meth}(args.first, self, *args[1..-1])
end
end
EOM
end
##
# infect_with_assertions has been removed due to excessive clever.
# Use infect_an_assertion directly instead.
def infect_with_assertions(pos_prefix, neg_prefix,
skip_re,
dont_flip_re = /\c0/,
map = {})
abort "infect_with_assertions is dead. Use infect_an_assertion directly"
end
end
module Kernel # :nodoc:
##
# Describe a series of expectations for a given target +desc+.
#
# TODO: find good tutorial url.
#
# Defines a test class subclassing from either MiniTest::Spec or
# from the surrounding describe's class. The surrounding class may
# subclass MiniTest::Spec manually in order to easily share code:
#
# class MySpec < MiniTest::Spec
# # ... shared code ...
# end
#
# class TestStuff < MySpec
# it "does stuff" do
# # shared code available here
# end
# describe "inner stuff" do
# it "still does stuff" do
# # ...and here
# end
# end
# end
def describe desc, additional_desc = nil, &block # :doc:
stack = MiniTest::Spec.describe_stack
name = [stack.last, desc, additional_desc].compact.join("::")
sclas = stack.last || if Class === self && is_a?(MiniTest::Spec::DSL) then
self
else
MiniTest::Spec.spec_type desc
end
cls = sclas.create name, desc
stack.push cls
cls.class_eval(&block)
stack.pop
cls
end
private :describe
end
##
# MiniTest::Spec -- The faster, better, less-magical spec framework!
#
# For a list of expectations, see MiniTest::Expectations.
class MiniTest::Spec < MiniTest::Unit::TestCase
##
# Oh look! A MiniTest::Spec::DSL module! Eat your heart out DHH.
module DSL
##
# Contains pairs of matchers and Spec classes to be used to
# calculate the superclass of a top-level describe. This allows for
# automatically customizable spec types.
#
# See: register_spec_type and spec_type
TYPES = [[//, MiniTest::Spec]]
##
# Register a new type of spec that matches the spec's description.
# This method can take either a Regexp and a spec class or a spec
# class and a block that takes the description and returns true if
# it matches.
#
# Eg:
#
# register_spec_type(/Controller$/, MiniTest::Spec::Rails)
#
# or:
#
# register_spec_type(MiniTest::Spec::RailsModel) do |desc|
# desc.superclass == ActiveRecord::Base
# end
def register_spec_type(*args, &block)
if block then
matcher, klass = block, args.first
else
matcher, klass = *args
end
TYPES.unshift [matcher, klass]
end
##
# Figure out the spec class to use based on a spec's description. Eg:
#
# spec_type("BlahController") # => MiniTest::Spec::Rails
def spec_type desc
TYPES.find { |matcher, klass|
if matcher.respond_to? :call then
matcher.call desc
else
matcher === desc.to_s
end
}.last
end
def describe_stack # :nodoc:
Thread.current[:describe_stack] ||= []
end
##
# Returns the children of this spec.
def children
@children ||= []
end
def nuke_test_methods! # :nodoc:
self.public_instance_methods.grep(/^test_/).each do |name|
self.send :undef_method, name
end
end
##
# Define a 'before' action. Inherits the way normal methods should.
#
# NOTE: +type+ is ignored and is only there to make porting easier.
#
# Equivalent to MiniTest::Unit::TestCase#setup.
def before type = nil, &block
define_method :setup do
super()
self.instance_eval(&block)
end
end
##
# Define an 'after' action. Inherits the way normal methods should.
#
# NOTE: +type+ is ignored and is only there to make porting easier.
#
# Equivalent to MiniTest::Unit::TestCase#teardown.
def after type = nil, &block
define_method :teardown do
self.instance_eval(&block)
super()
end
end
##
# Define an expectation with name +desc+. Name gets morphed to a
# proper test method name. For some freakish reason, people who
# write specs don't like class inheritance, so this goes way out of
# its way to make sure that expectations aren't inherited.
#
# This is also aliased to #specify and doesn't require a +desc+ arg.
#
# Hint: If you _do_ want inheritence, use minitest/unit. You can mix
# and match between assertions and expectations as much as you want.
def it desc = "anonymous", &block
block ||= proc { skip "(no tests defined)" }
@specs ||= 0
@specs += 1
name = "test_%04d_%s" % [ @specs, desc ]
define_method name, &block
self.children.each do |mod|
mod.send :undef_method, name if mod.public_method_defined? name
end
name
end
##
# Essentially, define an accessor for +name+ with +block+.
#
# Why use let instead of def? I honestly don't know.
def let name, &block
define_method name do
@_memoized ||= {}
@_memoized.fetch(name) { |k| @_memoized[k] = instance_eval(&block) }
end
end
##
# Another lazy man's accessor generator. Made even more lazy by
# setting the name for you to +subject+.
def subject &block
let :subject, &block
end
def create name, desc # :nodoc:
cls = Class.new(self) do
@name = name
@desc = desc
nuke_test_methods!
end
children << cls
cls
end
def name # :nodoc:
defined?(@name) ? @name : super
end
def to_s # :nodoc:
name # Can't alias due to 1.8.7, not sure why
end
# :stopdoc:
attr_reader :desc
alias :specify :it
# :startdoc:
end
extend DSL
TYPES = DSL::TYPES # :nodoc:
end
##
# It's where you hide your "assertions".
module MiniTest::Expectations
##
# See MiniTest::Assertions#assert_empty.
#
# collection.must_be_empty
#
# :method: must_be_empty
infect_an_assertion :assert_empty, :must_be_empty, :unary
##
# See MiniTest::Assertions#assert_equal
#
# a.must_equal b
#
# :method: must_equal
infect_an_assertion :assert_equal, :must_equal
##
# See MiniTest::Assertions#assert_in_delta
#
# n.must_be_close_to m [, delta]
#
# :method: must_be_close_to
infect_an_assertion :assert_in_delta, :must_be_close_to
alias :must_be_within_delta :must_be_close_to # :nodoc:
##
# See MiniTest::Assertions#assert_in_epsilon
#
# n.must_be_within_epsilon m [, epsilon]
#
# :method: must_be_within_epsilon
infect_an_assertion :assert_in_epsilon, :must_be_within_epsilon
##
# See MiniTest::Assertions#assert_includes
#
# collection.must_include obj
#
# :method: must_include
infect_an_assertion :assert_includes, :must_include, :reverse
##
# See MiniTest::Assertions#assert_instance_of
#
# obj.must_be_instance_of klass
#
# :method: must_be_instance_of
infect_an_assertion :assert_instance_of, :must_be_instance_of
##
# See MiniTest::Assertions#assert_kind_of
#
# obj.must_be_kind_of mod
#
# :method: must_be_kind_of
infect_an_assertion :assert_kind_of, :must_be_kind_of
##
# See MiniTest::Assertions#assert_match
#
# a.must_match b
#
# :method: must_match
infect_an_assertion :assert_match, :must_match
##
# See MiniTest::Assertions#assert_nil
#
# obj.must_be_nil
#
# :method: must_be_nil
infect_an_assertion :assert_nil, :must_be_nil, :unary
##
# See MiniTest::Assertions#assert_operator
#
# n.must_be :<=, 42
#
# This can also do predicates:
#
# str.must_be :empty?
#
# :method: must_be
infect_an_assertion :assert_operator, :must_be, :reverse
##
# See MiniTest::Assertions#assert_output
#
# proc { ... }.must_output out_or_nil [, err]
#
# :method: must_output
infect_an_assertion :assert_output, :must_output
##
# See MiniTest::Assertions#assert_raises
#
# proc { ... }.must_raise exception
#
# :method: must_raise
infect_an_assertion :assert_raises, :must_raise
##
# See MiniTest::Assertions#assert_respond_to
#
# obj.must_respond_to msg
#
# :method: must_respond_to
infect_an_assertion :assert_respond_to, :must_respond_to, :reverse
##
# See MiniTest::Assertions#assert_same
#
# a.must_be_same_as b
#
# :method: must_be_same_as
infect_an_assertion :assert_same, :must_be_same_as
##
# See MiniTest::Assertions#assert_send
# TODO: remove me
#
# a.must_send
#
# :method: must_send
infect_an_assertion :assert_send, :must_send
##
# See MiniTest::Assertions#assert_silent
#
# proc { ... }.must_be_silent
#
# :method: must_be_silent
infect_an_assertion :assert_silent, :must_be_silent
##
# See MiniTest::Assertions#assert_throws
#
# proc { ... }.must_throw sym
#
# :method: must_throw
infect_an_assertion :assert_throws, :must_throw
##
# See MiniTest::Assertions#refute_empty
#
# collection.wont_be_empty
#
# :method: wont_be_empty
infect_an_assertion :refute_empty, :wont_be_empty, :unary
##
# See MiniTest::Assertions#refute_equal
#
# a.wont_equal b
#
# :method: wont_equal
infect_an_assertion :refute_equal, :wont_equal
##
# See MiniTest::Assertions#refute_in_delta
#
# n.wont_be_close_to m [, delta]
#
# :method: wont_be_close_to
infect_an_assertion :refute_in_delta, :wont_be_close_to
alias :wont_be_within_delta :wont_be_close_to # :nodoc:
##
# See MiniTest::Assertions#refute_in_epsilon
#
# n.wont_be_within_epsilon m [, epsilon]
#
# :method: wont_be_within_epsilon
infect_an_assertion :refute_in_epsilon, :wont_be_within_epsilon
##
# See MiniTest::Assertions#refute_includes
#
# collection.wont_include obj
#
# :method: wont_include
infect_an_assertion :refute_includes, :wont_include, :reverse
##
# See MiniTest::Assertions#refute_instance_of
#
# obj.wont_be_instance_of klass
#
# :method: wont_be_instance_of
infect_an_assertion :refute_instance_of, :wont_be_instance_of
##
# See MiniTest::Assertions#refute_kind_of
#
# obj.wont_be_kind_of mod
#
# :method: wont_be_kind_of
infect_an_assertion :refute_kind_of, :wont_be_kind_of
##
# See MiniTest::Assertions#refute_match
#
# a.wont_match b
#
# :method: wont_match
infect_an_assertion :refute_match, :wont_match
##
# See MiniTest::Assertions#refute_nil
#
# obj.wont_be_nil
#
# :method: wont_be_nil
infect_an_assertion :refute_nil, :wont_be_nil, :unary
##
# See MiniTest::Assertions#refute_operator
#
# n.wont_be :<=, 42
#
# This can also do predicates:
#
# str.wont_be :empty?
#
# :method: wont_be
infect_an_assertion :refute_operator, :wont_be, :reverse
##
# See MiniTest::Assertions#refute_respond_to
#
# obj.wont_respond_to msg
#
# :method: wont_respond_to
infect_an_assertion :refute_respond_to, :wont_respond_to, :reverse
##
# See MiniTest::Assertions#refute_same
#
# a.wont_be_same_as b
#
# :method: wont_be_same_as
infect_an_assertion :refute_same, :wont_be_same_as
end
class Object # :nodoc:
include MiniTest::Expectations unless ENV["MT_NO_EXPECTATIONS"]
end

1422
test/lib/minitest/unit.rb Normal file

File diff suppressed because it is too large Load diff

880
test/lib/test/unit.rb Normal file
View file

@ -0,0 +1,880 @@
begin
gem 'minitest', '< 5.0.0' if defined? Gem
rescue Gem::LoadError
end
require 'minitest/unit'
require 'test/unit/assertions'
require 'test/unit/testcase'
require 'optparse'
# See Test::Unit
module Test
##
# Test::Unit is an implementation of the xUnit testing framework for Ruby.
#
# If you are writing new test code, please use MiniTest instead of Test::Unit.
#
# Test::Unit has been left in the standard library to support legacy test
# suites.
module Unit
TEST_UNIT_IMPLEMENTATION = 'test/unit compatibility layer using minitest' # :nodoc:
module RunCount # :nodoc: all
@@run_count = 0
def self.have_run?
@@run_count.nonzero?
end
def run(*)
@@run_count += 1
super
end
def run_once
return if have_run?
return if $! # don't run if there was an exception
yield
end
module_function :run_once
end
module Options # :nodoc: all
def initialize(*, &block)
@init_hook = block
@options = nil
super(&nil)
end
def option_parser
@option_parser ||= OptionParser.new
end
def process_args(args = [])
return @options if @options
orig_args = args.dup
options = {}
opts = option_parser
setup_options(opts, options)
opts.parse!(args)
orig_args -= args
args = @init_hook.call(args, options) if @init_hook
non_options(args, options)
@help = orig_args.map { |s| s =~ /[\s|&<>$()]/ ? s.inspect : s }.join " "
@options = options
if @options[:parallel]
@files = args
@args = orig_args
end
options
end
private
def setup_options(opts, options)
opts.separator 'minitest options:'
opts.version = MiniTest::Unit::VERSION
options[:retry] = true
options[:job_status] = nil
opts.on '-h', '--help', 'Display this help.' do
puts opts
exit
end
opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m|
options[:seed] = m
end
opts.on '-v', '--verbose', "Verbose. Show progress processing files." do
options[:verbose] = true
self.verbose = options[:verbose]
end
opts.on '-n', '--name PATTERN', "Filter test names on pattern." do |a|
options[:filter] = a
end
opts.on '--jobs-status [TYPE]', [:normal, :replace],
"Show status of jobs every file; Disabled when --jobs isn't specified." do |type|
options[:job_status] = type || :normal
end
opts.on '-j N', '--jobs N', "Allow run tests with N jobs at once" do |a|
if /^t/ =~ a
options[:testing] = true # For testing
options[:parallel] = a[1..-1].to_i
else
options[:parallel] = a.to_i
end
end
opts.on '--separate', "Restart job process after one testcase has done" do
options[:parallel] ||= 1
options[:separate] = true
end
opts.on '--retry', "Retry running testcase when --jobs specified" do
options[:retry] = true
end
opts.on '--no-retry', "Disable --retry" do
options[:retry] = false
end
opts.on '--ruby VAL', "Path to ruby; It'll have used at -j option" do |a|
options[:ruby] = a.split(/ /).reject(&:empty?)
end
opts.on '-q', '--hide-skip', 'Hide skipped tests' do
options[:hide_skip] = true
end
opts.on '--show-skip', 'Show skipped tests' do
options[:hide_skip] = false
end
opts.on '--color[=WHEN]',
[:always, :never, :auto],
"colorize the output. WHEN defaults to 'always'", "or can be 'never' or 'auto'." do |c|
options[:color] = c || :always
end
opts.on '--tty[=WHEN]',
[:yes, :no],
"force to output tty control. WHEN defaults to 'yes'", "or can be 'no'." do |c|
@tty = c != :no
end
end
def non_options(files, options)
begin
require "rbconfig"
rescue LoadError
warn "#{caller(1)[0]}: warning: Parallel running disabled because can't get path to ruby; run specify with --ruby argument"
options[:parallel] = nil
else
options[:ruby] ||= [RbConfig.ruby]
end
true
end
end
module GlobOption # :nodoc: all
@@testfile_prefix = "test"
def setup_options(parser, options)
super
parser.on '-b', '--basedir=DIR', 'Base directory of test suites.' do |dir|
options[:base_directory] = dir
end
parser.on '-x', '--exclude PATTERN', 'Exclude test files on pattern.' do |pattern|
(options[:reject] ||= []) << pattern
end
end
def non_options(files, options)
paths = [options.delete(:base_directory), nil].uniq
if reject = options.delete(:reject)
reject_pat = Regexp.union(reject.map {|r| /#{r}/ })
end
files.map! {|f|
f = f.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
((paths if /\A\.\.?(?:\z|\/)/ !~ f) || [nil]).any? do |prefix|
if prefix
path = f.empty? ? prefix : "#{prefix}/#{f}"
else
next if f.empty?
path = f
end
if !(match = Dir["#{path}/**/#{@@testfile_prefix}_*.rb"]).empty?
if reject
match.reject! {|n|
n[(prefix.length+1)..-1] if prefix
reject_pat =~ n
}
end
break match
elsif !reject or reject_pat !~ f and File.exist? path
break path
end
end or
raise ArgumentError, "file not found: #{f}"
}
files.flatten!
super(files, options)
end
end
module LoadPathOption # :nodoc: all
def setup_options(parser, options)
super
parser.on '-Idirectory', 'Add library load path' do |dirs|
dirs.split(':').each { |d| $LOAD_PATH.unshift d }
end
end
end
module GCStressOption # :nodoc: all
def setup_options(parser, options)
super
parser.on '--[no-]gc-stress', 'Set GC.stress as true' do |flag|
options[:gc_stress] = flag
end
end
def non_options(files, options)
if options.delete(:gc_stress)
MiniTest::Unit::TestCase.class_eval do
oldrun = instance_method(:run)
define_method(:run) do |runner|
begin
gc_stress, GC.stress = GC.stress, true
oldrun.bind(self).call(runner)
ensure
GC.stress = gc_stress
end
end
end
end
super
end
end
module RequireFiles # :nodoc: all
def non_options(files, options)
return false if !super
result = false
files.each {|f|
d = File.dirname(path = File.realpath(f))
unless $:.include? d
$: << d
end
begin
require path unless options[:parallel]
result = true
rescue LoadError
puts "#{f}: #{$!}"
end
}
result
end
end
class Runner < MiniTest::Unit # :nodoc: all
include Test::Unit::Options
include Test::Unit::GlobOption
include Test::Unit::LoadPathOption
include Test::Unit::GCStressOption
include Test::Unit::RunCount
class Worker
def self.launch(ruby,args=[])
io = IO.popen([*ruby,
"#{File.dirname(__FILE__)}/unit/parallel.rb",
*args], "rb+")
new(io, io.pid, :waiting)
end
attr_reader :quit_called
def initialize(io, pid, status)
@io = io
@pid = pid
@status = status
@file = nil
@real_file = nil
@loadpath = []
@hooks = {}
@quit_called = false
end
def puts(*args)
@io.puts(*args)
end
def run(task,type)
@file = File.basename(task, ".rb")
@real_file = task
begin
puts "loadpath #{[Marshal.dump($:-@loadpath)].pack("m0")}"
@loadpath = $:.dup
puts "run #{task} #{type}"
@status = :prepare
rescue Errno::EPIPE
died
rescue IOError
raise unless ["stream closed","closed stream"].include? $!.message
died
end
end
def hook(id,&block)
@hooks[id] ||= []
@hooks[id] << block
self
end
def read
res = (@status == :quit) ? @io.read : @io.gets
res && res.chomp
end
def close
@io.close unless @io.closed?
self
rescue IOError
end
def quit
return if @io.closed?
@quit_called = true
@io.puts "quit"
@io.close
end
def kill
Process.kill(:KILL, @pid)
rescue Errno::ESRCH
end
def died(*additional)
@status = :quit
@io.close
call_hook(:dead,*additional)
end
def to_s
if @file
"#{@pid}=#{@file}"
else
"#{@pid}:#{@status.to_s.ljust(7)}"
end
end
attr_reader :io, :pid
attr_accessor :status, :file, :real_file, :loadpath
private
def call_hook(id,*additional)
@hooks[id] ||= []
@hooks[id].each{|hook| hook[self,additional] }
self
end
end
class << self; undef autorun; end
@@stop_auto_run = false
def self.autorun
at_exit {
Test::Unit::RunCount.run_once {
exit(Test::Unit::Runner.new.run(ARGV) || true)
} unless @@stop_auto_run
} unless @@installed_at_exit
@@installed_at_exit = true
end
def after_worker_down(worker, e=nil, c=false)
return unless @options[:parallel]
return if @interrupt
warn e if e
@need_quit = true
warn ""
warn "Some worker was crashed. It seems ruby interpreter's bug"
warn "or, a bug of test/unit/parallel.rb. try again without -j"
warn "option."
warn ""
STDERR.flush
exit c
end
def terminal_width
unless @terminal_width ||= nil
begin
require 'io/console'
width = $stdout.winsize[1]
rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF
width = ENV["COLUMNS"].to_i.nonzero? || 80
end
width -= 1 if /mswin|mingw/ =~ RUBY_PLATFORM
@terminal_width = width
end
@terminal_width
end
def del_status_line
@status_line_size ||= 0
unless @options[:job_status] == :replace
$stdout.puts
return
end
print "\r"+" "*@status_line_size+"\r"
$stdout.flush
@status_line_size = 0
end
def put_status(line)
unless @options[:job_status] == :replace
print(line)
return
end
@status_line_size ||= 0
del_status_line
$stdout.flush
line = line[0...terminal_width]
print line
$stdout.flush
@status_line_size = line.size
end
def add_status(line)
unless @options[:job_status] == :replace
print(line)
return
end
@status_line_size ||= 0
line = line[0...(terminal_width-@status_line_size)]
print line
$stdout.flush
@status_line_size += line.size
end
def jobs_status
return unless @options[:job_status]
puts "" unless @options[:verbose] or @options[:job_status] == :replace
status_line = @workers.map(&:to_s).join(" ")
update_status(status_line) or (puts; nil)
end
def del_jobs_status
return unless @options[:job_status] == :replace && @status_line_size.nonzero?
del_status_line
end
def after_worker_quit(worker)
return unless @options[:parallel]
return if @interrupt
@workers.delete(worker)
@dead_workers << worker
@ios = @workers.map(&:io)
end
def launch_worker
begin
worker = Worker.launch(@options[:ruby],@args)
rescue => e
abort "ERROR: Failed to launch job process - #{e.class}: #{e.message}"
end
worker.hook(:dead) do |w,info|
after_worker_quit w
after_worker_down w, *info if !info.empty? && !worker.quit_called
end
@workers << worker
@ios << worker.io
@workers_hash[worker.io] = worker
worker
end
def delete_worker(worker)
@workers_hash.delete worker.io
@workers.delete worker
@ios.delete worker.io
end
def quit_workers
return if @workers.empty?
@workers.reject! do |worker|
begin
timeout(1) do
worker.quit
end
rescue Errno::EPIPE
rescue Timeout::Error
end
worker.close
end
return if @workers.empty?
begin
timeout(0.2 * @workers.size) do
Process.waitall
end
rescue Timeout::Error
@workers.each do |worker|
worker.kill
end
@worker.clear
end
end
def start_watchdog
Thread.new do
while stat = Process.wait2
break if @interrupt # Break when interrupt
pid, stat = stat
w = (@workers + @dead_workers).find{|x| pid == x.pid }
next unless w
w = w.dup
if w.status != :quit && !w.quit_called?
# Worker down
w.died(nil, !stat.signaled? && stat.exitstatus)
end
end
end
end
def deal(io, type, result, rep, shutting_down = false)
worker = @workers_hash[io]
case worker.read
when /^okay$/
worker.status = :running
jobs_status
when /^ready(!)?$/
bang = $1
worker.status = :ready
return nil unless task = @tasks.shift
if @options[:separate] and not bang
worker.quit
worker = add_worker
end
worker.run(task, type)
@test_count += 1
jobs_status
when /^done (.+?)$/
r = Marshal.load($1.unpack("m")[0])
result << r[0..1] unless r[0..1] == [nil,nil]
rep << {file: worker.real_file, report: r[2], result: r[3], testcase: r[5]}
$:.push(*r[4]).uniq!
return true
when /^p (.+?)$/
del_jobs_status
print $1.unpack("m")[0]
jobs_status if @options[:job_status] == :replace
when /^after (.+?)$/
@warnings << Marshal.load($1.unpack("m")[0])
when /^bye (.+?)$/
after_worker_down worker, Marshal.load($1.unpack("m")[0])
when /^bye$/, nil
if shutting_down || worker.quit_called
after_worker_quit worker
else
after_worker_down worker
end
end
return false
end
def _run_parallel suites, type, result
if @options[:parallel] < 1
warn "Error: parameter of -j option should be greater than 0."
return
end
# Require needed things for parallel running
require 'thread'
require 'timeout'
@tasks = @files.dup # Array of filenames.
@need_quit = false
@dead_workers = [] # Array of dead workers.
@warnings = []
@total_tests = @tasks.size.to_s(10)
rep = [] # FIXME: more good naming
@workers = [] # Array of workers.
@workers_hash = {} # out-IO => worker
@ios = [] # Array of worker IOs
begin
# Thread: watchdog
watchdog = start_watchdog
@options[:parallel].times {launch_worker}
while _io = IO.select(@ios)[0]
break if _io.any? do |io|
@need_quit or
(deal(io, type, result, rep).nil? and
!@workers.any? {|x| [:running, :prepare].include? x.status})
end
end
rescue Interrupt => ex
@interrupt = ex
return result
ensure
watchdog.kill if watchdog
if @interrupt
@ios.select!{|x| @workers_hash[x].status == :running }
while !@ios.empty? && (__io = IO.select(@ios,[],[],10))
__io[0].reject! {|io| deal(io, type, result, rep, true)}
end
end
quit_workers
unless @interrupt || !@options[:retry] || @need_quit
@options[:parallel] = false
suites, rep = rep.partition {|r| r[:testcase] && r[:file] && r[:report].any? {|e| !e[2].is_a?(MiniTest::Skip)}}
suites.map {|r| r[:file]}.uniq.each {|file| require file}
suites.map! {|r| eval("::"+r[:testcase])}
del_status_line or puts
unless suites.empty?
puts "Retrying..."
_run_suites(suites, type)
end
end
unless @options[:retry]
del_status_line or puts
end
unless rep.empty?
rep.each do |r|
r[:report].each do |f|
puke(*f) if f
end
end
if @options[:retry]
@errors += rep.map{|x| x[:result][0] }.inject(:+)
@failures += rep.map{|x| x[:result][1] }.inject(:+)
@skips += rep.map{|x| x[:result][2] }.inject(:+)
end
end
unless @warnings.empty?
warn ""
@warnings.uniq! {|w| w[1].message}
@warnings.each do |w|
warn "#{w[0]}: #{w[1].message} (#{w[1].class})"
end
warn ""
end
end
end
def _run_suites suites, type
_prepare_run(suites, type)
@interrupt = nil
result = []
GC.start
if @options[:parallel]
_run_parallel suites, type, result
else
suites.each {|suite|
begin
result << _run_suite(suite, type)
rescue Interrupt => e
@interrupt = e
break
end
}
end
report.reject!{|r| r.start_with? "Skipped:" } if @options[:hide_skip]
report.sort_by!{|r| r.start_with?("Skipped:") ? 0 : \
(r.start_with?("Failure:") ? 1 : 2) }
result
end
alias mini_run_suite _run_suite
def output
(@output ||= nil) || super
end
def _prepare_run(suites, type)
options[:job_status] ||= :replace if @tty && !@verbose
case options[:color]
when :always
color = true
when :auto, nil
color = @options[:job_status] == :replace && /dumb/ !~ ENV["TERM"]
else
color = false
end
if color
# dircolors-like style
colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:]*)/)] : {}
@passed_color = "\e[#{colors["pass"] || "32"}m"
@failed_color = "\e[#{colors["fail"] || "31"}m"
@skipped_color = "\e[#{colors["skip"] || "33"}m"
@reset_color = "\e[m"
else
@passed_color = @failed_color = @skipped_color = @reset_color = ""
end
if color or @options[:job_status] == :replace
@verbose = !options[:parallel]
@output = StatusLineOutput.new(self)
end
if /\A\/(.*)\/\z/ =~ (filter = options[:filter])
filter = Regexp.new($1)
end
type = "#{type}_methods"
total = if filter
suites.inject(0) {|n, suite| n + suite.send(type).grep(filter).size}
else
suites.inject(0) {|n, suite| n + suite.send(type).size}
end
@test_count = 0
@total_tests = total.to_s(10)
end
def new_test(s)
@test_count += 1
update_status(s)
end
def update_status(s)
count = @test_count.to_s(10).rjust(@total_tests.size)
put_status("#{@passed_color}[#{count}/#{@total_tests}]#{@reset_color} #{s}")
end
def _print(s); $stdout.print(s); end
def succeed; del_status_line; end
def failed(s)
sep = "\n"
@report_count ||= 0
report.each do |msg|
if msg.start_with? "Skipped:"
if @options[:hide_skip]
del_status_line
next
end
color = @skipped_color
else
color = @failed_color
end
msg = msg.split(/$/, 2)
$stdout.printf("%s%s%3d) %s%s%s\n",
sep, color, @report_count += 1,
msg[0], @reset_color, msg[1])
sep = nil
end
report.clear
end
# Overriding of MiniTest::Unit#puke
def puke klass, meth, e
# TODO:
# this overriding is for minitest feature that skip messages are
# hidden when not verbose (-v), note this is temporally.
n = report.size
rep = super
if MiniTest::Skip === e and /no message given\z/ =~ e.message
report.slice!(n..-1)
rep = "."
end
rep
end
def initialize
super
@tty = $stdout.tty?
end
def status(*args)
result = super
raise @interrupt if @interrupt
result
end
def run(*args)
result = super
puts "\nruby -v: #{RUBY_DESCRIPTION}"
result
end
end
class StatusLineOutput < Struct.new(:runner) # :nodoc: all
def puts(*a) $stdout.puts(*a) unless a.empty? end
def respond_to_missing?(*a) $stdout.respond_to?(*a) end
def method_missing(*a, &b) $stdout.__send__(*a, &b) end
def print(s)
case s
when /\A(.*\#.*) = \z/
runner.new_test($1)
when /\A(.* s) = \z/
runner.add_status(" = "+$1.chomp)
when /\A\.+\z/
runner.succeed
when /\A[EFS]\z/
runner.failed(s)
else
$stdout.print(s)
end
end
end
class AutoRunner # :nodoc: all
class Runner < Test::Unit::Runner
include Test::Unit::RequireFiles
end
attr_accessor :to_run, :options
def initialize(force_standalone = false, default_dir = nil, argv = ARGV)
@force_standalone = force_standalone
@runner = Runner.new do |files, options|
options[:base_directory] ||= default_dir
files << default_dir if files.empty? and default_dir
@to_run = files
yield self if block_given?
files
end
Runner.runner = @runner
@options = @runner.option_parser
if @force_standalone
@options.banner.sub!(/\[options\]/, '\& tests...')
end
@argv = argv
end
def process_args(*args)
@runner.process_args(*args)
!@to_run.empty?
end
def run
if @force_standalone and not process_args(@argv)
abort @options.banner
end
@runner.run(@argv) || true
end
def self.run(*args)
new(*args).run
end
end
class ProxyError < StandardError # :nodoc: all
def initialize(ex)
@message = ex.message
@backtrace = ex.backtrace
end
attr_accessor :message, :backtrace
end
end
end
module MiniTest # :nodoc: all
class Unit
end
end
class MiniTest::Unit::TestCase # :nodoc: all
undef run_test
RUN_TEST_TRACE = "#{__FILE__}:#{__LINE__+3}:in `run_test'".freeze
def run_test(name)
progname, $0 = $0, "#{$0}: #{self.class}##{name}"
self.__send__(name)
ensure
$@.delete(RUN_TEST_TRACE) if $@
$0 = progname
end
end
Test::Unit::Runner.autorun

View file

@ -0,0 +1,461 @@
require 'minitest/unit'
require 'pp'
module Test
module Unit
module Assertions
include MiniTest::Assertions
def mu_pp(obj) #:nodoc:
obj.pretty_inspect.chomp
end
MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc:
# :call-seq:
# assert(test, [failure_message])
#
#Tests if +test+ is true.
#
#+msg+ may be a String or a Proc. If +msg+ is a String, it will be used
#as the failure message. Otherwise, the result of calling +msg+ will be
#used as the message if the assertion fails.
#
#If no +msg+ is given, a default message will be used.
#
# assert(false, "This was expected to be true")
def assert(test, *msgs)
case msg = msgs.first
when String, Proc
when nil
msgs.shift
else
bt = caller.reject { |s| s.start_with?(MINI_DIR) }
raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt
end unless msgs.empty?
super
end
# :call-seq:
# assert_block( failure_message = nil )
#
#Tests the result of the given block. If the block does not return true,
#the assertion will fail. The optional +failure_message+ argument is the same as in
#Assertions#assert.
#
# assert_block do
# [1, 2, 3].any? { |num| num < 1 }
# end
def assert_block(*msgs)
assert yield, *msgs
end
# :call-seq:
# assert_raise( *args, &block )
#
#Tests if the given block raises an exception. Acceptable exception
#types may be given as optional arguments. If the last argument is a
#String, it will be used as the error message.
#
# assert_raise do #Fails, no Exceptions are raised
# end
#
# assert_raise NameError do
# puts x #Raises NameError, so assertion succeeds
# end
def assert_raise(*exp, &b)
case exp.last
when String, Proc
msg = exp.pop
end
begin
yield
rescue MiniTest::Skip => e
return e if exp.include? MiniTest::Skip
raise e
rescue Exception => e
expected = exp.any? { |ex|
if ex.instance_of? Module then
e.kind_of? ex
else
e.instance_of? ex
end
}
assert expected, proc {
exception_details(e, message(msg) {"#{mu_pp(exp)} exception expected, not"}.call)
}
return e
end
exp = exp.first if exp.size == 1
flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"})
end
# :call-seq:
# assert_raise_with_message(exception, expected, msg = nil, &block)
#
#Tests if the given block raises an exception with the expected
#message.
#
# assert_raise_with_message(RuntimeError, "foo") do
# nil #Fails, no Exceptions are raised
# end
#
# assert_raise_with_message(RuntimeError, "foo") do
# raise ArgumentError, "foo" #Fails, different Exception is raised
# end
#
# assert_raise_with_message(RuntimeError, "foo") do
# raise "bar" #Fails, RuntimeError is raised but the message differs
# end
#
# assert_raise_with_message(RuntimeError, "foo") do
# raise "foo" #Raises RuntimeError with the message, so assertion succeeds
# end
def assert_raise_with_message(exception, expected, msg = nil, &block)
case expected
when String
assert = :assert_equal
when Regexp
assert = :assert_match
else
raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}"
end
ex = assert_raise(exception, *msg) {yield}
msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"}
if assert == :assert_equal
assert_equal(expected, ex.message, msg)
else
msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp ex.message}" }
assert expected =~ ex.message, msg
block.binding.eval("proc{|_|$~=_}").call($~)
end
ex
end
# :call-seq:
# assert_nothing_raised( *args, &block )
#
#If any exceptions are given as arguments, the assertion will
#fail if one of those exceptions are raised. Otherwise, the test fails
#if any exceptions are raised.
#
#The final argument may be a failure message.
#
# assert_nothing_raised RuntimeError do
# raise Exception #Assertion passes, Exception is not a RuntimeError
# end
#
# assert_nothing_raised do
# raise Exception #Assertion fails
# end
def assert_nothing_raised(*args)
self._assertions += 1
if Module === args.last
msg = nil
else
msg = args.pop
end
begin
line = __LINE__; yield
rescue MiniTest::Skip
raise
rescue Exception => e
bt = e.backtrace
as = e.instance_of?(MiniTest::Assertion)
if as
ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o
bt.reject! {|ln| ans =~ ln}
end
if ((args.empty? && !as) ||
args.any? {|a| a.instance_of?(Module) ? e.is_a?(a) : e.class == a })
msg = message(msg) { "Exception raised:\n<#{mu_pp(e)}>" }
raise MiniTest::Assertion, msg.call, bt
else
raise
end
end
nil
end
# :call-seq:
# assert_nothing_thrown( failure_message = nil, &block )
#
#Fails if the given block uses a call to Kernel#throw, and
#returns the result of the block otherwise.
#
#An optional failure message may be provided as the final argument.
#
# assert_nothing_thrown "Something was thrown!" do
# throw :problem?
# end
def assert_nothing_thrown(msg=nil)
begin
ret = yield
rescue ArgumentError => error
raise error if /\Auncaught throw (.+)\z/m !~ error.message
msg = message(msg) { "<#{$1}> was thrown when nothing was expected" }
flunk(msg)
end
assert(true, "Expected nothing to be thrown")
ret
end
# :call-seq:
# assert_throw( tag, failure_message = nil, &block )
#
#Fails unless the given block throws +tag+, returns the caught
#value otherwise.
#
#An optional failure message may be provided as the final argument.
#
# tag = Object.new
# assert_throw(tag, "#{tag} was not thrown!") do
# throw tag
# end
def assert_throw(tag, msg = nil)
ret = catch(tag) do
begin
yield(tag)
rescue ArgumentError => e
raise unless thrown = e.message[/\Auncaught throw (.+)\z/m, 1]
end
msg = message(msg) {
"Expected #{mu_pp(tag)} to have been thrown"\
"#{", not #{thrown}" if thrown}"
}
assert(false, msg)
end
assert(true)
ret
end
# :call-seq:
# assert_equal( expected, actual, failure_message = nil )
#
#Tests if +expected+ is equal to +actual+.
#
#An optional failure message may be provided as the final argument.
def assert_equal(exp, act, msg = nil)
msg = message(msg) {
exp_str = mu_pp(exp)
act_str = mu_pp(act)
exp_comment = ''
act_comment = ''
if exp_str == act_str
if (exp.is_a?(String) && act.is_a?(String)) ||
(exp.is_a?(Regexp) && act.is_a?(Regexp))
exp_comment = " (#{exp.encoding})"
act_comment = " (#{act.encoding})"
elsif exp.is_a?(Float) && act.is_a?(Float)
exp_str = "%\#.#{Float::DIG+2}g" % exp
act_str = "%\#.#{Float::DIG+2}g" % act
elsif exp.is_a?(Time) && act.is_a?(Time)
if exp.subsec * 1000_000_000 == exp.nsec
exp_comment = " (#{exp.nsec}[ns])"
else
exp_comment = " (subsec=#{exp.subsec})"
end
if act.subsec * 1000_000_000 == act.nsec
act_comment = " (#{act.nsec}[ns])"
else
act_comment = " (subsec=#{act.subsec})"
end
elsif exp.class != act.class
# a subclass of Range, for example.
exp_comment = " (#{exp.class})"
act_comment = " (#{act.class})"
end
elsif !Encoding.compatible?(exp_str, act_str)
if exp.is_a?(String) && act.is_a?(String)
exp_str = exp.dump
act_str = act.dump
exp_comment = " (#{exp.encoding})"
act_comment = " (#{act.encoding})"
else
exp_str = exp_str.dump
act_str = act_str.dump
end
end
"<#{exp_str}>#{exp_comment} expected but was\n<#{act_str}>#{act_comment}"
}
assert(exp == act, msg)
end
# :call-seq:
# assert_not_nil( expression, failure_message = nil )
#
#Tests if +expression+ is not nil.
#
#An optional failure message may be provided as the final argument.
def assert_not_nil(exp, msg=nil)
msg = message(msg) { "<#{mu_pp(exp)}> expected to not be nil" }
assert(!exp.nil?, msg)
end
# :call-seq:
# assert_not_equal( expected, actual, failure_message = nil )
#
#Tests if +expected+ is not equal to +actual+.
#
#An optional failure message may be provided as the final argument.
def assert_not_equal(exp, act, msg=nil)
msg = message(msg) { "<#{mu_pp(exp)}> expected to be != to\n<#{mu_pp(act)}>" }
assert(exp != act, msg)
end
# :call-seq:
# assert_no_match( regexp, string, failure_message = nil )
#
#Tests if the given Regexp does not match a given String.
#
#An optional failure message may be provided as the final argument.
def assert_no_match(regexp, string, msg=nil)
assert_instance_of(Regexp, regexp, "The first argument to assert_no_match should be a Regexp.")
self._assertions -= 1
msg = message(msg) { "<#{mu_pp(regexp)}> expected to not match\n<#{mu_pp(string)}>" }
assert(regexp !~ string, msg)
end
# :call-seq:
# assert_not_same( expected, actual, failure_message = nil )
#
#Tests if +expected+ is not the same object as +actual+.
#This test uses Object#equal? to test equality.
#
#An optional failure message may be provided as the final argument.
#
# assert_not_same("x", "x") #Succeeds
def assert_not_same(expected, actual, message="")
msg = message(msg) { build_message(message, <<EOT, expected, expected.__id__, actual, actual.__id__) }
<?>
with id <?> expected to not be equal\\? to
<?>
with id <?>.
EOT
assert(!actual.equal?(expected), msg)
end
# :call-seq:
# assert_respond_to( object, method, failure_message = nil )
#
#Tests if the given Object responds to +method+.
#
#An optional failure message may be provided as the final argument.
#
# assert_respond_to("hello", :reverse) #Succeeds
# assert_respond_to("hello", :does_not_exist) #Fails
def assert_respond_to obj, (meth, priv), msg = nil
if priv
msg = message(msg) {
"Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv}"
}
return assert obj.respond_to?(meth, priv), msg
end
#get rid of overcounting
super if !caller[0].rindex(MINI_DIR, 0) || !obj.respond_to?(meth)
end
# :call-seq:
# assert_send( +send_array+, failure_message = nil )
#
# Passes if the method send returns a true value.
#
# +send_array+ is composed of:
# * A receiver
# * A method
# * Arguments to the method
#
# Example:
# assert_send(["Hello world", :include?, "Hello"]) # -> pass
# assert_send(["Hello world", :include?, "Goodbye"]) # -> fail
def assert_send send_ary, m = nil
recv, msg, *args = send_ary
m = message(m) {
if args.empty?
argsstr = ""
else
(argsstr = mu_pp(args)).sub!(/\A\[(.*)\]\z/m, '(\1)')
end
"Expected #{mu_pp(recv)}.#{msg}#{argsstr} to return true"
}
assert recv.__send__(msg, *args), m
end
# :call-seq:
# assert_not_send( +send_array+, failure_message = nil )
#
# Passes if the method send doesn't return a true value.
#
# +send_array+ is composed of:
# * A receiver
# * A method
# * Arguments to the method
#
# Example:
# assert_not_send([[1, 2], :member?, 1]) # -> fail
# assert_not_send([[1, 2], :member?, 4]) # -> pass
def assert_not_send send_ary, m = nil
recv, msg, *args = send_ary
m = message(m) {
if args.empty?
argsstr = ""
else
(argsstr = mu_pp(args)).sub!(/\A\[(.*)\]\z/m, '(\1)')
end
"Expected #{mu_pp(recv)}.#{msg}#{argsstr} to return false"
}
assert !recv.__send__(msg, *args), m
end
ms = instance_methods(true).map {|sym| sym.to_s }
ms.grep(/\Arefute_/) do |m|
mname = ('assert_not_' << m.to_s[/.*?_(.*)/, 1])
alias_method(mname, m) unless ms.include? mname
end
alias assert_include assert_includes
alias assert_not_include assert_not_includes
def assert_all?(obj, m = nil, &blk)
failed = []
obj.each do |*a, &b|
unless blk.call(*a, &b)
failed << (a.size > 1 ? a : a[0])
end
end
assert(failed.empty?, message(m) {failed.pretty_inspect})
end
def assert_not_all?(obj, m = nil, &blk)
failed = []
obj.each do |*a, &b|
if blk.call(*a, &b)
failed << a.size > 1 ? a : a[0]
end
end
assert(failed.empty?, message(m) {failed.pretty_inspect})
end
def build_message(head, template=nil, *arguments) #:nodoc:
template &&= template.chomp
template.gsub(/\G((?:[^\\]|\\.)*?)(\\)?\?/) { $1 + ($2 ? "?" : mu_pp(arguments.shift)) }
end
def message(msg = nil, *args, &default) # :nodoc:
if Proc === msg
super(nil, *args) do
[msg.call, (default.call if default)].compact.reject(&:empty?).join(".\n")
end
else
super
end
end
end
end
end

View file

@ -0,0 +1,190 @@
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../.."
require 'test/unit'
module Test
module Unit
class Worker < Runner # :nodoc:
class << self
undef autorun
end
alias orig_run_suite mini_run_suite
undef _run_suite
undef _run_suites
undef run
def increment_io(orig) # :nodoc:
*rest, io = 32.times.inject([orig.dup]){|ios, | ios << ios.last.dup }
rest.each(&:close)
io
end
def _run_suites(suites, type) # :nodoc:
suites.map do |suite|
_run_suite(suite, type)
end
end
def _run_suite(suite, type) # :nodoc:
@partial_report = []
orig_testout = MiniTest::Unit.output
i,o = IO.pipe
MiniTest::Unit.output = o
orig_stdin, orig_stdout = $stdin, $stdout
th = Thread.new do
begin
while buf = (self.verbose ? i.gets : i.read(5))
_report "p", buf
end
rescue IOError
rescue Errno::EPIPE
end
end
e, f, s = @errors, @failures, @skips
begin
result = orig_run_suite(suite, type)
rescue Interrupt
@need_exit = true
result = [nil,nil]
end
MiniTest::Unit.output = orig_testout
$stdin = orig_stdin
$stdout = orig_stdout
o.close
begin
th.join
rescue IOError
raise unless ["stream closed","closed stream"].include? $!.message
end
i.close
result << @partial_report
@partial_report = nil
result << [@errors-e,@failures-f,@skips-s]
result << ($: - @old_loadpath)
result << suite.name
begin
_report "done", Marshal.dump(result)
rescue Errno::EPIPE; end
return result
ensure
MiniTest::Unit.output = orig_stdout
$stdin = orig_stdin
$stdout = orig_stdout
o.close if o && !o.closed?
i.close if i && !i.closed?
end
def run(args = []) # :nodoc:
process_args args
@@stop_auto_run = true
@opts = @options.dup
@need_exit = false
@old_loadpath = []
begin
begin
@stdout = increment_io(STDOUT)
@stdin = increment_io(STDIN)
rescue
exit 2
end
exit 2 unless @stdout && @stdin
@stdout.sync = true
_report "ready!"
while buf = @stdin.gets
case buf.chomp
when /^loadpath (.+?)$/
@old_loadpath = $:.dup
$:.push(*Marshal.load($1.unpack("m")[0].force_encoding("ASCII-8BIT"))).uniq!
when /^run (.+?) (.+?)$/
_report "okay"
@options = @opts.dup
suites = MiniTest::Unit::TestCase.test_suites
begin
require $1
rescue LoadError
_report "after", Marshal.dump([$1, ProxyError.new($!)])
_report "ready"
next
end
_run_suites MiniTest::Unit::TestCase.test_suites-suites, $2.to_sym
if @need_exit
begin
_report "bye"
rescue Errno::EPIPE; end
exit
else
_report "ready"
end
when /^quit$/
begin
_report "bye"
rescue Errno::EPIPE; end
exit
end
end
rescue Errno::EPIPE
rescue Exception => e
begin
trace = e.backtrace
err = ["#{trace.shift}: #{e.message} (#{e.class})"] + trace.map{|t| t.prepend("\t") }
_report "bye", Marshal.dump(err.join("\n"))
rescue Errno::EPIPE;end
exit
ensure
@stdin.close if @stdin
@stdout.close if @stdout
end
end
def _report(res, *args) # :nodoc:
res = "#{res} #{args.pack("m0")}" unless args.empty?
@stdout.puts(res)
end
def puke(klass, meth, e) # :nodoc:
if e.is_a?(MiniTest::Skip)
new_e = MiniTest::Skip.new(e.message)
new_e.set_backtrace(e.backtrace)
e = new_e
end
@partial_report << [klass.name, meth, e.is_a?(MiniTest::Assertion) ? e : ProxyError.new(e)]
super
end
end
end
end
if $0 == __FILE__
module Test
module Unit
class TestCase < MiniTest::Unit::TestCase # :nodoc: all
undef on_parallel_worker?
def on_parallel_worker?
true
end
end
end
end
require 'rubygems'
module Gem # :nodoc:
end
class Gem::TestCase < MiniTest::Unit::TestCase # :nodoc:
@@project_dir = File.expand_path('../../../..', __FILE__)
end
Test::Unit::Worker.new.run(ARGV)
end

View file

@ -0,0 +1,18 @@
# -*- ruby -*-
Gem::Specification.new do |s|
s.name = "test-unit"
s.version = "#{RUBY_VERSION}.0"
s.homepage = "http://www.ruby-lang.org"
s.author = "Shota Fukumori"
s.email = "sorah@tubusu.net"
s.summary = "test/unit compatible API testing framework"
s.description =
"This library implements test/unit compatible API on minitest. " +
"The test/unit means that test/unit which was bundled with Ruby 1.8."
s.executables = ["testrb"]
# Ruby bundled test/unit is a compatibility layer for minitest,
# and it doesn't have support for minitest 5.
s.add_runtime_dependency "minitest", '< 5.0.0'
end

View file

@ -0,0 +1,34 @@
require 'test/unit/assertions'
module Test
module Unit
# remove silly TestCase class
remove_const(:TestCase) if defined?(self::TestCase)
class TestCase < MiniTest::Unit::TestCase # :nodoc: all
include Assertions
def on_parallel_worker?
false
end
def run runner
@options = runner.options
super runner
end
def self.test_order
:sorted
end
def self.method_added(name)
return unless name.to_s.start_with?("test_")
@test_methods ||= {}
if @test_methods[name]
warn "test/unit warning: method #{ self }##{ name } is redefined"
end
@test_methods[name] = true
end
end
end
end

View file

@ -1,9 +1,11 @@
require 'rbconfig'
require 'test/unit'
src_testdir = File.dirname(File.realpath(__FILE__))
$LOAD_PATH << src_testdir
$LOAD_PATH.unshift "#{src_testdir}/lib"
require 'test/unit'
module Gem
end
class Gem::TestCase < MiniTest::Unit::TestCase

View file

@ -1,3 +1,5 @@
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
require 'test/unit'
class TestForTestHideSkip < Test::Unit::TestCase

View file

@ -1,3 +1,5 @@
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
require 'test/unit'
class TestForTestRedefinition < Test::Unit::TestCase

View file

@ -1,3 +1,5 @@
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
require 'test/unit'
class TestForTestHideSkip < Test::Unit::TestCase

View file

@ -2,10 +2,9 @@ require 'test/unit'
require 'timeout'
module TestParallel
PARALLEL_RB = "#{File.dirname(__FILE__)}/../../lib/test/unit/parallel.rb"
PARALLEL_RB = "#{File.dirname(__FILE__)}/../lib/test/unit/parallel.rb"
TESTS = "#{File.dirname(__FILE__)}/tests_for_parallel"
class TestParallelWorker < Test::Unit::TestCase
def setup
i, @worker_in = IO.pipe

View file

@ -18,6 +18,7 @@ class RakeIntegration < MiniTest::Unit::TestCase
filename = File.join dir, 'testing.rb'
File.open(filename, 'wb') do |f|
f.write <<-eotest
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
require 'test/unit'
raise 'loaded twice' if defined?(FooTest)
class FooTest; end

View file

@ -1,4 +1,7 @@
require 'rbconfig'
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../lib"
require 'test/unit'
src_testdir = File.dirname(File.expand_path(__FILE__))