ruby-changes:20987
From: ryan <ko1@a...>
Date: Wed, 24 Aug 2011 06:47:47 +0900 (JST)
Subject: [ruby-changes:20987] ryan:r33036 (trunk): Imported minitest 2.5.0 (r6557)
ryan 2011-08-24 06:47:25 +0900 (Wed, 24 Aug 2011) New Revision: 33036 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=33036 Log: Imported minitest 2.5.0 (r6557) Added files: trunk/lib/minitest/README.txt Modified files: trunk/ChangeLog trunk/lib/minitest/mock.rb trunk/lib/minitest/pride.rb trunk/lib/minitest/spec.rb trunk/lib/minitest/unit.rb trunk/test/minitest/test_minitest_mock.rb trunk/test/minitest/test_minitest_spec.rb trunk/test/minitest/test_minitest_unit.rb Index: ChangeLog =================================================================== --- ChangeLog (revision 33035) +++ ChangeLog (revision 33036) @@ -1,3 +1,8 @@ +Wed Aug 24 06:45:20 2011 Ryan Davis <ryand-ruby@z...> + + * lib/minitest/*: Imported minitest 2.5.0 (r6557) + * test/minitest/*: ditto + Wed Aug 24 00:38:22 2011 Yusuke Endoh <mame@t...> * thread.c (update_coverage): skip coverage count up if the current Index: lib/minitest/unit.rb =================================================================== --- lib/minitest/unit.rb (revision 33035) +++ lib/minitest/unit.rb (revision 33036) @@ -152,7 +152,7 @@ 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 @@ -491,7 +491,7 @@ # 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 @@ -577,7 +577,7 @@ 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 @@ -620,7 +620,7 @@ end class Unit - VERSION = "2.2.2" # :nodoc: + VERSION = "2.5.0" # :nodoc: attr_accessor :report, :failures, :errors, :skips # :nodoc: attr_accessor :test_count, :assertion_count # :nodoc: @@ -945,6 +945,7 @@ begin @passed = nil self.setup + self.run_setup_hooks self.__send__ self.__name__ result = "." unless io? @passed = true @@ -955,6 +956,7 @@ result = runner.puke self.class, self.__name__, e ensure begin + self.run_teardown_hooks self.teardown rescue *PASSTHROUGH_EXCEPTIONS raise @@ -987,16 +989,24 @@ 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 @@ -1035,6 +1045,111 @@ 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 Index: lib/minitest/pride.rb =================================================================== --- lib/minitest/pride.rb (revision 33035) +++ lib/minitest/pride.rb (revision 33036) @@ -10,32 +10,90 @@ # Show your testing pride! class PrideIO + ESC = "\e[" + NND = "#{ESC}0m" + attr_reader :io - # stolen from /System/Library/Perl/5.10.0/Term/ANSIColor.pm - COLORS = (31..36).to_a - CHARS = ["*"] - def initialize io @io = io - @colors = COLORS.cycle - @chars = CHARS.cycle + # 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 "\e[#{@colors.next}m#{@chars.next}\e[0m" + io.print pride o when "E", "F" then - io.print "\e[41m\e[37m#{o}\e[0m" + 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 -MiniTest::Unit.output = PrideIO.new(MiniTest::Unit.output) +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) Index: lib/minitest/README.txt =================================================================== --- lib/minitest/README.txt (revision 0) +++ lib/minitest/README.txt (revision 33036) @@ -0,0 +1,270 @@ += minitest/{unit,spec,mock,benchmark} + +home :: https://github.com/seattlerb/minitest +rdoc :: http://bfts.rubyforge.org/minitest + +== 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. 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. + +== 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. Index: lib/minitest/mock.rb =================================================================== --- lib/minitest/mock.rb (revision 33035) +++ lib/minitest/mock.rb (revision 33036) @@ -15,20 +15,39 @@ # 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 = {} @actual_calls = Hash.new {|h,k| h[k] = [] } end ## - # Expect that method +name+ is called, optionally with +args+, and - # returns +retval+. + # 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=[]) @expected_calls[name] = { :retval => retval, :args => args } @@ -43,25 +62,45 @@ 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: - raise NoMethodError unless @expected_calls.has_key?(sym) - raise ArgumentError unless @expected_calls[sym][:args].size == args.size - retval = @expected_calls[sym][:retval] - @actual_calls[sym] << { :retval => retval, :args => args } + 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 - alias :original_respond_to? :respond_to? def respond_to?(sym) # :nodoc: return true if @expected_calls.has_key?(sym) - return original_respond_to?(sym) + return __respond_to?(sym) end end end Index: lib/minitest/spec.rb =================================================================== --- lib/minitest/spec.rb (revision 33035) +++ lib/minitest/spec.rb (revision 33036) @@ -8,7 +8,7 @@ require 'minitest/unit' -class Module +class Module # :nodoc: def infect_an_asserti (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/