From 2c43b9664b29f76c73ac8bae5400b79d0a5313e0 Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 23 Aug 2011 21:47:25 +0000 Subject: Imported minitest 2.5.0 (r6557) git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@33036 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 5 + lib/minitest/README.txt | 270 +++++++++++++++++++++++++ lib/minitest/mock.rb | 59 +++++- lib/minitest/pride.rb | 76 ++++++- lib/minitest/spec.rb | 393 +++++++++++++++++++++++------------- lib/minitest/unit.rb | 135 ++++++++++++- test/minitest/test_minitest_mock.rb | 62 +++++- test/minitest/test_minitest_spec.rb | 60 +++++- test/minitest/test_minitest_unit.rb | 151 +++++++++++++- 9 files changed, 1024 insertions(+), 187 deletions(-) create mode 100644 lib/minitest/README.txt diff --git a/ChangeLog b/ChangeLog index 2cc9782097..3c048a9ee5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +Wed Aug 24 06:45:20 2011 Ryan Davis + + * lib/minitest/*: Imported minitest 2.5.0 (r6557) + * test/minitest/*: ditto + Wed Aug 24 00:38:22 2011 Yusuke Endoh * thread.c (update_coverage): skip coverage count up if the current diff --git a/lib/minitest/README.txt b/lib/minitest/README.txt new file mode 100644 index 0000000000..0595083d49 --- /dev/null +++ b/lib/minitest/README.txt @@ -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. diff --git a/lib/minitest/mock.rb b/lib/minitest/mock.rb index 1cbf18b19f..c342c04995 100644 --- a/lib/minitest/mock.rb +++ b/lib/minitest/mock.rb @@ -15,20 +15,39 @@ module MiniTest # 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 @@ module MiniTest 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 diff --git a/lib/minitest/pride.rb b/lib/minitest/pride.rb index 9a4d68859a..ac7745695c 100644 --- a/lib/minitest/pride.rb +++ b/lib/minitest/pride.rb @@ -10,32 +10,90 @@ require "minitest/unit" # Show your testing pride! class PrideIO - attr_reader :io + ESC = "\e[" + NND = "#{ESC}0m" - # stolen from /System/Library/Perl/5.10.0/Term/ANSIColor.pm - COLORS = (31..36).to_a - CHARS = ["*"] + attr_reader :io 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) diff --git a/lib/minitest/spec.rb b/lib/minitest/spec.rb index 4b16cd03ec..7b414e324a 100644 --- a/lib/minitest/spec.rb +++ b/lib/minitest/spec.rb @@ -8,7 +8,7 @@ require 'minitest/unit' -class Module +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 @@ -23,39 +23,18 @@ class Module end ## - # Create your own expectations from MiniTest::Assertions using a - # flexible set of rules. If you don't like must/wont, then this - # method is your friend. For an example of its usage see the bottom - # of minitest/spec.rb. + # 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).sort.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 - - puts "\n##\n# :method: #{new_name}\n# See MiniTest::Assertions##{meth}" if - $0 == __FILE__ - - 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 -module Kernel +module Kernel # :nodoc: ## # Describe a series of expectations for a given target +desc+. # @@ -80,25 +59,16 @@ module Kernel # end # end - def describe desc, &block # :doc: + def describe desc, additional_desc = nil, &block # :doc: stack = MiniTest::Spec.describe_stack - name = [stack.last, desc].compact.join("::") + 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 = Class.new sclas - - sclas.children << cls unless cls == MiniTest::Spec - # :stopdoc: - # omg this sucks - (class << cls; self; end).send(:define_method, :to_s) { name } - (class << cls; self; end).send(:define_method, :desc) { desc } - # :startdoc: - - cls.nuke_test_methods! + cls = sclas.create name, desc stack.push cls cls.class_eval(&block) @@ -111,7 +81,7 @@ end ## # MiniTest::Spec -- The faster, better, less-magical spec framework! # -# For a list of expectations, see Object. +# For a list of expectations, see MiniTest::Expectations. class MiniTest::Spec < MiniTest::Unit::TestCase ## @@ -151,6 +121,9 @@ class MiniTest::Spec < MiniTest::Unit::TestCase @@current_spec end + ## + # Returns the children of this spec. + def self.children @children ||= [] end @@ -166,25 +139,6 @@ class MiniTest::Spec < MiniTest::Unit::TestCase end end - ## - # Spec users want setup/teardown to be inherited and NOTHING ELSE. - # It is almost like method reuse is lost on them. - - def self.define_inheritable_method name, &block # :nodoc: - # regular super() warns - super_method = self.superclass.instance_method name - - teardown = name.to_s == "teardown" - super_before = super_method && ! teardown - super_after = super_method && teardown - - define_method name do - super_method.bind(self).call if super_before - instance_eval(&block) - super_method.bind(self).call if super_after - end - end - ## # Define a 'before' action. Inherits the way normal methods should. # @@ -194,7 +148,8 @@ class MiniTest::Spec < MiniTest::Unit::TestCase 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 ## @@ -206,7 +161,8 @@ class MiniTest::Spec < MiniTest::Unit::TestCase 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 ## @@ -232,154 +188,311 @@ class MiniTest::Spec < MiniTest::Unit::TestCase mod.send :undef_method, name if mod.public_method_defined? name end 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|silent|empty)/ => '\1_be_\2', - /must_raises/ => 'must_raise') + def self.let name, &block + define_method name do + @_memoized ||= {} + @_memoized.fetch(name) { |k| @_memoized[k] = instance_eval(&block) } + end + end -class Object - alias :must_be_close_to :must_be_within_delta - alias :wont_be_close_to :wont_be_within_delta + def self.subject &block + let :subject, &block + end + + def self.create name, desc # :nodoc: + cls = Class.new(self) do + @name = name + @desc = desc - if $0 == __FILE__ then - { "must" => "assert", "wont" => "refute" }.each do |a, b| - puts "\n" - puts "##" - puts "# :method: #{a}_be_close_to" - puts "# See MiniTest::Assertions##{b}_in_delta" + nuke_test_methods! end + + children << cls + + cls end - ## - # :method: must_be - # See MiniTest::Assertions#assert_operator + def self.to_s # :nodoc: + defined?(@name) ? @name : super + end - ## - # :method: must_be_close_to - # See MiniTest::Assertions#assert_in_delta + # :stopdoc: + class << self + attr_reader :name, :desc + end + # :startdoc: +end +module MiniTest::Expectations ## + # See MiniTest::Assertions#assert_empty. + # + # collection.must_be_empty + # # :method: must_be_empty - # See MiniTest::Assertions#assert_empty - ## - # :method: must_be_instance_of - # See MiniTest::Assertions#assert_instance_of + infect_an_assertion :assert_empty, :must_be_empty ## - # :method: must_be_kind_of - # See MiniTest::Assertions#assert_kind_of + # See MiniTest::Assertions#assert_equal + # + # a.must_equal b + # + # :method: must_equal + + infect_an_assertion :assert_equal, :must_equal ## - # :method: must_be_nil - # See MiniTest::Assertions#assert_nil + # 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_same_as - # See MiniTest::Assertions#assert_same + # 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 ## - # :method: must_be_silent - # See MiniTest::Assertions#assert_silent + # See MiniTest::Assertions#assert_includes + # + # collection.must_include obj + # + # :method: must_include + + infect_an_assertion :assert_includes, :must_include, :reverse ## - # :method: must_be_within_delta - # See MiniTest::Assertions#assert_in_delta + # 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_within_epsilon - # See MiniTest::Assertions#assert_in_epsilon + # 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 ## - # :method: must_equal - # See MiniTest::Assertions#assert_equal + # See MiniTest::Assertions#assert_match + # + # a.must_match b + # + # :method: must_match + + infect_an_assertion :assert_match, :must_match ## - # :method: must_include - # See MiniTest::Assertions#assert_includes + # See MiniTest::Assertions#assert_nil + # + # obj.must_be_nil + # + # :method: must_be_nil + + infect_an_assertion :assert_nil, :must_be_nil ## - # :method: must_match - # See MiniTest::Assertions#assert_match + # See MiniTest::Assertions#assert_operator + # + # n.must_be :<=, 42 + # + # :method: must_be + + infect_an_assertion :assert_operator, :must_be ## - # :method: must_output # 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_throws + # 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_operator + # 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 diff --git a/lib/minitest/unit.rb b/lib/minitest/unit.rb index 199360e644..7fda1a0e22 100644 --- a/lib/minitest/unit.rb +++ b/lib/minitest/unit.rb @@ -152,7 +152,7 @@ module MiniTest 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 @@ module MiniTest # 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 @@ module MiniTest 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 @@ module MiniTest 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 @@ module MiniTest begin @passed = nil self.setup + self.run_setup_hooks self.__send__ self.__name__ result = "." unless io? @passed = true @@ -955,6 +956,7 @@ module MiniTest result = runner.puke self.class, self.__name__, e ensure begin + self.run_teardown_hooks self.teardown rescue *PASSTHROUGH_EXCEPTIONS raise @@ -987,16 +989,24 @@ module MiniTest 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 @@ module MiniTest 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 diff --git a/test/minitest/test_minitest_mock.rb b/test/minitest/test_minitest_mock.rb index 311f5fa7fb..b6e801c3aa 100644 --- a/test/minitest/test_minitest_mock.rb +++ b/test/minitest/test_minitest_mock.rb @@ -49,20 +49,34 @@ class TestMiniTestMock < MiniTest::Unit::TestCase util_verify_bad end - def test_not_verify_if_unexpected_method_is_called - assert_raises NoMethodError do - @mock.unexpected - end - end - def test_blow_up_on_wrong_number_of_arguments @mock.foo @mock.meaning_of_life @mock.expect(:sum, 3, [1, 2]) - assert_raises ArgumentError do + e = assert_raises ArgumentError do @mock.sum end + + assert_equal "mocked method :sum expects 2 arguments, got 0", e.message + end + + def test_return_mock_does_not_raise + retval = MiniTest::Mock.new + mock = MiniTest::Mock.new + mock.expect(:foo, retval) + mock.foo + + assert mock.verify + end + + def test_mock_args_does_not_raise + arg = MiniTest::Mock.new + mock = MiniTest::Mock.new + mock.expect(:foo, nil, [arg]) + mock.foo(arg) + + assert mock.verify end def test_blow_up_on_wrong_arguments @@ -81,18 +95,22 @@ class TestMiniTestMock < MiniTest::Unit::TestCase end def test_no_method_error_on_unexpected_methods - assert_raises NoMethodError do + e = assert_raises NoMethodError do @mock.bar end + + expected = "unmocked method :bar, expected one of [:foo, :meaning_of_life]" + + assert_equal expected, e.message end def test_assign_per_mock_return_values a = MiniTest::Mock.new b = MiniTest::Mock.new - + a.expect(:foo, :a) b.expect(:foo, :b) - + assert_equal :a, a.foo assert_equal :b, b.foo end @@ -104,6 +122,30 @@ class TestMiniTestMock < MiniTest::Unit::TestCase assert !MiniTest::Mock.new.respond_to?(:foo) end + def test_mock_is_a_blank_slate + @mock.expect :kind_of?, true, [Fixnum] + @mock.expect :==, true, [1] + + assert @mock.kind_of?(Fixnum), "didn't mock :kind_of\?" + assert @mock == 1, "didn't mock :==" + end + + def test_verify_allows_called_args_to_be_loosely_specified + mock = MiniTest::Mock.new + mock.expect :loose_expectation, true, [Integer] + mock.loose_expectation 1 + + assert mock.verify + end + + def test_verify_raises_with_strict_args + mock = MiniTest::Mock.new + mock.expect :strict_expectation, true, [2] + mock.strict_expectation 1 + + util_verify_bad + end + def util_verify_bad assert_raises MockExpectationError do @mock.verify diff --git a/test/minitest/test_minitest_spec.rb b/test/minitest/test_minitest_spec.rb index 942156abdb..26dd4ec46a 100644 --- a/test/minitest/test_minitest_spec.rb +++ b/test/minitest/test_minitest_spec.rb @@ -4,11 +4,9 @@ # File a patch instead and assign it to Ryan Davis. ###################################################################### -require 'minitest/spec' +require 'minitest/autorun' require 'stringio' -MiniTest::Unit.autorun - describe MiniTest::Spec do before do @assertion_count = 4 @@ -202,6 +200,53 @@ describe MiniTest::Spec do end end +describe MiniTest::Spec, :let do + i_suck_and_my_tests_are_order_dependent! + + def _count + $let_count ||= 0 + end + + let :count do + $let_count += 1 + $let_count + end + + it "is evaluated once per example" do + _count.must_equal 0 + + count.must_equal 1 + count.must_equal 1 + + _count.must_equal 1 + end + + it "is REALLY evaluated once per example" do + _count.must_equal 1 + + count.must_equal 2 + count.must_equal 2 + + _count.must_equal 2 + end +end + +describe MiniTest::Spec, :subject do + attr_reader :subject_evaluation_count + + subject do + @subject_evaluation_count ||= 0 + @subject_evaluation_count += 1 + @subject_evaluation_count + end + + it "is evaluated once per example" do + subject.must_equal 1 + subject.must_equal 1 + subject_evaluation_count.must_equal 1 + end +end + class TestMeta < MiniTest::Unit::TestCase def test_setup srand 42 @@ -245,8 +290,8 @@ class TestMeta < MiniTest::Unit::TestCase assert_equal "inner thingy", y.desc assert_equal "very inner thingy", z.desc - top_methods = %w(setup teardown test_0001_top_level_it) - inner_methods = %w(setup teardown test_0001_inner_it) + top_methods = %w(test_0001_top_level_it) + inner_methods = %w(test_0001_inner_it) assert_equal top_methods, x.instance_methods(false).sort.map {|o| o.to_s } assert_equal inner_methods, y.instance_methods(false).sort.map {|o| o.to_s } @@ -257,8 +302,9 @@ class TestMeta < MiniTest::Unit::TestCase _, _, z, before_list, after_list = util_structure tc = z.new(nil) - tc.setup - tc.teardown + + tc.run_setup_hooks + tc.run_teardown_hooks assert_equal [1, 2, 3], before_list assert_equal [3, 2, 1], after_list diff --git a/test/minitest/test_minitest_unit.rb b/test/minitest/test_minitest_unit.rb index b56fb0dc18..c921ca749e 100644 --- a/test/minitest/test_minitest_unit.rb +++ b/test/minitest/test_minitest_unit.rb @@ -12,6 +12,7 @@ MiniTest::Unit.autorun module MyModule; end class AnError < StandardError; include MyModule; end +class ImmutableString < String; def inspect; super.freeze; end; end class TestMiniTestUnit < MiniTest::Unit::TestCase pwd = Pathname.new(File.expand_path(Dir.pwd)) @@ -46,12 +47,12 @@ Finished tests in 0.00 MiniTest::Unit::TestCase.reset @tu = MiniTest::Unit.new @output = StringIO.new("") + MiniTest::Unit.runner = nil # protect the outer runner from the inner tests MiniTest::Unit.output = @output end def teardown MiniTest::Unit.output = $stdout - MiniTest::Unit.runner = nil Object.send :remove_const, :ATestCase if defined? ATestCase end @@ -442,6 +443,149 @@ Finished tests in 0.00 assert_report expected end + def with_overridden_include + Class.class_eval do + def inherited_with_hacks klass + throw :inherited_hook + end + + alias inherited_without_hacks inherited + alias inherited inherited_with_hacks + alias IGNORE_ME! inherited # 1.8 bug. god I love venture bros + end + + yield + + ensure + Class.class_eval do + alias inherited inherited_without_hacks + + undef_method :inherited_with_hacks + undef_method :inherited_without_hacks + end + + refute_respond_to Class, :inherited_with_hacks + refute_respond_to Class, :inherited_without_hacks + end + + def test_inherited_hook_plays_nice_with_others + with_overridden_include do + assert_throws :inherited_hook do + Class.new MiniTest::Unit::TestCase + end + end + end + + def test_setup_hooks + call_order = [] + + tc = Class.new(MiniTest::Unit::TestCase) do + define_method :setup do + super() + call_order << :method + end + + define_method :test2 do + call_order << :test2 + end + + define_method :test1 do + call_order << :test1 + end + end + + tc.add_setup_hook lambda { call_order << :proc } + + argument = nil + + tc.add_setup_hook do |arg| + argument = arg + call_order << :block + end + + @tu.run %w[--seed 42] + + assert_kind_of tc, argument + + expected = [:method, :proc, :block, :test1, + :method, :proc, :block, :test2] + + assert_equal expected, call_order + end + + def test_teardown_hooks + call_order = [] + + tc = Class.new(MiniTest::Unit::TestCase) do + define_method :teardown do + super() + call_order << :method + end + + define_method :test2 do + call_order << :test2 + end + + define_method :test1 do + call_order << :test1 + end + end + + tc.add_teardown_hook lambda { call_order << :proc } + + argument = nil + + tc.add_teardown_hook do |arg| + argument = arg + call_order << :block + end + + @tu.run %w[--seed 42] + + assert_kind_of tc, argument + + expected = [:test1, :block, :proc, :method, + :test2, :block, :proc, :method] + + assert_equal expected, call_order + end + + def test_setup_and_teardown_hooks_survive_inheritance + call_order = [] + + parent = Class.new(MiniTest::Unit::TestCase) do + define_method :setup do + super() + call_order << :setup_method + end + + define_method :teardown do + super() + call_order << :teardown_method + end + + define_method :test_something do + call_order << :test + end + end + + parent.add_setup_hook { call_order << :setup_hook } + parent.add_teardown_hook { call_order << :teardown_hook } + + _ = Class.new parent + + parent.add_setup_hook { call_order << :setup_after } + parent.add_teardown_hook { call_order << :teardown_after } + + @tu.run %w[--seed 42] + + # Once for the parent class, once for the child + expected = [:setup_method, :setup_hook, :setup_after, :test, + :teardown_after, :teardown_hook, :teardown_method] * 2 + + assert_equal expected, call_order + end + def util_expand_bt bt if RUBY_VERSION =~ /^1\.9/ then bt.map { |f| (f =~ /^\./) ? File.expand_path(f) : f } @@ -1084,6 +1228,11 @@ FILE:LINE:in `test_assert_raises_triggered_subclass' @tc.pass end + def test_prints + printer = Class.new { extend MiniTest::Assertions } + @tc.assert_equal '"test"', printer.mu_pp(ImmutableString.new 'test') + end + def test_refute @assertion_count = 2 -- cgit v1.2.3