Permalink
Browse files

Imported minitest from 1.9.3-p0

  • Loading branch information...
1 parent f123952 commit 0c3394ea6da6696fc4f09a6896245e3b4e094e65 @jfirebaugh jfirebaugh committed Jan 7, 2012
Showing with 1,681 additions and 261 deletions.
  1. +269 −0 lib/19/minitest/README.txt
  2. +12 −5 lib/19/minitest/autorun.rb
  3. +372 −0 lib/19/minitest/benchmark.rb
  4. +83 −15 lib/19/minitest/mock.rb
  5. +99 −0 lib/19/minitest/pride.rb
  6. +353 −126 lib/19/minitest/spec.rb
  7. +493 −115 lib/19/minitest/unit.rb
View
269 lib/19/minitest/README.txt
@@ -0,0 +1,269 @@
+= minitest/*
+
+* http://rubyforge.org/projects/bfts
+
+== DESCRIPTION:
+
+minitest provides a complete suite of testing facilities supporting
+TDD, BDD, mocking, and benchmarking.
+
+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 object
+framework.
+
+minitest/pride shows pride in testing and adds coloring to your test
+output.
+
+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.
+
+== 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 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
+ 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
+
+=== 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
+ n.times do
+ @obj.my_algorithm
+ end
+ 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
+
+=== 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
+
+== 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 ...
+
+== 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
17 lib/19/minitest/autorun.rb
@@ -1,8 +1,15 @@
-############################################################
-# This file is imported from a different project.
-# DO NOT make modifications in this repo.
-# File a patch instead and assign it to Ryan Davis
-############################################################
+######################################################################
+# 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'
View
372 lib/19/minitest/benchmark.rb
@@ -0,0 +1,372 @@
+######################################################################
+# 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
+ attr_accessor :runner
+
+ 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 |x|
+ # @obj.algorithm
+ # 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 |x|
+ # @obj.algorithm
+ # 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 |x|
+ # @obj.algorithm
+ # 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 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 |x|
+ # @obj.algorithm
+ # 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
+
+ ##
+ # 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
+
+ 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
+ # @obj.fast_algorithm
+ # 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
+ # @obj.zoom_algorithm!
+ # 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
+ # @obj.algorithm
+ # end
+ # end
+
+ def self.bench_performance_exponential name, threshold = 0.99, &work
+ bench name do
+ assert_performance_exponential threshold, &work
+ end
+ end
+end
View
98 lib/19/minitest/mock.rb
@@ -1,38 +1,106 @@
-############################################################
-# This file is imported from a different project.
-# DO NOT make modifications in this repo.
-# File a patch instead and assign it to Ryan Davis
-############################################################
+######################################################################
+# 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
+##
+# A simple and clean mock object framework.
+
module MiniTest
+
+ ##
+ # All mock objects are an instance of Mock
+
class Mock
- def initialize
+ 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 = {}
@actual_calls = Hash.new {|h,k| h[k] = [] }
end
+ ##
+ # Expect that method +name+ is called, optionally with +args+, 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
+ #
+ # +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=[])
- n, r, a = name, retval, args # for the closure below
@expected_calls[name] = { :retval => retval, :args => args }
- self.class.__send__ :remove_method, name if respond_to? name
- self.class.__send__(:define_method, name) { |*x|
- raise ArgumentError unless @expected_calls[n][:args].size == x.size
- @actual_calls[n] << { :retval => r, :args => x }
- retval
- }
self
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_key do |name|
expected = @expected_calls[name]
- msg = "expected #{name}, #{expected.inspect}"
- raise MockExpectationError, msg unless
+ msg1 = "expected #{name}, #{expected.inspect}"
+ msg2 = "#{msg1}, got #{@actual_calls[name].inspect}"
+
+ 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
true
end
+
+ def method_missing(sym, *args) # :nodoc:
+ expected = @expected_calls[sym]
+
+ unless expected then
+ raise NoMethodError, "unmocked method %p, expected one of %p" %
+ [sym, @expected_calls.keys.sort_by(&:to_s)]
+ end
+
+ expected_args, retval = expected[:args], expected[:retval]
+
+ unless expected_args.size == args.size
+ raise ArgumentError, "mocked method %p expects %d arguments, got %d" %
+ [sym, expected[:args].size, args.size]
+ end
+
+ @actual_calls[sym] << {
+ :retval => retval,
+ :args => expected_args.zip(args).map { |mod, a| mod if mod === a }
+ }
+
+ retval
+ end
+
+ def respond_to?(sym) # :nodoc:
+ return true if @expected_calls.has_key?(sym)
+ return __respond_to?(sym)
+ end
end
end
View
99 lib/19/minitest/pride.rb
@@ -0,0 +1,99 @@
+######################################################################
+# 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
+ ESC = "\e["
+ NND = "#{ESC}0m"
+
+ attr_reader :io
+
+ def initialize io
+ @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
+
+ 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)
+ o.map! { |s|
+ s.sub(/Finished tests/) {
+ @index = 0
+ 'Fabulous tests'.split(//).map { |c|
+ pride(c)
+ }.join
+ }
+ }
+
+ super
+ end
+
+ def pride string
+ string = "*" if string == "."
+ c = @colors[@index % @size]
+ @index += 1
+ "#{ESC}#{c}m#{string}#{NND}"
+ end
+
+ def method_missing msg, *args
+ io.send(msg, *args)
+ end
+end
+
+class PrideLOL < PrideIO # inspired by lolcat, but massively cleaned up
+ PI_3 = Math::PI / 3
+
+ def initialize io
+ # 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
+
+ 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)
View
479 lib/19/minitest/spec.rb
@@ -1,15 +1,15 @@
-############################################################
-# This file is imported from a different project.
-# DO NOT make modifications in this repo.
-# File a patch instead and assign it to Ryan Davis
-############################################################
+######################################################################
+# 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
- def infect_an_assertion meth, new_name, dont_flip = false
+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, &block
@@ -22,82 +22,118 @@ def #{new_name} *args, &block
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 = {})
- MiniTest::Assertions.public_instance_methods(false).each do |meth|
- meth = meth.to_s
-
- new_name = case meth
- when /^assert/ then
- meth.sub(/^assert/, pos_prefix.to_s)
- when /^refute/ then
- meth.sub(/^refute/, neg_prefix.to_s)
- end
- next unless new_name
- next if new_name =~ skip_re
-
- regexp, replacement = map.find { |re, _| new_name =~ re }
- new_name.sub! regexp, replacement if replacement
-
- infect_an_assertion meth, new_name, new_name =~ dont_flip_re
- end
+ abort "infect_with_assertions is dead. Use infect_an_assertion directly"
end
end
-Object.infect_with_assertions(:must, :wont,
- /^(must|wont)$|wont_(throw)|
- must_(block|not?_|nothing|raise$)/x,
- /(must|wont)_(include|respond_to)/,
- /(must_throw)s/ => '\1',
- /(?!not)_same/ => '_be_same_as',
- /_in_/ => '_be_within_',
- /_operator/ => '_be',
- /_includes/ => '_include',
- /(must|wont)_(.*_of|nil|empty)/ => '\1_be_\2',
- /must_raises/ => 'must_raise')
-
-class Object
- alias :must_be_close_to :must_be_within_delta
- alias :wont_be_close_to :wont_be_within_delta
-end
-
-module Kernel
+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::Unit::TestCase or from the surrounding describe's class.
-
- def describe desc, &block
+ # 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 = desc.to_s.split(/\W+/).map { |s| s.capitalize }.join + "Spec"
- prev = stack.last
- name = "#{prev == MiniTest::Spec ? nil : prev}::#{name}"
- cls = Object.class_eval "class #{name} < #{prev}; end; #{name}"
+ name = [stack.last, desc, additional_desc].compact.join("::")
+ sclas = stack.last || if Class === self && self < MiniTest::Spec then
+ self
+ else
+ MiniTest::Spec.spec_type desc
+ end
- cls.nuke_test_methods!
+ cls = sclas.create name, desc
stack.push cls
cls.class_eval(&block)
stack.pop
+ cls
end
private :describe
end
-class Module
- def classes type = Object # :nodoc:
- constants.map { |n| const_get n }.find_all { |c|
- c.class == Class and type > c
- } - [self]
- end
-end
+##
+# MiniTest::Spec -- The faster, better, less-magical spec framework!
+#
+# For a list of expectations, see MiniTest::Expectations.
class MiniTest::Spec < MiniTest::Unit::TestCase
- @@describe_stack = [MiniTest::Spec]
+ ##
+ # 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 self.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 self.spec_type desc
+ TYPES.find { |matcher, klass|
+ if matcher.respond_to? :call then
+ matcher.call desc
+ else
+ matcher === desc.to_s
+ end
+ }.last
+ end
+
+ @@describe_stack = []
def self.describe_stack # :nodoc:
@@describe_stack
end
@@ -106,6 +142,13 @@ def self.current # :nodoc:
@@current_spec
end
+ ##
+ # Returns the children of this spec.
+
+ def self.children
+ @children ||= []
+ end
+
def initialize name # :nodoc:
super
@@current_spec = self
@@ -117,15 +160,6 @@ def self.nuke_test_methods! # :nodoc:
end
end
- def self.define_inheritable_method name, &block # :nodoc:
- super_method = self.superclass.instance_method name
-
- define_method name do
- super_method.bind(self).call if super_method # regular super() warns
- instance_eval(&block)
- end
- end
-
##
# Define a 'before' action. Inherits the way normal methods should.
#
@@ -135,7 +169,8 @@ def self.define_inheritable_method name, &block # :nodoc:
def self.before type = :each, &block
raise "unsupported before type: #{type}" unless type == :each
- define_inheritable_method :setup, &block
+
+ add_setup_hook {|tc| tc.instance_eval(&block) }
end
##
@@ -147,7 +182,8 @@ def self.before type = :each, &block
def self.after type = :each, &block
raise "unsupported after type: #{type}" unless type == :each
- define_inheritable_method :teardown, &block
+
+ add_teardown_hook {|tc| tc.instance_eval(&block) }
end
##
@@ -169,124 +205,315 @@ def self.it desc, &block
define_method name, &block
- classes(MiniTest::Spec).each do |mod|
- mod.send :undef_method, name if mod.respond_to? name
+ self.children.each do |mod|
+ mod.send :undef_method, name if mod.public_method_defined? name
+ end
+ end
+
+ def self.let name, &block
+ define_method name do
+ @_memoized ||= {}
+ @_memoized.fetch(name) { |k| @_memoized[k] = instance_eval(&block) }
end
end
+ def self.subject &block
+ let :subject, &block
+ end
+
+ def self.create name, desc # :nodoc:
+ cls = Class.new(self) do
+ @name = name
+ @desc = desc
+
+ nuke_test_methods!
+ end
+
+ children << cls
+
+ cls
+ end
+
+ def self.to_s # :nodoc:
+ defined?(@name) ? @name : super
+ end
+
+ # :stopdoc:
+ class << self
+ attr_reader :name, :desc
+ end
+ # :startdoc:
+end
+
+module MiniTest::Expectations
##
- # :method: must_be
- # See MiniTest::Assertions#assert
+ # See MiniTest::Assertions#assert_empty.
+ #
+ # collection.must_be_empty
+ #
+ # :method: must_be_empty
+
+ infect_an_assertion :assert_empty, :must_be_empty
+
+ ##
+ # See MiniTest::Assertions#assert_equal
+ #
+ # a.must_equal b
+ #
+ # :method: must_equal
+
+ infect_an_assertion :assert_equal, :must_equal
##
- # :method: must_be_close_to
# See MiniTest::Assertions#assert_in_delta
+ #
+ # n.must_be_close_to m [, delta]
+ #
+ # :method: must_be_within_delta
+
+ infect_an_assertion :assert_in_delta, :must_be_close_to
+
+ alias :must_be_within_delta :must_be_close_to
##
- # :method: must_be_empty
- # See MiniTest::Assertions#assert_empty
+ # 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
##
- # :method: must_be_instance_of
# 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
##
- # :method: must_be_kind_of
# See MiniTest::Assertions#assert_kind_of
+ #
+ # obj.must_be_kind_of mod
+ #
+ # :method: must_be_kind_of
- ##
- # :method: must_be_nil
- # See MiniTest::Assertions#assert_nil
+ infect_an_assertion :assert_kind_of, :must_be_kind_of
##
- # :method: must_be_same_as
- # See MiniTest::Assertions#assert_same
+ # See MiniTest::Assertions#assert_match
+ #
+ # a.must_match b
+ #
+ # :method: must_match
- ##
- # :method: must_be_within_delta
- # See MiniTest::Assertions#assert_in_delta
+ infect_an_assertion :assert_match, :must_match
##
- # :method: must_be_within_epsilon
- # See MiniTest::Assertions#assert_in_epsilon
+ # See MiniTest::Assertions#assert_nil
+ #
+ # obj.must_be_nil
+ #
+ # :method: must_be_nil
- ##
- # :method: must_equal
- # See MiniTest::Assertions#assert_equal
+ infect_an_assertion :assert_nil, :must_be_nil
##
- # :method: must_include
- # See MiniTest::Assertions#assert_includes
+ # See MiniTest::Assertions#assert_operator
+ #
+ # n.must_be :<=, 42
+ #
+ # :method: must_be
+
+ infect_an_assertion :assert_operator, :must_be
##
- # :method: must_match
- # See MiniTest::Assertions#assert_match
+ # See MiniTest::Assertions#assert_output
+ #
+ # proc { ... }.must_output out_or_nil [, err]
+ #
+ # :method: must_output
+
+ infect_an_assertion :assert_output, :must_output
##
- # :method: must_raise
# See MiniTest::Assertions#assert_raises
+ #
+ # proc { ... }.must_raise exception
+ #
+ # :method: must_raise
+
+ infect_an_assertion :assert_raises, :must_raise
##
- # :method: must_respond_to
# 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
##
- # :method: must_send
- # See MiniTest::Assertions#assert_send
+ # 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
##
- # :method: must_throw
- # See MiniTest::Assertions#assert_throw
+ # See MiniTest::Assertions#assert_send
+ # TODO: remove me
+ #
+ # a.must_send
+ #
+ # :method: must_send
+
+ infect_an_assertion :assert_send, :must_send
##
- # :method: wont_be
- # See MiniTest::Assertions#refute
+ # See MiniTest::Assertions#assert_silent
+ #
+ # proc { ... }.must_be_silent
+ #
+ # :method: must_be_silent
+
+ infect_an_assertion :assert_silent, :must_be_silent
##
- # :method: wont_be_close_to
- # See MiniTest::Assertions#refute_in_delta
+ # See MiniTest::Assertions#assert_throws
+ #
+ # proc { ... }.must_throw sym
+ #
+ # :method: must_throw
+
+ infect_an_assertion :assert_throws, :must_throw
##
- # :method: wont_be_empty
# See MiniTest::Assertions#refute_empty
+ #
+ # collection.wont_be_empty
+ #
+ # :method: wont_be_empty
+
+ infect_an_assertion :refute_empty, :wont_be_empty
##
- # :method: wont_be_instance_of
- # See MiniTest::Assertions#refute_instance_of
+ # See MiniTest::Assertions#refute_equal
+ #
+ # a.wont_equal b
+ #
+ # :method: wont_equal
+
+ infect_an_assertion :refute_equal, :wont_equal
##
- # :method: wont_be_kind_of
- # See MiniTest::Assertions#refute_kind_of
+ # See MiniTest::Assertions#refute_in_delta
+ #
+ # n.wont_be_close_to m [, delta]
+ #
+ # :method: wont_be_within_delta
+
+ infect_an_assertion :refute_in_delta, :wont_be_within_delta
+
+ alias :wont_be_close_to :wont_be_within_delta
+ # FIX: reverse aliases
##
- # :method: wont_be_nil
- # See MiniTest::Assertions#refute_nil
+ # 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
##
- # :method: wont_be_same_as
- # See MiniTest::Assertions#refute_same
+ # See MiniTest::Assertions#refute_includes
+ #
+ # collection.wont_include obj
+ #
+ # :method: wont_include
+
+ infect_an_assertion :refute_includes, :wont_include, :reverse
##
- # :method: wont_be_within_delta
- # See MiniTest::Assertions#refute_in_delta
+ # 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
##
- # :method: wont_be_within_epsilon
- # See MiniTest::Assertions#refute_in_epsilon
+ # 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
##
- # :method: wont_equal
- # See MiniTest::Assertions#refute_equal
+ # See MiniTest::Assertions#refute_match
+ #
+ # a.wont_match b
+ #
+ # :method: wont_match
+
+ infect_an_assertion :refute_match, :wont_match
##
- # :method: wont_include
- # See MiniTest::Assertions#refute_includes
+ # See MiniTest::Assertions#refute_nil
+ #
+ # obj.wont_be_nil
+ #
+ # :method: wont_be_nil
+
+ infect_an_assertion :refute_nil, :wont_be_nil
##
- # :method: wont_match
- # See MiniTest::Assertions#refute_match
+ # See MiniTest::Assertions#refute_operator
+ #
+ # n.wont_be :<=, 42
+ #
+ # :method: wont_be
+
+ infect_an_assertion :refute_operator, :wont_be
##
- # :method: wont_respond_to
# 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
+ include MiniTest::Expectations
end
View
608 lib/19/minitest/unit.rb
@@ -1,13 +1,16 @@
-############################################################
-# This file is imported from a different project.
-# DO NOT make modifications in this repo.
-# File a patch instead and assign it to Ryan Davis
-############################################################
+######################################################################
+# 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 'optparse'
+require 'rbconfig'
##
# Minimal (mostly drop-in) replacement for test-unit.
+#
+# :include: README.txt
module MiniTest
@@ -27,7 +30,8 @@ class Skip < Assertion; end
require 'pathname'
pwd = Pathname.new Dir.pwd
pn = Pathname.new File.expand_path(__FILE__)
- pn = File.join(".", pn.relative_path_from(pwd)) unless pn.relative?
+ relpath = pn.relative_path_from(pwd) rescue pn
+ pn = File.join ".", relpath unless pn.relative?
pn.to_s
else # assume both are expanded
__FILE__
@@ -40,13 +44,19 @@ def self.filter_backtrace bt # :nodoc:
return ["No backtrace"] unless bt
new_bt = []
- bt.each do |line|
- break if line.rindex(MINI_DIR, 0)
- new_bt << line
+
+ unless $DEBUG then
+ bt.each do |line|
+ break if line.rindex MINI_DIR, 0
+ new_bt << line
+ end
+
+ new_bt = bt.reject { |line| line.rindex MINI_DIR, 0 } if new_bt.empty?
+ new_bt = bt.dup if new_bt.empty?
+ else
+ new_bt = bt.dup
end
- new_bt = bt.reject { |line| line.rindex(MINI_DIR, 0) } if new_bt.empty?
- new_bt = bt.dup if new_bt.empty?
new_bt
end
@@ -56,16 +66,106 @@ def self.filter_backtrace bt # :nodoc:
module Assertions
+ WINDOZE = RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
+
+ ##
+ # Returns the diff command to use in #diff. Tries to intelligently
+ # figure out what diff to use.
+
+ def self.diff
+ @diff = if WINDOZE
+ "diff.exe -u"
+ else
+ if system("gdiff", __FILE__, __FILE__)
+ "gdiff -u" # solaris and kin suck
+ elsif system("diff", __FILE__, __FILE__)
+ "diff -u"
+ else
+ nil
+ end
+ end unless defined? @diff
+
+ @diff
+ end
+
##
- # mu_pp gives a human-readable version of +obj+. By default #inspect is
- # called. You can override this to use #pretty_print if you want.
+ # Set the diff command to use in #diff.
+
+ def self.diff= o
+ @diff = o
+ end
+
+ ##
+ # Returns a diff between +exp+ and +act+. If there is no known
+ # diff command or if it doesn't make sense to diff the output
+ # (single line, short output), then it simply returns a basic
+ # comparison between the two.
+
+ def diff exp, act
+ require "tempfile"
+
+ expect = mu_pp_for_diff exp
+ butwas = mu_pp_for_diff act
+ result = nil
+
+ need_to_diff =
+ MiniTest::Assertions.diff &&
+ (expect.include?("\n") ||
+ butwas.include?("\n") ||
+ expect.size > 30 ||
+ butwas.size > 30 ||
+ expect == butwas)
+
+ return "Expected: #{mu_pp exp}\n Actual: #{mu_pp act}" unless
+ need_to_diff
+
+ Tempfile.open("expect") do |a|
+ a.puts expect
+ a.flush
+
+ Tempfile.open("butwas") do |b|
+ b.puts butwas
+ b.flush
+
+ result = `#{MiniTest::Assertions.diff} #{a.path} #{b.path}`
+ result.sub!(/^\-\-\- .+/, "--- expected")
+ result.sub!(/^\+\+\+ .+/, "+++ actual")
+
+ if result.empty? then
+ klass = exp.class
+ result = [
+ "No visible difference.",
+ "You should look at your implementation of #{klass}#==.",
+ expect
+ ].join "\n"
+ end
+ end
+ end
+
+ result
+ end
+
+ ##
+ # This returns a human-readable version of +obj+. By default
+ # #inspect is called. You can override this to use #pretty_print
+ # if you want.
def mu_pp obj
s = obj.inspect
- s = s.force_encoding(Encoding.default_external) if defined? Encoding
+ s = s.encode Encoding.default_external if defined? Encoding
s
end
+ ##
+ # This returns a diff-able human-readable version of +obj+. This
+ # differs from the regular mu_pp because it expands escaped
+ # newlines and makes hex-values generic (like object_ids). This
+ # uses mu_pp to do the first pass and then cleans it up.
+
+ def mu_pp_for_diff obj # TODO: possibly rename
+ mu_pp(obj).gsub(/\\n/, "\n").gsub(/0x[a-f0-9]+/m, '0xXXXXXX')
+ end
+
def _assertions= n # :nodoc:
@_assertions = n
end
@@ -91,7 +191,8 @@ def assert test, msg = nil
# Fails unless the block returns a true value.
def assert_block msg = nil
- assert yield, "Expected block to return true value."
+ msg = message(msg) { "Expected block to return true value" }
+ assert yield, msg
end
##
@@ -104,12 +205,19 @@ def assert_empty obj, msg = nil
end
##
- # Fails unless <tt>exp == act</tt>.
+ # Fails unless <tt>exp == act</tt> printing the difference between
+ # the two, if possible.
+ #
+ # If there is no visible difference but the assertion fails, you
+ # should suspect that your #== is buggy, or your inspect output is
+ # missing crucial details.
#
- # For floats use assert_in_delta
+ # For floats use assert_in_delta.
+ #
+ # See also: MiniTest::Assertions.diff
def assert_equal exp, act, msg = nil
- msg = message(msg) { "Expected #{mu_pp(exp)}, not #{mu_pp(act)}" }
+ msg = message(msg, "") { diff exp, act }
assert(exp == act, msg)
end
@@ -171,7 +279,7 @@ def assert_kind_of cls, obj, msg = nil # TODO: merge with instance_of
def assert_match exp, act, msg = nil
msg = message(msg) { "Expected #{mu_pp(exp)} to match #{mu_pp(act)}" }
assert_respond_to act, :"=~"
- exp = /#{Regexp.escape exp}/ if String === exp && String === act
+ exp = Regexp.new Regexp.escape exp if String === exp and String === act
assert exp =~ act, msg
end
@@ -194,11 +302,29 @@ def assert_operator o1, op, o2, msg = nil
end
##
+ # Fails if stdout or stderr do not output the expected results.
+ # Pass in nil if you don't care about that streams output. Pass in
+ # "" if you require it to be silent.
+ #
+ # See also: #assert_silent
+
+ def assert_output stdout = nil, stderr = nil
+ out, err = capture_io do
+ yield
+ end
+
+ x = assert_equal stdout, out, "In stdout" if stdout
+ y = assert_equal stderr, err, "In stderr" if stderr
+
+ (!stdout || x) && (!stderr || y)
+ end
+
+ ##
# Fails unless the block raises one of +exp+
def assert_raises *exp
- msg = String === exp.last ? exp.pop : nil
- msg = msg.to_s + "\n" if msg
+ msg = "#{exp.pop}\n" if String === exp.last
+
should_raise = false
begin
yield
@@ -230,8 +356,8 @@ def assert_raises *exp
def assert_respond_to obj, meth, msg = nil
msg = message(msg) {
- "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}"
- }
+ "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}"
+ }
assert obj.respond_to?(meth), msg
end
@@ -260,6 +386,17 @@ def assert_send send_ary, m = nil
end
##
+ # Fails if the block outputs anything to stderr or stdout.
+ #
+ # See also: #assert_output
+
+ def assert_silent
+ assert_output "", "" do
+ yield
+ end
+ end
+
+ ##
# Fails unless the block throws +sym+
def assert_throws sym, msg = nil
@@ -307,7 +444,14 @@ def capture_io
# Returns details for exception +e+
def exception_details e, msg
- "#{msg}\nClass: <#{e.class}>\nMessage: <#{e.message.inspect}>\n---Backtrace---\n#{MiniTest::filter_backtrace(e.backtrace).join("\n")}\n---------------"
+ [
+ "#{msg}",
+ "Class: <#{e.class}>",
+ "Message: <#{e.message.inspect}>",
+ "---Backtrace---",
+ "#{MiniTest::filter_backtrace(e.backtrace).join("\n")}",
+ "---------------",
+ ].join "\n"
end
##
@@ -321,16 +465,10 @@ def flunk msg = nil
##
# Returns a proc that will output +msg+ along with the default message.
- def message msg = nil, &default
+ def message msg = nil, ending = ".", &default
proc {
- if msg then
- msg = msg.to_s unless String === msg
- msg += '.' unless msg.empty?
- msg += "\n#{default.call}."
- msg.strip
- else
- "#{default.call}."
- end
+ custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty?
+ "#{custom_message}#{default.call}#{ending}"
}
end
@@ -353,7 +491,7 @@ def refute test, msg = nil
# Fails if +obj+ is empty.
def refute_empty obj, msg = nil
- msg = message(msg) { "Expected #{obj.inspect} to not be empty" }
+ msg = message(msg) { "Expected #{mu_pp(obj)} to not be empty" }
assert_respond_to obj, :empty?
refute obj.empty?, msg
end
@@ -439,7 +577,7 @@ def refute_nil obj, msg = nil
end
##
- # Fails if +o1+ is not +op+ +o2+ nil. eg:
+ # Fails if +o1+ is not +op+ +o2+. Eg:
#
# refute_operator 1, :>, 2 #=> pass
# refute_operator 1, :<, 2 #=> fail
@@ -482,16 +620,33 @@ def skip msg = nil, bt = caller
end
class Unit
- VERSION = "1.6.0" # :nodoc:
+ VERSION = "2.5.1" # :nodoc:
attr_accessor :report, :failures, :errors, :skips # :nodoc:
attr_accessor :test_count, :assertion_count # :nodoc:
attr_accessor :start_time # :nodoc:
+ attr_accessor :help # :nodoc:
+ attr_accessor :verbose # :nodoc:
+ attr_writer :options # :nodoc:
+
+ def options
+ @options ||= {}
+ end
@@installed_at_exit ||= false
@@out = $stdout
##
+ # A simple hook allowing you to run a block of code after the
+ # tests are done. Eg:
+ #
+ # MiniTest::Unit.after_tests { p $debugging_info }
+
+ def self.after_tests
+ at_exit { at_exit { yield } }
+ end
+
+ ##
# Registers MiniTest::Unit to run tests at process exit
def self.autorun
@@ -505,19 +660,143 @@ def self.autorun
exit_code = nil
at_exit { exit false if exit_code && exit_code != 0 }
- exit_code = MiniTest::Unit.new.run(ARGV)
+
+ exit_code = MiniTest::Unit.new.run ARGV
} unless @@installed_at_exit
@@installed_at_exit = true
end
##
+ # Returns the stream to use for output.
+
+ def self.output
+ @@out
+ end
+
+ ##
+ # Returns the stream to use for output.
+ #
+ # DEPRECATED: use ::output instead.
+
+ def self.out
+ warn "::out deprecated, use ::output instead." if $VERBOSE
+ output
+ end
+
+ ##
# Sets MiniTest::Unit to write output to +stream+. $stdout is the default
# output
def self.output= stream
@@out = stream
end
+ ##
+ # Tells MiniTest::Unit to delegate to +runner+, an instance of a
+ # MiniTest::Unit subclass, when MiniTest::Unit#run is called.
+
+ def self.runner= runner
+ @@runner = runner
+ end
+
+ ##
+ # Returns the MiniTest::Unit subclass instance that will be used
+ # to run the tests. A MiniTest::Unit instance is the default
+ # runner.
+
+ def self.runner
+ @@runner ||= self.new
+ end
+
+ ##
+ # Return all plugins' run methods (methods that start with "run_").
+
+ def self.plugins
+ @@plugins ||= (["run_tests"] +
+ public_instance_methods(false).
+ grep(/^run_/).map { |s| s.to_s }).uniq
+ end
+
+ def output
+ self.class.output
+ end
+
+ def puts *a # :nodoc:
+ output.puts(*a)
+ end
+
+ def print *a # :nodoc:
+ output.print(*a)
+ end
+
+ def _run_anything type
+ suites = TestCase.send "#{type}_suites"
+ return if suites.empty?
+
+ start = Time.now
+
+ puts
+ puts "# Running #{type}s:"
+ puts
+
+ @test_count, @assertion_count = 0, 0
+ sync = output.respond_to? :"sync=" # stupid emacs
+ old_sync, output.sync = output.sync, true if sync
+
+ results = _run_suites suites, type
+
+ @test_count = results.inject(0) { |sum, (tc, _)| sum + tc }
+ @assertion_count = results.inject(0) { |sum, (_, ac)| sum + ac }
+
+ output.sync = old_sync if sync
+
+ t = Time.now - start
+
+ puts
+ puts
+ puts "Finished #{type}s in %.6fs, %.4f tests/s, %.4f assertions/s." %
+ [t, test_count / t, assertion_count / t]
+
+ report.each_with_index do |msg, i|
+ puts "\n%3d) %s" % [i + 1, msg]
+ end
+
+ puts
+
+ status
+ end
+
+ def _run_suites suites, type
+ suites.map { |suite| _run_suite suite, type }
+ end
+
+ def _run_suite suite, type
+ header = "#{type}_suite_header"
+ puts send(header, suite) if respond_to? header
+
+ filter = options[:filter] || '/./'
+ filter = Regexp.new $1 if filter =~ /\/(.*)\//
+
+ assertions = suite.send("#{type}_methods").grep(filter).map { |method|
+ inst = suite.new method
+ inst._assertions = 0
+
+ print "#{suite}##{method} = " if @verbose
+
+ @start_time = Time.now
+ result = inst.run self
+ time = Time.now - @start_time
+
+ print "%.2f s = " % time if @verbose
+ print result
+ puts if @verbose
+
+ inst._assertions
+ }
+
+ return assertions.size, assertions.inject(0) { |sum, n| sum + n }
+ end
+
def location e # :nodoc:
last_before_assertion = ""
e.backtrace.reverse_each do |s|
@@ -535,13 +814,14 @@ def puke klass, meth, e
e = case e
when MiniTest::Skip then
@skips += 1
+ return "S" unless @verbose
"Skipped:\n#{meth}(#{klass}) [#{location e}]:\n#{e.message}\n"
when MiniTest::Assertion then
@failures += 1
"Failure:\n#{meth}(#{klass}) [#{location e}]:\n#{e.message}\n"
else
@errors += 1
- bt = MiniTest::filter_backtrace(e.backtrace).join("\n ")
+ bt = MiniTest::filter_backtrace(e.backtrace).join "\n "
"Error:\n#{meth}(#{klass}):\n#{e.class}: #{e.message}\n #{bt}\n"
end
@report << e
@@ -556,6 +836,7 @@ def initialize # :nodoc:
def process_args args = []
options = {}
+ orig_args = args.dup
OptionParser.new do |opts|
opts.banner = 'minitest options:'
@@ -578,97 +859,69 @@ def process_args args = []
options[:filter] = a
end
- opts.parse args
+ opts.parse! args
+ orig_args -= args
end
- options
- end
-
- ##
- # Top level driver, controls all output and filtering.
-
- def run args = []
- options = process_args args
-
- @verbose = options[:verbose]
-
- filter = options[:filter] || '/./'
- filter = Regexp.new $1 if filter and filter =~ /\/(.*)\//
-
- seed = options[:seed]
- unless seed then
+ unless options[:seed] then
srand
- seed = srand % 0xFFFF
+ options[:seed] = srand % 0xFFFF
+ orig_args << "--seed" << options[:seed].to_s
end
- srand seed
-
- @@out.puts "Loaded suite #{$0.sub(/\.rb$/, '')}\nStarted"
+ srand options[:seed]
- start = Time.now
- run_test_suites filter
+ self.verbose = options[:verbose]
+ @help = orig_args.map { |s| s =~ /[\s|&<>$()]/ ? s.inspect : s }.join " "
- @@out.puts
- @@out.puts "Finished in #{'%.6f' % (Time.now - start)} seconds."
+ options
+ end
- @report.each_with_index do |msg, i|
- @@out.puts "\n%3d) %s" % [i + 1, msg]
- end
+ ##
+ # Begins the full test run. Delegates to +runner+'s #_run method.
- @@out.puts
+ def run args = []
+ self.class.runner._run(args)
+ end
- status
+ ##
+ # Top level driver, controls all output and filtering.
- @@out.puts
+ def _run args = []
+ self.options = process_args args
- help = ["--seed", seed]
- help.push "--verbose" if @verbose
- help.push("--name", options[:filter].inspect) if options[:filter]
+ puts "Run options: #{help}"
- @@out.puts "Test run options: #{help.join(" ")}"
+ self.class.plugins.each do |plugin|
+ send plugin
+ break unless report.empty?
+ end
return failures + errors if @test_count > 0 # or return nil...
rescue Interrupt
abort 'Interrupted'
end
##
- # Writes status to +io+
+ # Runs test suites matching +filter+.
- def status io = @@out
- format = "%d tests, %d assertions, %d failures, %d errors, %d skips"
- io.puts format % [test_count, assertion_count, failures, errors, skips]
+ def run_tests
+ _run_anything :test
end
##
- # Runs test suites matching +filter+
+ # Writes status to +io+
- def run_test_suites filter = /./
- @test_count, @assertion_count = 0, 0
- old_sync, @@out.sync = @@out.sync, true if @@out.respond_to? :sync=
- TestCase.test_suites.each do |suite|
- suite.test_methods.grep(filter).each do |test|
- inst = suite.new test
- inst._assertions = 0
- @@out.print "#{suite}##{test}: " if @verbose
-
- @start_time = Time.now
- result = inst.run(self)
-
- @@out.print "%.2f s: " % (Time.now - @start_time) if @verbose
- @@out.print result
- @@out.puts if @verbose
- @test_count += 1
- @assertion_count += inst._assertions
- end
- end
- @@out.sync = old_sync if @@out.respond_to? :sync=
- [@test_count, @assertion_count]
+ def status io = self.output
+ format = "%d tests, %d assertions, %d failures, %d errors, %d skips"
+ io.puts format % [test_count, assertion_count, failures, errors, skips]
end
##
- # Subclass TestCase to create your own tests. Typically you'll want a
+ # Subclass TestCase to create your own tests. Typically you'll want a
# TestCase subclass per implementation class.
+ #
+ # See MiniTest::Assertions
class TestCase
attr_reader :__name__ # :nodoc:
@@ -682,30 +935,33 @@ class TestCase
# Runs the tests reporting the status to +runner+
def run runner
- trap 'INFO' do
- warn '%s#%s %.2fs' % [self.class, self.__name__,
- (Time.now - runner.start_time)]
+ trap "INFO" do
+ time = runner.start_time ? Time.now - runner.start_time : 0
+ warn "%s#%s %.2fs" % [self.class, self.__name__, time]
runner.status $stderr
end if SUPPORTS_INFO_SIGNAL
- result = '.'
+ result = ""
begin
@passed = nil
self.setup
+ self.run_setup_hooks
self.__send__ self.__name__
+ result = "." unless io?
@passed = true
rescue *PASSTHROUGH_EXCEPTIONS
raise
rescue Exception => e
@passed = false
- result = runner.puke(self.class, self.__name__, e)
+ result = runner.puke self.class, self.__name__, e
ensure
begin
+ self.run_teardown_hooks
self.teardown
rescue *PASSTHROUGH_EXCEPTIONS
raise
rescue Exception => e
- result = runner.puke(self.class, self.__name__, e)
+ result = runner.puke self.class, self.__name__, e
end
trap 'INFO', 'DEFAULT' if SUPPORTS_INFO_SIGNAL
end
@@ -714,30 +970,48 @@ def run runner
def initialize name # :nodoc:
@__name__ = name
+ @__io__ = nil
@passed = nil
end
+ def io
+ @__io__ = true
+ MiniTest::Unit.output
+ end
+
+ def io?
+ @__io__
+ end
+
def self.reset # :nodoc:
@@test_suites = {}
end
reset
+ ##
+ # Call this at the top of your tests when you absolutely
+ # positively need to have ordered tests. In doing so, you're
+ # admitting that you suck and your tests are weak.
+
+ def self.i_suck_and_my_tests_are_order_dependent!
+ class << self
+ define_method :test_order do :alpha end
+ end
+ end
+
def self.inherited klass # :nodoc:
@@test_suites[klass] = true
+ klass.reset_setup_teardown_hooks
+ super
end
- ##
- # Defines test order and is subclassable. Defaults to :random
- # but can be overridden to return :alpha if your tests are order
- # dependent (read: weak).
-
- def self.test_order
+ def self.test_order # :nodoc:
:random
end
def self.test_suites # :nodoc:
- @@test_suites.keys.sort_by { |ts| ts.name }
+ @@test_suites.keys.sort_by { |ts| ts.name.to_s }
end
def self.test_methods # :nodoc:
@@ -746,7 +1020,7 @@ def self.test_methods # :nodoc:
case self.test_order
when :random then
max = methods.size
- methods.sort.sort_by { rand(max) }
+ methods.sort.sort_by { rand max }
when :alpha, :sorted then
methods.sort
else
@@ -771,6 +1045,111 @@ def setup; end
def teardown; end
+ def self.reset_setup_teardown_hooks # :nodoc:
+ @setup_hooks = []
+ @teardown_hooks = []
+ end
+
+ reset_setup_teardown_hooks
+
+ ##
+ # Adds a block of code that will be executed before every TestCase is
+ # run. Equivalent to +setup+, but usable multiple times and without
+ # re-opening any classes.
+ #
+ # All of the setup hooks will run in order after the +setup+ method, if
+ # one is defined.
+ #
+ # The argument can be any object that responds to #call or a block.
+ # That means that this call,
+ #
+ # MiniTest::TestCase.add_setup_hook { puts "foo" }
+ #
+ # ... is equivalent to:
+ #
+ # module MyTestSetup
+ # def call
+ # puts "foo"
+ # end
+ # end
+ #
+ # MiniTest::TestCase.add_setup_hook MyTestSetup
+ #
+ # The blocks passed to +add_setup_hook+ take an optional parameter that
+ # will be the TestCase instance that is executing the block.
+
+ def self.add_setup_hook arg=nil, &block
+ hook = arg || block
+ @setup_hooks << hook
+ end
+
+ def self.setup_hooks # :nodoc:
+ if superclass.respond_to? :setup_hooks then
+ superclass.setup_hooks
+ else
+ []
+ end + @setup_hooks
+ end
+
+ def run_setup_hooks # :nodoc:
+ self.class.setup_hooks.each do |hook|
+ if hook.respond_to?(:arity) && hook.arity == 1
+ hook.call(self)
+ else
+ hook.call
+ end
+ end
+ end
+
+ ##
+ # Adds a block of code that will be executed after every TestCase is
+ # run. Equivalent to +teardown+, but usable multiple times and without
+ # re-opening any classes.
+ #
+ # All of the teardown hooks will run in reverse order after the
+ # +teardown+ method, if one is defined.
+ #
+ # The argument can be any object that responds to #call or a block.
+ # That means that this call,
+ #
+ # MiniTest::TestCase.add_teardown_hook { puts "foo" }
+ #
+ # ... is equivalent to:
+ #
+ # module MyTestTeardown
+ # def call
+ # puts "foo"
+ # end
+ # end
+ #
+ # MiniTest::TestCase.add_teardown_hook MyTestTeardown
+ #
+ # The blocks passed to +add_teardown_hook+ take an optional parameter
+ # that will be the TestCase instance that is executing the block.
+
+ def self.add_teardown_hook arg=nil, &block
+ hook = arg || block
+ @teardown_hooks << hook
+ end
+
+ def self.teardown_hooks # :nodoc:
+ if superclass.respond_to? :teardown_hooks then
+ superclass.teardown_hooks
+ else
+ []
+ end + @teardown_hooks
+ end
+
+ def run_teardown_hooks # :nodoc:
+ self.class.teardown_hooks.reverse.each do |hook|
+ if hook.respond_to?(:arity) && hook.arity == 1
+ hook.call(self)
+ else
+ hook.call
+ end
+ end
+ end
+
include MiniTest::Assertions
end # class TestCase
end # class Unit
@@ -788,4 +1167,3 @@ def self.inherited x # :nodoc:
end
end
end
-

0 comments on commit 0c3394e

Please sign in to comment.