From f8c6a5dc02439bb99c8403602288c2937b9356cc Mon Sep 17 00:00:00 2001 From: hsbt Date: Sat, 17 May 2014 06:26:51 +0000 Subject: * test/runner.rb: remove dependency test-unit and minitest from stdlib when running with test-all. [Feature #9711][ruby-core:61890] * test/testunit/*.rb: ditto. * test/lib: ditto. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@45970 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- test/lib/test/unit/assertions.rb | 461 +++++++++++++++++++++++++++++++++++ test/lib/test/unit/parallel.rb | 190 +++++++++++++++ test/lib/test/unit/test-unit.gemspec | 18 ++ test/lib/test/unit/testcase.rb | 34 +++ 4 files changed, 703 insertions(+) create mode 100644 test/lib/test/unit/assertions.rb create mode 100644 test/lib/test/unit/parallel.rb create mode 100644 test/lib/test/unit/test-unit.gemspec create mode 100644 test/lib/test/unit/testcase.rb (limited to 'test/lib/test/unit') diff --git a/test/lib/test/unit/assertions.rb b/test/lib/test/unit/assertions.rb new file mode 100644 index 0000000000..869baf505f --- /dev/null +++ b/test/lib/test/unit/assertions.rb @@ -0,0 +1,461 @@ +require 'minitest/unit' +require 'pp' + +module Test + module Unit + module Assertions + include MiniTest::Assertions + + def mu_pp(obj) #:nodoc: + obj.pretty_inspect.chomp + end + + MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc: + + # :call-seq: + # assert(test, [failure_message]) + # + #Tests if +test+ is true. + # + #+msg+ may be a String or a Proc. If +msg+ is a String, it will be used + #as the failure message. Otherwise, the result of calling +msg+ will be + #used as the message if the assertion fails. + # + #If no +msg+ is given, a default message will be used. + # + # assert(false, "This was expected to be true") + def assert(test, *msgs) + case msg = msgs.first + when String, Proc + when nil + msgs.shift + else + bt = caller.reject { |s| s.start_with?(MINI_DIR) } + raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt + end unless msgs.empty? + super + end + + # :call-seq: + # assert_block( failure_message = nil ) + # + #Tests the result of the given block. If the block does not return true, + #the assertion will fail. The optional +failure_message+ argument is the same as in + #Assertions#assert. + # + # assert_block do + # [1, 2, 3].any? { |num| num < 1 } + # end + def assert_block(*msgs) + assert yield, *msgs + end + + # :call-seq: + # assert_raise( *args, &block ) + # + #Tests if the given block raises an exception. Acceptable exception + #types may be given as optional arguments. If the last argument is a + #String, it will be used as the error message. + # + # assert_raise do #Fails, no Exceptions are raised + # end + # + # assert_raise NameError do + # puts x #Raises NameError, so assertion succeeds + # end + def assert_raise(*exp, &b) + case exp.last + when String, Proc + msg = exp.pop + end + + begin + yield + rescue MiniTest::Skip => e + return e if exp.include? MiniTest::Skip + raise e + rescue Exception => e + expected = exp.any? { |ex| + if ex.instance_of? Module then + e.kind_of? ex + else + e.instance_of? ex + end + } + + assert expected, proc { + exception_details(e, message(msg) {"#{mu_pp(exp)} exception expected, not"}.call) + } + + return e + end + + exp = exp.first if exp.size == 1 + + flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"}) + end + + # :call-seq: + # assert_raise_with_message(exception, expected, msg = nil, &block) + # + #Tests if the given block raises an exception with the expected + #message. + # + # assert_raise_with_message(RuntimeError, "foo") do + # nil #Fails, no Exceptions are raised + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise ArgumentError, "foo" #Fails, different Exception is raised + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise "bar" #Fails, RuntimeError is raised but the message differs + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise "foo" #Raises RuntimeError with the message, so assertion succeeds + # end + def assert_raise_with_message(exception, expected, msg = nil, &block) + case expected + when String + assert = :assert_equal + when Regexp + assert = :assert_match + else + raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}" + end + + ex = assert_raise(exception, *msg) {yield} + msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} + + if assert == :assert_equal + assert_equal(expected, ex.message, msg) + else + msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp ex.message}" } + assert expected =~ ex.message, msg + block.binding.eval("proc{|_|$~=_}").call($~) + end + ex + end + + # :call-seq: + # assert_nothing_raised( *args, &block ) + # + #If any exceptions are given as arguments, the assertion will + #fail if one of those exceptions are raised. Otherwise, the test fails + #if any exceptions are raised. + # + #The final argument may be a failure message. + # + # assert_nothing_raised RuntimeError do + # raise Exception #Assertion passes, Exception is not a RuntimeError + # end + # + # assert_nothing_raised do + # raise Exception #Assertion fails + # end + def assert_nothing_raised(*args) + self._assertions += 1 + if Module === args.last + msg = nil + else + msg = args.pop + end + begin + line = __LINE__; yield + rescue MiniTest::Skip + raise + rescue Exception => e + bt = e.backtrace + as = e.instance_of?(MiniTest::Assertion) + if as + ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o + bt.reject! {|ln| ans =~ ln} + end + if ((args.empty? && !as) || + args.any? {|a| a.instance_of?(Module) ? e.is_a?(a) : e.class == a }) + msg = message(msg) { "Exception raised:\n<#{mu_pp(e)}>" } + raise MiniTest::Assertion, msg.call, bt + else + raise + end + end + nil + end + + # :call-seq: + # assert_nothing_thrown( failure_message = nil, &block ) + # + #Fails if the given block uses a call to Kernel#throw, and + #returns the result of the block otherwise. + # + #An optional failure message may be provided as the final argument. + # + # assert_nothing_thrown "Something was thrown!" do + # throw :problem? + # end + def assert_nothing_thrown(msg=nil) + begin + ret = yield + rescue ArgumentError => error + raise error if /\Auncaught throw (.+)\z/m !~ error.message + msg = message(msg) { "<#{$1}> was thrown when nothing was expected" } + flunk(msg) + end + assert(true, "Expected nothing to be thrown") + ret + end + + # :call-seq: + # assert_throw( tag, failure_message = nil, &block ) + # + #Fails unless the given block throws +tag+, returns the caught + #value otherwise. + # + #An optional failure message may be provided as the final argument. + # + # tag = Object.new + # assert_throw(tag, "#{tag} was not thrown!") do + # throw tag + # end + def assert_throw(tag, msg = nil) + ret = catch(tag) do + begin + yield(tag) + rescue ArgumentError => e + raise unless thrown = e.message[/\Auncaught throw (.+)\z/m, 1] + end + msg = message(msg) { + "Expected #{mu_pp(tag)} to have been thrown"\ + "#{", not #{thrown}" if thrown}" + } + assert(false, msg) + end + assert(true) + ret + end + + # :call-seq: + # assert_equal( expected, actual, failure_message = nil ) + # + #Tests if +expected+ is equal to +actual+. + # + #An optional failure message may be provided as the final argument. + def assert_equal(exp, act, msg = nil) + msg = message(msg) { + exp_str = mu_pp(exp) + act_str = mu_pp(act) + exp_comment = '' + act_comment = '' + if exp_str == act_str + if (exp.is_a?(String) && act.is_a?(String)) || + (exp.is_a?(Regexp) && act.is_a?(Regexp)) + exp_comment = " (#{exp.encoding})" + act_comment = " (#{act.encoding})" + elsif exp.is_a?(Float) && act.is_a?(Float) + exp_str = "%\#.#{Float::DIG+2}g" % exp + act_str = "%\#.#{Float::DIG+2}g" % act + elsif exp.is_a?(Time) && act.is_a?(Time) + if exp.subsec * 1000_000_000 == exp.nsec + exp_comment = " (#{exp.nsec}[ns])" + else + exp_comment = " (subsec=#{exp.subsec})" + end + if act.subsec * 1000_000_000 == act.nsec + act_comment = " (#{act.nsec}[ns])" + else + act_comment = " (subsec=#{act.subsec})" + end + elsif exp.class != act.class + # a subclass of Range, for example. + exp_comment = " (#{exp.class})" + act_comment = " (#{act.class})" + end + elsif !Encoding.compatible?(exp_str, act_str) + if exp.is_a?(String) && act.is_a?(String) + exp_str = exp.dump + act_str = act.dump + exp_comment = " (#{exp.encoding})" + act_comment = " (#{act.encoding})" + else + exp_str = exp_str.dump + act_str = act_str.dump + end + end + "<#{exp_str}>#{exp_comment} expected but was\n<#{act_str}>#{act_comment}" + } + assert(exp == act, msg) + end + + # :call-seq: + # assert_not_nil( expression, failure_message = nil ) + # + #Tests if +expression+ is not nil. + # + #An optional failure message may be provided as the final argument. + def assert_not_nil(exp, msg=nil) + msg = message(msg) { "<#{mu_pp(exp)}> expected to not be nil" } + assert(!exp.nil?, msg) + end + + # :call-seq: + # assert_not_equal( expected, actual, failure_message = nil ) + # + #Tests if +expected+ is not equal to +actual+. + # + #An optional failure message may be provided as the final argument. + def assert_not_equal(exp, act, msg=nil) + msg = message(msg) { "<#{mu_pp(exp)}> expected to be != to\n<#{mu_pp(act)}>" } + assert(exp != act, msg) + end + + # :call-seq: + # assert_no_match( regexp, string, failure_message = nil ) + # + #Tests if the given Regexp does not match a given String. + # + #An optional failure message may be provided as the final argument. + def assert_no_match(regexp, string, msg=nil) + assert_instance_of(Regexp, regexp, "The first argument to assert_no_match should be a Regexp.") + self._assertions -= 1 + msg = message(msg) { "<#{mu_pp(regexp)}> expected to not match\n<#{mu_pp(string)}>" } + assert(regexp !~ string, msg) + end + + # :call-seq: + # assert_not_same( expected, actual, failure_message = nil ) + # + #Tests if +expected+ is not the same object as +actual+. + #This test uses Object#equal? to test equality. + # + #An optional failure message may be provided as the final argument. + # + # assert_not_same("x", "x") #Succeeds + def assert_not_same(expected, actual, message="") + msg = message(msg) { build_message(message, < +with id expected to not be equal\\? to + +with id . +EOT + assert(!actual.equal?(expected), msg) + end + + # :call-seq: + # assert_respond_to( object, method, failure_message = nil ) + # + #Tests if the given Object responds to +method+. + # + #An optional failure message may be provided as the final argument. + # + # assert_respond_to("hello", :reverse) #Succeeds + # assert_respond_to("hello", :does_not_exist) #Fails + def assert_respond_to obj, (meth, priv), msg = nil + if priv + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv}" + } + return assert obj.respond_to?(meth, priv), msg + end + #get rid of overcounting + super if !caller[0].rindex(MINI_DIR, 0) || !obj.respond_to?(meth) + end + + # :call-seq: + # assert_send( +send_array+, failure_message = nil ) + # + # Passes if the method send returns a true value. + # + # +send_array+ is composed of: + # * A receiver + # * A method + # * Arguments to the method + # + # Example: + # assert_send(["Hello world", :include?, "Hello"]) # -> pass + # assert_send(["Hello world", :include?, "Goodbye"]) # -> fail + def assert_send send_ary, m = nil + recv, msg, *args = send_ary + m = message(m) { + if args.empty? + argsstr = "" + else + (argsstr = mu_pp(args)).sub!(/\A\[(.*)\]\z/m, '(\1)') + end + "Expected #{mu_pp(recv)}.#{msg}#{argsstr} to return true" + } + assert recv.__send__(msg, *args), m + end + + # :call-seq: + # assert_not_send( +send_array+, failure_message = nil ) + # + # Passes if the method send doesn't return a true value. + # + # +send_array+ is composed of: + # * A receiver + # * A method + # * Arguments to the method + # + # Example: + # assert_not_send([[1, 2], :member?, 1]) # -> fail + # assert_not_send([[1, 2], :member?, 4]) # -> pass + def assert_not_send send_ary, m = nil + recv, msg, *args = send_ary + m = message(m) { + if args.empty? + argsstr = "" + else + (argsstr = mu_pp(args)).sub!(/\A\[(.*)\]\z/m, '(\1)') + end + "Expected #{mu_pp(recv)}.#{msg}#{argsstr} to return false" + } + assert !recv.__send__(msg, *args), m + end + + ms = instance_methods(true).map {|sym| sym.to_s } + ms.grep(/\Arefute_/) do |m| + mname = ('assert_not_' << m.to_s[/.*?_(.*)/, 1]) + alias_method(mname, m) unless ms.include? mname + end + alias assert_include assert_includes + alias assert_not_include assert_not_includes + + def assert_all?(obj, m = nil, &blk) + failed = [] + obj.each do |*a, &b| + unless blk.call(*a, &b) + failed << (a.size > 1 ? a : a[0]) + end + end + assert(failed.empty?, message(m) {failed.pretty_inspect}) + end + + def assert_not_all?(obj, m = nil, &blk) + failed = [] + obj.each do |*a, &b| + if blk.call(*a, &b) + failed << a.size > 1 ? a : a[0] + end + end + assert(failed.empty?, message(m) {failed.pretty_inspect}) + end + + def build_message(head, template=nil, *arguments) #:nodoc: + template &&= template.chomp + template.gsub(/\G((?:[^\\]|\\.)*?)(\\)?\?/) { $1 + ($2 ? "?" : mu_pp(arguments.shift)) } + end + + def message(msg = nil, *args, &default) # :nodoc: + if Proc === msg + super(nil, *args) do + [msg.call, (default.call if default)].compact.reject(&:empty?).join(".\n") + end + else + super + end + end + end + end +end diff --git a/test/lib/test/unit/parallel.rb b/test/lib/test/unit/parallel.rb new file mode 100644 index 0000000000..d51fe2f7bb --- /dev/null +++ b/test/lib/test/unit/parallel.rb @@ -0,0 +1,190 @@ +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../.." +require 'test/unit' + +module Test + module Unit + class Worker < Runner # :nodoc: + class << self + undef autorun + end + + alias orig_run_suite mini_run_suite + undef _run_suite + undef _run_suites + undef run + + def increment_io(orig) # :nodoc: + *rest, io = 32.times.inject([orig.dup]){|ios, | ios << ios.last.dup } + rest.each(&:close) + io + end + + def _run_suites(suites, type) # :nodoc: + suites.map do |suite| + _run_suite(suite, type) + end + end + + def _run_suite(suite, type) # :nodoc: + @partial_report = [] + orig_testout = MiniTest::Unit.output + i,o = IO.pipe + + MiniTest::Unit.output = o + orig_stdin, orig_stdout = $stdin, $stdout + + th = Thread.new do + begin + while buf = (self.verbose ? i.gets : i.read(5)) + _report "p", buf + end + rescue IOError + rescue Errno::EPIPE + end + end + + e, f, s = @errors, @failures, @skips + + begin + result = orig_run_suite(suite, type) + rescue Interrupt + @need_exit = true + result = [nil,nil] + end + + MiniTest::Unit.output = orig_testout + $stdin = orig_stdin + $stdout = orig_stdout + + o.close + begin + th.join + rescue IOError + raise unless ["stream closed","closed stream"].include? $!.message + end + i.close + + result << @partial_report + @partial_report = nil + result << [@errors-e,@failures-f,@skips-s] + result << ($: - @old_loadpath) + result << suite.name + + begin + _report "done", Marshal.dump(result) + rescue Errno::EPIPE; end + return result + ensure + MiniTest::Unit.output = orig_stdout + $stdin = orig_stdin + $stdout = orig_stdout + o.close if o && !o.closed? + i.close if i && !i.closed? + end + + def run(args = []) # :nodoc: + process_args args + @@stop_auto_run = true + @opts = @options.dup + @need_exit = false + + @old_loadpath = [] + begin + begin + @stdout = increment_io(STDOUT) + @stdin = increment_io(STDIN) + rescue + exit 2 + end + exit 2 unless @stdout && @stdin + + @stdout.sync = true + _report "ready!" + while buf = @stdin.gets + case buf.chomp + when /^loadpath (.+?)$/ + @old_loadpath = $:.dup + $:.push(*Marshal.load($1.unpack("m")[0].force_encoding("ASCII-8BIT"))).uniq! + when /^run (.+?) (.+?)$/ + _report "okay" + + @options = @opts.dup + suites = MiniTest::Unit::TestCase.test_suites + + begin + require $1 + rescue LoadError + _report "after", Marshal.dump([$1, ProxyError.new($!)]) + _report "ready" + next + end + _run_suites MiniTest::Unit::TestCase.test_suites-suites, $2.to_sym + + if @need_exit + begin + _report "bye" + rescue Errno::EPIPE; end + exit + else + _report "ready" + end + when /^quit$/ + begin + _report "bye" + rescue Errno::EPIPE; end + exit + end + end + rescue Errno::EPIPE + rescue Exception => e + begin + trace = e.backtrace + err = ["#{trace.shift}: #{e.message} (#{e.class})"] + trace.map{|t| t.prepend("\t") } + + _report "bye", Marshal.dump(err.join("\n")) + rescue Errno::EPIPE;end + exit + ensure + @stdin.close if @stdin + @stdout.close if @stdout + end + end + + def _report(res, *args) # :nodoc: + res = "#{res} #{args.pack("m0")}" unless args.empty? + @stdout.puts(res) + end + + def puke(klass, meth, e) # :nodoc: + if e.is_a?(MiniTest::Skip) + new_e = MiniTest::Skip.new(e.message) + new_e.set_backtrace(e.backtrace) + e = new_e + end + @partial_report << [klass.name, meth, e.is_a?(MiniTest::Assertion) ? e : ProxyError.new(e)] + super + end + end + end +end + +if $0 == __FILE__ + module Test + module Unit + class TestCase < MiniTest::Unit::TestCase # :nodoc: all + undef on_parallel_worker? + def on_parallel_worker? + true + end + end + end + end + require 'rubygems' + module Gem # :nodoc: + end + class Gem::TestCase < MiniTest::Unit::TestCase # :nodoc: + @@project_dir = File.expand_path('../../../..', __FILE__) + end + + Test::Unit::Worker.new.run(ARGV) +end diff --git a/test/lib/test/unit/test-unit.gemspec b/test/lib/test/unit/test-unit.gemspec new file mode 100644 index 0000000000..363fd50c02 --- /dev/null +++ b/test/lib/test/unit/test-unit.gemspec @@ -0,0 +1,18 @@ +# -*- ruby -*- + +Gem::Specification.new do |s| + s.name = "test-unit" + s.version = "#{RUBY_VERSION}.0" + s.homepage = "http://www.ruby-lang.org" + s.author = "Shota Fukumori" + s.email = "sorah@tubusu.net" + s.summary = "test/unit compatible API testing framework" + s.description = + "This library implements test/unit compatible API on minitest. " + + "The test/unit means that test/unit which was bundled with Ruby 1.8." + s.executables = ["testrb"] + + # Ruby bundled test/unit is a compatibility layer for minitest, + # and it doesn't have support for minitest 5. + s.add_runtime_dependency "minitest", '< 5.0.0' +end diff --git a/test/lib/test/unit/testcase.rb b/test/lib/test/unit/testcase.rb new file mode 100644 index 0000000000..984f08dd32 --- /dev/null +++ b/test/lib/test/unit/testcase.rb @@ -0,0 +1,34 @@ +require 'test/unit/assertions' + +module Test + module Unit + # remove silly TestCase class + remove_const(:TestCase) if defined?(self::TestCase) + + class TestCase < MiniTest::Unit::TestCase # :nodoc: all + include Assertions + + def on_parallel_worker? + false + end + + def run runner + @options = runner.options + super runner + end + + def self.test_order + :sorted + end + + def self.method_added(name) + return unless name.to_s.start_with?("test_") + @test_methods ||= {} + if @test_methods[name] + warn "test/unit warning: method #{ self }##{ name } is redefined" + end + @test_methods[name] = true + end + end + end +end -- cgit v1.2.3