diff options
Diffstat (limited to 'jni/ruby/test/lib')
-rw-r--r-- | jni/ruby/test/lib/envutil.rb | 604 | ||||
-rw-r--r-- | jni/ruby/test/lib/find_executable.rb | 21 | ||||
-rw-r--r-- | jni/ruby/test/lib/leakchecker.rb | 167 | ||||
-rw-r--r-- | jni/ruby/test/lib/memory_status.rb | 111 | ||||
-rw-r--r-- | jni/ruby/test/lib/minitest/README.txt | 457 | ||||
-rw-r--r-- | jni/ruby/test/lib/minitest/autorun.rb | 13 | ||||
-rw-r--r-- | jni/ruby/test/lib/minitest/benchmark.rb | 417 | ||||
-rw-r--r-- | jni/ruby/test/lib/minitest/mock.rb | 195 | ||||
-rw-r--r-- | jni/ruby/test/lib/minitest/unit.rb | 1419 | ||||
-rw-r--r-- | jni/ruby/test/lib/profile_test_all.rb | 90 | ||||
-rw-r--r-- | jni/ruby/test/lib/test/unit.rb | 896 | ||||
-rw-r--r-- | jni/ruby/test/lib/test/unit/assertions.rb | 465 | ||||
-rw-r--r-- | jni/ruby/test/lib/test/unit/parallel.rb | 190 | ||||
-rw-r--r-- | jni/ruby/test/lib/test/unit/testcase.rb | 34 | ||||
-rw-r--r-- | jni/ruby/test/lib/tracepointchecker.rb | 118 | ||||
-rw-r--r-- | jni/ruby/test/lib/with_different_ofs.rb | 17 |
16 files changed, 5214 insertions, 0 deletions
diff --git a/jni/ruby/test/lib/envutil.rb b/jni/ruby/test/lib/envutil.rb new file mode 100644 index 0000000..1193a1a --- /dev/null +++ b/jni/ruby/test/lib/envutil.rb @@ -0,0 +1,604 @@ +# -*- coding: us-ascii -*- +require "open3" +require "timeout" +require_relative "find_executable" + +module EnvUtil + def rubybin + if ruby = ENV["RUBY"] + return ruby + end + ruby = "ruby" + exeext = RbConfig::CONFIG["EXEEXT"] + rubyexe = (ruby + exeext if exeext and !exeext.empty?) + 3.times do + if File.exist? ruby and File.executable? ruby and !File.directory? ruby + return File.expand_path(ruby) + end + if rubyexe and File.exist? rubyexe and File.executable? rubyexe + return File.expand_path(rubyexe) + end + ruby = File.join("..", ruby) + end + if defined?(RbConfig.ruby) + RbConfig.ruby + else + "ruby" + end + end + module_function :rubybin + + LANG_ENVS = %w"LANG LC_ALL LC_CTYPE" + + def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, + encoding: nil, timeout: 10, reprieve: 1, + stdout_filter: nil, stderr_filter: nil, + rubybin: EnvUtil.rubybin, + **opt) + in_c, in_p = IO.pipe + out_p, out_c = IO.pipe if capture_stdout + err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout + opt[:in] = in_c + opt[:out] = out_c if capture_stdout + opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr + if encoding + out_p.set_encoding(encoding) if out_p + err_p.set_encoding(encoding) if err_p + end + c = "C" + child_env = {} + LANG_ENVS.each {|lc| child_env[lc] = c} + if Array === args and Hash === args.first + child_env.update(args.shift) + end + args = [args] if args.kind_of?(String) + pid = spawn(child_env, rubybin, *args, **opt) + in_c.close + out_c.close if capture_stdout + err_c.close if capture_stderr && capture_stderr != :merge_to_stdout + if block_given? + return yield in_p, out_p, err_p, pid + else + th_stdout = Thread.new { out_p.read } if capture_stdout + th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout + in_p.write stdin_data.to_str unless stdin_data.empty? + in_p.close + if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) + stdout = th_stdout.value if capture_stdout + stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout + else + signal = /mswin|mingw/ =~ RUBY_PLATFORM ? :KILL : :TERM + case pgroup = opt[:pgroup] + when 0, true + pgroup = -pid + when nil, false + pgroup = pid + end + begin + Process.kill signal, pgroup + Timeout.timeout((reprieve unless signal == :KILL)) do + Process.wait(pid) + end + rescue Errno::ESRCH + break + rescue Timeout::Error + raise if signal == :KILL + signal = :KILL + else + break + end while true + bt = caller_locations + raise Timeout::Error, "execution of #{bt.shift.label} expired", bt.map(&:to_s) + end + out_p.close if capture_stdout + err_p.close if capture_stderr && capture_stderr != :merge_to_stdout + Process.wait pid + status = $? + stdout = stdout_filter.call(stdout) if stdout_filter + stderr = stderr_filter.call(stderr) if stderr_filter + return stdout, stderr, status + end + ensure + [th_stdout, th_stderr].each do |th| + th.kill if th + end + [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| + io.close if io && !io.closed? + end + [th_stdout, th_stderr].each do |th| + th.join if th + end + end + module_function :invoke_ruby + + alias rubyexec invoke_ruby + class << self + alias rubyexec invoke_ruby + end + + def verbose_warning + class << (stderr = "") + alias write << + end + stderr, $stderr, verbose, $VERBOSE = $stderr, stderr, $VERBOSE, true + yield stderr + return $stderr + ensure + stderr, $stderr, $VERBOSE = $stderr, stderr, verbose + end + module_function :verbose_warning + + def default_warning + verbose, $VERBOSE = $VERBOSE, false + yield + ensure + $VERBOSE = verbose + end + module_function :default_warning + + def suppress_warning + verbose, $VERBOSE = $VERBOSE, nil + yield + ensure + $VERBOSE = verbose + end + module_function :suppress_warning + + def under_gc_stress(stress = true) + stress, GC.stress = GC.stress, stress + yield + ensure + GC.stress = stress + end + module_function :under_gc_stress + + def with_default_external(enc) + verbose, $VERBOSE = $VERBOSE, nil + origenc, Encoding.default_external = Encoding.default_external, enc + $VERBOSE = verbose + yield + ensure + verbose, $VERBOSE = $VERBOSE, nil + Encoding.default_external = origenc + $VERBOSE = verbose + end + module_function :with_default_external + + def with_default_internal(enc) + verbose, $VERBOSE = $VERBOSE, nil + origenc, Encoding.default_internal = Encoding.default_internal, enc + $VERBOSE = verbose + yield + ensure + verbose, $VERBOSE = $VERBOSE, nil + Encoding.default_internal = origenc + $VERBOSE = verbose + end + module_function :with_default_internal + + def labeled_module(name, &block) + Module.new do + singleton_class.class_eval {define_method(:to_s) {name}; alias inspect to_s} + class_eval(&block) if block + end + end + module_function :labeled_module + + def labeled_class(name, superclass = Object, &block) + Class.new(superclass) do + singleton_class.class_eval {define_method(:to_s) {name}; alias inspect to_s} + class_eval(&block) if block + end + end + module_function :labeled_class + + if /darwin/ =~ RUBY_PLATFORM + DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports") + DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S' + def self.diagnostic_reports(signame, cmd, pid, now) + return unless %w[ABRT QUIT SEGV ILL].include?(signame) + cmd = File.basename(cmd) + path = DIAGNOSTIC_REPORTS_PATH + timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT + pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash" + first = true + 30.times do + first ? (first = false) : sleep(0.1) + Dir.glob(pat) do |name| + log = File.read(name) rescue next + if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log + File.unlink(name) + File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil + return log + end + end + end + nil + end + else + def self.diagnostic_reports(signame, cmd, pid, now) + end + end +end + +module Test + module Unit + module Assertions + public + def assert_valid_syntax(code, fname = caller_locations(1, 1)[0], mesg = fname.to_s, verbose: nil) + code = code.dup.force_encoding("ascii-8bit") + code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { + "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n" + } + code.force_encoding(Encoding::UTF_8) + verbose, $VERBOSE = $VERBOSE, verbose + yield if defined?(yield) + case + when Array === fname + fname, line = *fname + when defined?(fname.path) && defined?(fname.lineno) + fname, line = fname.path, fname.lineno + else + line = 0 + end + assert_nothing_raised(SyntaxError, mesg) do + assert_equal(:ok, catch {|tag| eval(code, binding, fname, line)}, mesg) + end + ensure + $VERBOSE = verbose + end + + def assert_syntax_error(code, error, fname = caller_locations(1, 1)[0], mesg = fname.to_s) + code = code.dup.force_encoding("ascii-8bit") + code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { + "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ng}\n" + } + code.force_encoding("us-ascii") + verbose, $VERBOSE = $VERBOSE, nil + yield if defined?(yield) + case + when Array === fname + fname, line = *fname + when defined?(fname.path) && defined?(fname.lineno) + fname, line = fname.path, fname.lineno + else + line = 0 + end + e = assert_raise(SyntaxError, mesg) do + catch {|tag| eval(code, binding, fname, line)} + end + assert_match(error, e.message, mesg) + ensure + $VERBOSE = verbose + end + + def assert_normal_exit(testsrc, message = '', child_env: nil, **opt) + assert_valid_syntax(testsrc, caller_locations(1, 1)[0]) + if child_env + child_env = [child_env] + else + child_env = [] + end + out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt) + assert !status.signaled?, FailDesc[status, message, out] + end + + FailDesc = proc do |status, message = "", out = ""| + pid = status.pid + now = Time.now + faildesc = proc do + if signo = status.termsig + signame = Signal.signame(signo) + sigdesc = "signal #{signo}" + end + log = EnvUtil.diagnostic_reports(signame, EnvUtil.rubybin, pid, now) + if signame + sigdesc = "SIG#{signame} (#{sigdesc})" + end + if status.coredump? + sigdesc << " (core dumped)" + end + full_message = '' + if message and !message.empty? + full_message << message << "\n" + end + full_message << "pid #{pid} killed by #{sigdesc}" + if out and !out.empty? + full_message << "\n#{out.gsub(/^/, '| ')}" + full_message << "\n" if /\n\z/ !~ full_message + end + if log + full_message << "\n#{log.gsub(/^/, '| ')}" + end + full_message + end + faildesc + end + + def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, **opt) + stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt) + if signo = status.termsig + sleep 0.1 + EnvUtil.diagnostic_reports(Signal.signame(signo), EnvUtil.rubybin, status.pid, Time.now) + end + if block_given? + raise "test_stdout ignored, use block only or without block" if test_stdout != [] + raise "test_stderr ignored, use block only or without block" if test_stderr != [] + yield(stdout.lines.map {|l| l.chomp }, stderr.lines.map {|l| l.chomp }, status) + else + errs = [] + [[test_stdout, stdout], [test_stderr, stderr]].each do |exp, act| + begin + if exp.is_a?(Regexp) + assert_match(exp, act, message) + else + assert_equal(exp, act.lines.map {|l| l.chomp }, message) + end + rescue MiniTest::Assertion => e + errs << e.message + message = nil + end + end + raise MiniTest::Assertion, errs.join("\n---\n") unless errs.empty? + status + end + end + + def assert_ruby_status(args, test_stdin="", message=nil, **opt) + out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt) + assert(!status.signaled?, FailDesc[status, message, out]) + message ||= "ruby exit status is not success:" + assert(status.success?, "#{message} (#{status.inspect})") + end + + ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV") + + def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt) + unless file and line + loc, = caller_locations(1,1) + file ||= loc.path + line ||= loc.lineno + end + line -= 5 # lines until src + src = <<eom +# -*- coding: #{src.encoding}; -*- + require #{__dir__.dump}'/test/unit';include Test::Unit::Assertions + END { + puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}" + } +#{src} + class Test::Unit::Runner + @@stop_auto_run = true + end +eom + args = args.dup + args.insert((Hash === args.first ? 1 : 0), "--disable=gems", *$:.map {|l| "-I#{l}"}) + stdout, stderr, status = EnvUtil.invoke_ruby(args, src, true, true, **opt) + abort = status.coredump? || (status.signaled? && ABORT_SIGNALS.include?(status.termsig)) + assert(!abort, FailDesc[status, nil, stderr]) + self._assertions += stdout[/^assertions=(\d+)/, 1].to_i + begin + res = Marshal.load(stdout.unpack("m")[0]) + rescue => marshal_error + ignore_stderr = nil + end + if res + if bt = res.backtrace + bt.each do |l| + l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} + end + bt.concat(caller) + else + res.set_backtrace(caller) + end + raise res + end + + # really is it succeed? + unless ignore_stderr + # the body of assert_separately must not output anything to detect error + assert_equal("", stderr, "assert_separately failed with error message") + end + assert_equal(0, status, "assert_separately failed: '#{stderr}'") + raise marshal_error if marshal_error + end + + def assert_warning(pat, msg = nil) + stderr = EnvUtil.verbose_warning { yield } + msg = message(msg) {diff pat, stderr} + assert(pat === stderr, msg) + end + + def assert_warn(*args) + assert_warning(*args) {$VERBOSE = false; yield} + end + + case RUBY_PLATFORM + when /solaris2\.(?:9|[1-9][0-9])/i # Solaris 9, 10, 11,... + bits = [nil].pack('p').size == 8 ? 64 : 32 + if ENV['LD_PRELOAD'].to_s.empty? && + ENV["LD_PRELOAD_#{bits}"].to_s.empty? && + (ENV['UMEM_OPTIONS'].to_s.empty? || + ENV['UMEM_OPTIONS'] == 'backend=mmap') then + envs = { + 'LD_PRELOAD' => 'libumem.so', + 'UMEM_OPTIONS' => 'backend=mmap' + } + args = [ + envs, + "--disable=gems", + "-v", "-", + ] + _, err, status = EnvUtil.invoke_ruby(args, "exit(0)", true, true) + if status.exitstatus == 0 && err.to_s.empty? then + NO_MEMORY_LEAK_ENVS = envs + end + end + end #case RUBY_PLATFORM + + def assert_no_memory_leak(args, prepare, code, message=nil, limit: 1.5, rss: false, **opt) + require_relative 'memory_status' + token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m" + token_dump = token.dump + token_re = Regexp.quote(token) + envs = args.shift if Array === args and Hash === args.first + args = [ + "--disable=gems", + "-r", File.expand_path("../memory_status", __FILE__), + *args, + "-v", "-", + ] + if defined? NO_MEMORY_LEAK_ENVS then + envs ||= {} + newenvs = envs.merge(NO_MEMORY_LEAK_ENVS) { |_, _, _| break } + envs = newenvs if newenvs + end + args.unshift(envs) if envs + cmd = [ + 'END {STDERR.puts '"#{token_dump}"'"FINAL=#{Memory::Status.new}"}', + prepare, + 'STDERR.puts('"#{token_dump}"'"START=#{$initial_status = Memory::Status.new}")', + '$initial_size = $initial_status.size', + code, + 'GC.start', + ].join("\n") + _, err, status = EnvUtil.invoke_ruby(args, cmd, true, true, **opt) + before = err.sub!(/^#{token_re}START=(\{.*\})\n/, '') && Memory::Status.parse($1) + after = err.sub!(/^#{token_re}FINAL=(\{.*\})\n/, '') && Memory::Status.parse($1) + assert_equal([true, ""], [status.success?, err], message) + ([:size, (rss && :rss)] & after.members).each do |n| + b = before[n] + a = after[n] + next unless a > 0 and b > 0 + assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"}) + end + rescue LoadError + skip + end + + def assert_is_minus_zero(f) + assert(1.0/f == -Float::INFINITY, "#{f} is not -0.0") + end + + def assert_file + AssertFile + end + + # pattern_list is an array which contains regexp and :*. + # :* means any sequence. + # + # pattern_list is anchored. + # Use [:*, regexp, :*] for non-anchored match. + def assert_pattern_list(pattern_list, actual, message=nil) + rest = actual + anchored = true + pattern_list.each_with_index {|pattern, i| + if pattern == :* + anchored = false + else + if anchored + match = /\A#{pattern}/.match(rest) + else + match = pattern.match(rest) + end + unless match + msg = message(msg) { + expect_msg = "Expected #{mu_pp pattern}\n" + if /\n[^\n]/ =~ rest + actual_mesg = "to match\n" + rest.scan(/.*\n+/) { + actual_mesg << ' ' << $&.inspect << "+\n" + } + actual_mesg.sub!(/\+\n\z/, '') + else + actual_mesg = "to match #{mu_pp rest}" + end + actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters" + expect_msg + actual_mesg + } + assert false, msg + end + rest = match.post_match + anchored = true + end + } + if anchored + assert_equal("", rest) + end + end + + # threads should respond to shift method. + # Array can be used. + def assert_join_threads(threads, message = nil) + errs = [] + values = [] + while th = threads.shift + begin + values << th.value + rescue Exception + errs << [th, $!] + end + end + if !errs.empty? + msg = "exceptions on #{errs.length} threads:\n" + + errs.map {|t, err| + "#{t.inspect}:\n" + + err.backtrace.map.with_index {|line, i| + if i == 0 + "#{line}: #{err.message} (#{err.class})" + else + "\tfrom #{line}" + end + }.join("\n") + }.join("\n---\n") + if message + msg = "#{message}\n#{msg}" + end + raise MiniTest::Assertion, msg + end + values + end + + class << (AssertFile = Struct.new(:failure_message).new) + include Assertions + def assert_file_predicate(predicate, *args) + if /\Anot_/ =~ predicate + predicate = $' + neg = " not" + end + result = File.__send__(predicate, *args) + result = !result if neg + mesg = "Expected file " << args.shift.inspect + mesg << "#{neg} to be #{predicate}" + mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty? + mesg << " #{failure_message}" if failure_message + assert(result, mesg) + end + alias method_missing assert_file_predicate + + def for(message) + clone.tap {|a| a.failure_message = message} + end + end + end + end +end + +begin + require 'rbconfig' +rescue LoadError +else + module RbConfig + @ruby = EnvUtil.rubybin + class << self + undef ruby if method_defined?(:ruby) + attr_reader :ruby + end + dir = File.dirname(ruby) + name = File.basename(ruby, CONFIG['EXEEXT']) + CONFIG['bindir'] = dir + CONFIG['ruby_install_name'] = name + CONFIG['RUBY_INSTALL_NAME'] = name + Gem::ConfigMap[:bindir] = dir if defined?(Gem::ConfigMap) + end +end diff --git a/jni/ruby/test/lib/find_executable.rb b/jni/ruby/test/lib/find_executable.rb new file mode 100644 index 0000000..4ba9a05 --- /dev/null +++ b/jni/ruby/test/lib/find_executable.rb @@ -0,0 +1,21 @@ +require "rbconfig" + +module EnvUtil + def find_executable(cmd, *args) + exts = RbConfig::CONFIG["EXECUTABLE_EXTS"].split | [RbConfig::CONFIG["EXEEXT"]] + ENV["PATH"].split(File::PATH_SEPARATOR).each do |path| + next if path.empty? + path = File.join(path, cmd) + exts.each do |ext| + cmdline = [path + ext, *args] + begin + return cmdline if yield(IO.popen(cmdline, "r", err: [:child, :out], &:read)) + rescue + next + end + end + end + nil + end + module_function :find_executable +end diff --git a/jni/ruby/test/lib/leakchecker.rb b/jni/ruby/test/lib/leakchecker.rb new file mode 100644 index 0000000..37c87c9 --- /dev/null +++ b/jni/ruby/test/lib/leakchecker.rb @@ -0,0 +1,167 @@ +class LeakChecker + def initialize + @fd_info = find_fds + @tempfile_info = find_tempfiles + @thread_info = find_threads + end + + def check(test_name) + leaked1 = check_fd_leak(test_name) + leaked2 = check_thread_leak(test_name) + leaked3 = check_tempfile_leak(test_name) + GC.start if leaked1 || leaked2 || leaked3 + end + + def find_fds + fd_dir = "/proc/self/fd" + if File.directory?(fd_dir) + fds = Dir.open(fd_dir) {|d| + a = d.grep(/\A\d+\z/, &:to_i) + if d.respond_to? :fileno + a -= [d.fileno] + end + a + } + fds.sort + else + [] + end + end + + def check_fd_leak(test_name) + leaked = false + live1 = @fd_info + if IO.respond_to?(:console) + IO.console(:close) + end + live2 = find_fds + fd_closed = live1 - live2 + if !fd_closed.empty? + fd_closed.each {|fd| + puts "Closed file descriptor: #{test_name}: #{fd}" + } + end + fd_leaked = live2 - live1 + if !fd_leaked.empty? + leaked = true + h = {} + ObjectSpace.each_object(IO) {|io| + inspect = io.inspect + begin + autoclose = io.autoclose? + fd = io.fileno + rescue IOError # closed IO object + next + end + (h[fd] ||= []) << [io, autoclose, inspect] + } + fd_leaked.each {|fd| + str = '' + if h[fd] + str << ' :' + h[fd].map {|io, autoclose, inspect| + s = ' ' + inspect + s << "(not-autoclose)" if !autoclose + s + }.sort.each {|s| + str << s + } + end + puts "Leaked file descriptor: #{test_name}: #{fd}#{str}" + } + #system("lsof -p #$$") if !fd_leaked.empty? + h.each {|fd, list| + next if list.length <= 1 + if 1 < list.count {|io, autoclose, inspect| autoclose } + str = list.map {|io, autoclose, inspect| " #{inspect}" + (autoclose ? "(autoclose)" : "") }.sort.join + puts "Multiple autoclose IO object for a file descriptor:#{str}" + end + } + end + @fd_info = live2 + return leaked + end + + def extend_tempfile_counter + return if defined? LeakChecker::TempfileCounter + m = Module.new { + @count = 0 + class << self + attr_accessor :count + end + + def new(data) + LeakChecker::TempfileCounter.count += 1 + super(data) + end + } + LeakChecker.const_set(:TempfileCounter, m) + + class << Tempfile::Remover + prepend LeakChecker::TempfileCounter + end + end + + def find_tempfiles(prev_count=-1) + return [prev_count, []] unless defined? Tempfile + extend_tempfile_counter + count = TempfileCounter.count + if prev_count == count + [prev_count, []] + else + tempfiles = ObjectSpace.each_object(Tempfile).find_all {|t| t.path } + [count, tempfiles] + end + end + + def check_tempfile_leak(test_name) + return false, @tempfile_info unless defined? Tempfile + count1, initial_tempfiles = @tempfile_info + count2, current_tempfiles = find_tempfiles(count1) + leaked = false + tempfiles_leaked = current_tempfiles - initial_tempfiles + if !tempfiles_leaked.empty? + leaked = true + list = tempfiles_leaked.map {|t| t.inspect }.sort + list.each {|str| + puts "Leaked tempfile: #{test_name}: #{str}" + } + tempfiles_leaked.each {|t| t.close! } + end + @tempfile_info = [count2, initial_tempfiles] + return leaked + end + + def find_threads + Thread.list.find_all {|t| + t != Thread.current && /\AWEBrick::/ !~ t.class.name && t.alive? + } + end + + def check_thread_leak(test_name) + live1 = @thread_info + live2 = find_threads + thread_finished = live1 - live2 + leaked = false + if !thread_finished.empty? + list = thread_finished.map {|t| t.inspect }.sort + list.each {|str| + puts "Finished thread: #{test_name}: #{str}" + } + end + thread_leaked = live2 - live1 + if !thread_leaked.empty? + leaked = true + list = thread_leaked.map {|t| t.inspect }.sort + list.each {|str| + puts "Leaked thread: #{test_name}: #{str}" + } + end + @thread_info = live2 + return leaked + end + + def puts(*a) + MiniTest::Unit.output.puts(*a) + end +end diff --git a/jni/ruby/test/lib/memory_status.rb b/jni/ruby/test/lib/memory_status.rb new file mode 100644 index 0000000..071c5f6 --- /dev/null +++ b/jni/ruby/test/lib/memory_status.rb @@ -0,0 +1,111 @@ +module Memory + keys = [] + vals = [] + + case + when File.exist?(procfile = "/proc/self/status") && (pat = /^Vm(\w+):\s+(\d+)/) =~ File.binread(procfile) + PROC_FILE = procfile + VM_PAT = pat + def self.read_status + IO.foreach(PROC_FILE, encoding: Encoding::ASCII_8BIT) do |l| + yield($1.downcase.intern, $2.to_i * 1024) if VM_PAT =~ l + end + end + + read_status {|k, v| keys << k; vals << v} + + when /mswin|mingw/ =~ RUBY_PLATFORM + require 'fiddle/import' + require 'fiddle/types' + + module Win32 + extend Fiddle::Importer + dlload "kernel32.dll", "psapi.dll" + include Fiddle::Win32Types + typealias "SIZE_T", "size_t" + + PROCESS_MEMORY_COUNTERS = struct [ + "DWORD cb", + "DWORD PageFaultCount", + "SIZE_T PeakWorkingSetSize", + "SIZE_T WorkingSetSize", + "SIZE_T QuotaPeakPagedPoolUsage", + "SIZE_T QuotaPagedPoolUsage", + "SIZE_T QuotaPeakNonPagedPoolUsage", + "SIZE_T QuotaNonPagedPoolUsage", + "SIZE_T PagefileUsage", + "SIZE_T PeakPagefileUsage", + ] + + typealias "PPROCESS_MEMORY_COUNTERS", "PROCESS_MEMORY_COUNTERS*" + + extern "HANDLE GetCurrentProcess()", :stdcall + extern "BOOL GetProcessMemoryInfo(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD)", :stdcall + + module_function + def memory_info + size = PROCESS_MEMORY_COUNTERS.size + data = PROCESS_MEMORY_COUNTERS.malloc + data.cb = size + data if GetProcessMemoryInfo(GetCurrentProcess(), data, size) + end + end + + keys << :peak << :size + def self.read_status + if info = Win32.memory_info + yield :peak, info.PeakPagefileUsage + yield :size, info.PagefileUsage + end + end + else + PAT = /^\s*(\d+)\s+(\d+)$/ + require_relative '../lib/find_executable' + if PSCMD = EnvUtil.find_executable("ps", "-ovsz=", "-orss=", "-p", $$.to_s) {|out| PAT =~ out} + PSCMD.pop + end + raise MiniTest::Skip, "ps command not found" unless PSCMD + + keys << :size << :rss + def self.read_status + if PAT =~ IO.popen(PSCMD + [$$.to_s], "r", err: [:child, :out], &:read) + yield :size, $1.to_i*1024 + yield :rss, $2.to_i*1024 + end + end + end + + Status = Struct.new(*keys) + + class Status + def _update + Memory.read_status do |key, val| + self[key] = val + end + end + end + + class Status + Header = members.map {|k| k.to_s.upcase.rjust(6)}.join('') + Format = "%6d" + + def initialize + _update + end + + def to_s + status = each_pair.map {|n,v| + "#{n}:#{v}" + } + "{#{status.join(",")}}" + end + + def self.parse(str) + status = allocate + str.scan(/(?:\A\{|\G,)(#{members.join('|')}):(\d+)(?=,|\}\z)/) do + status[$1] = $2.to_i + end + status + end + end +end diff --git a/jni/ruby/test/lib/minitest/README.txt b/jni/ruby/test/lib/minitest/README.txt new file mode 100644 index 0000000..368cc3a --- /dev/null +++ b/jni/ruby/test/lib/minitest/README.txt @@ -0,0 +1,457 @@ += minitest/{unit,spec,mock,benchmark} + +home :: https://github.com/seattlerb/minitest +rdoc :: http://docs.seattlerb.org/minitest +vim :: https://github.com/sunaku/vim-ruby-minitest + +== DESCRIPTION: + +minitest provides a complete suite of testing facilities supporting +TDD, BDD, mocking, and benchmarking. + + "I had a class with Jim Weirich on testing last week and we were + allowed to choose our testing frameworks. Kirk Haines and I were + paired up and we cracked open the code for a few test + frameworks... + + I MUST say that minitest is *very* readable / understandable + compared to the 'other two' options we looked at. Nicely done and + thank you for helping us keep our mental sanity." + + -- Wayne E. Seguin + +minitest/unit is a small and incredibly fast unit testing framework. +It provides a rich set of assertions to make your tests clean and +readable. + +minitest/spec is a functionally complete spec engine. It hooks onto +minitest/unit and seamlessly bridges test assertions over to spec +expectations. + +minitest/benchmark is an awesome way to assert the performance of your +algorithms in a repeatable manner. Now you can assert that your newb +co-worker doesn't replace your linear algorithm with an exponential +one! + +minitest/mock by Steven Baker, is a beautifully tiny mock (and stub) +object framework. + +minitest/pride shows pride in testing and adds coloring to your test +output. I guess it is an example of how to write IO pipes too. :P + +minitest/unit is meant to have a clean implementation for language +implementors that need a minimal set of methods to bootstrap a working +test suite. For example, there is no magic involved for test-case +discovery. + + "Again, I can't praise enough the idea of a testing/specing + framework that I can actually read in full in one sitting!" + + -- Piotr Szotkowski + +Comparing to rspec: + + rspec is a testing DSL. minitest is ruby. + + -- Adam Hawkins, "Bow Before MiniTest" + +minitest doesn't reinvent anything that ruby already provides, like: +classes, modules, inheritance, methods. This means you only have to +learn ruby to use minitest and all of your regular OO practices like +extract-method refactorings still apply. + +== FEATURES/PROBLEMS: + +* minitest/autorun - the easy and explicit way to run all your tests. +* minitest/unit - a very fast, simple, and clean test system. +* minitest/spec - a very fast, simple, and clean spec system. +* minitest/mock - a simple and clean mock/stub system. +* minitest/benchmark - an awesome way to assert your algorithm's performance. +* minitest/pride - show your pride in testing! +* Incredibly small and fast runner, but no bells and whistles. + +== RATIONALE: + +See design_rationale.rb to see how specs and tests work in minitest. + +== SYNOPSIS: + +Given that you'd like to test the following class: + + class Meme + def i_can_has_cheezburger? + "OHAI!" + end + + def will_it_blend? + "YES!" + end + end + +=== Unit tests + + require 'minitest/autorun' + + class TestMeme < MiniTest::Unit::TestCase + def setup + @meme = Meme.new + end + + def test_that_kitty_can_eat + assert_equal "OHAI!", @meme.i_can_has_cheezburger? + end + + def test_that_it_will_not_blend + refute_match /^no/i, @meme.will_it_blend? + end + + def test_that_will_be_skipped + skip "test this later" + end + end + +=== Specs + + require 'minitest/autorun' + + describe Meme do + before do + @meme = Meme.new + end + + describe "when asked about cheeseburgers" do + it "must respond positively" do + @meme.i_can_has_cheezburger?.must_equal "OHAI!" + end + end + + describe "when asked about blending possibilities" do + it "won't say no" do + @meme.will_it_blend?.wont_match /^no/i + end + end + end + +For matchers support check out: + +https://github.com/zenspider/minitest-matchers + +=== Benchmarks + +Add benchmarks to your regular unit tests. If the unit tests fail, the +benchmarks won't run. + + # optionally run benchmarks, good for CI-only work! + require 'minitest/benchmark' if ENV["BENCH"] + + class TestMeme < MiniTest::Unit::TestCase + # Override self.bench_range or default range is [1, 10, 100, 1_000, 10_000] + def bench_my_algorithm + assert_performance_linear 0.9999 do |n| # n is a range value + @obj.my_algorithm(n) + end + end + end + +Or add them to your specs. If you make benchmarks optional, you'll +need to wrap your benchmarks in a conditional since the methods won't +be defined. + + describe Meme do + if ENV["BENCH"] then + bench_performance_linear "my_algorithm", 0.9999 do |n| + 100.times do + @obj.my_algorithm(n) + end + end + end + end + +outputs something like: + + # Running benchmarks: + + TestBlah 100 1000 10000 + bench_my_algorithm 0.006167 0.079279 0.786993 + bench_other_algorithm 0.061679 0.792797 7.869932 + +Output is tab-delimited to make it easy to paste into a spreadsheet. + +=== Mocks + + class MemeAsker + def initialize(meme) + @meme = meme + end + + def ask(question) + method = question.tr(" ","_") + "?" + @meme.__send__(method) + end + end + + require 'minitest/autorun' + + describe MemeAsker do + before do + @meme = MiniTest::Mock.new + @meme_asker = MemeAsker.new @meme + end + + describe "#ask" do + describe "when passed an unpunctuated question" do + it "should invoke the appropriate predicate method on the meme" do + @meme.expect :will_it_blend?, :return_value + @meme_asker.ask "will it blend" + @meme.verify + end + end + end + end + +=== Stubs + + def test_stale_eh + obj_under_test = Something.new + + refute obj_under_test.stale? + + Time.stub :now, Time.at(0) do # stub goes away once the block is done + assert obj_under_test.stale? + end + end + +A note on stubbing: In order to stub a method, the method must +actually exist prior to stubbing. Use a singleton method to create a +new non-existing method: + + def obj_under_test.fake_method + ... + end + +=== Customizable Test Runner Types: + +MiniTest::Unit.runner=(runner) provides an easy way of creating custom +test runners for specialized needs. Justin Weiss provides the +following real-world example to create an alternative to regular +fixture loading: + + class MiniTestWithHooks::Unit < MiniTest::Unit + def before_suites + end + + def after_suites + end + + def _run_suites(suites, type) + begin + before_suites + super(suites, type) + ensure + after_suites + end + end + + def _run_suite(suite, type) + begin + suite.before_suite + super(suite, type) + ensure + suite.after_suite + end + end + end + + module MiniTestWithTransactions + class Unit < MiniTestWithHooks::Unit + include TestSetupHelper + + def before_suites + super + setup_nested_transactions + # load any data we want available for all tests + end + + def after_suites + teardown_nested_transactions + super + end + end + end + + MiniTest::Unit.runner = MiniTestWithTransactions::Unit.new + +== FAQ + +=== How to test SimpleDelegates? + +The following implementation and test: + + class Worker < SimpleDelegator + def work + end + end + + describe Worker do + before do + @worker = Worker.new(Object.new) + end + + it "must respond to work" do + @worker.must_respond_to :work + end + end + +outputs a failure: + + 1) Failure: + Worker#test_0001_must respond to work [bug11.rb:16]: + Expected #<Object:0x007f9e7184f0a0> (Object) to respond to #work. + +Worker is a SimpleDelegate which in 1.9+ is a subclass of BasicObject. +Expectations are put on Object (one level down) so the Worker +(SimpleDelegate) hits `method_missing` and delegates down to the +`Object.new` instance. That object doesn't respond to work so the test +fails. + +You can bypass `SimpleDelegate#method_missing` by extending the worker +with `MiniTest::Expectations`. You can either do that in your setup at +the instance level, like: + + before do + @worker = Worker.new(Object.new) + @worker.extend MiniTest::Expectations + end + +or you can extend the Worker class (within the test file!), like: + + class Worker + include ::MiniTest::Expectations + end + +== Known Extensions: + +capybara_minitest_spec :: Bridge between Capybara RSpec matchers and MiniTest::Spec expectations (e.g. page.must_have_content('Title')). +minispec-metadata :: Metadata for describe/it blocks + (e.g. `it 'requires JS driver', js: true do`) +minitest-ansi :: Colorize minitest output with ANSI colors. +minitest-around :: Around block for minitest. An alternative to setup/teardown dance. +minitest-capistrano :: Assertions and expectations for testing Capistrano recipes +minitest-capybara :: Capybara matchers support for minitest unit and spec +minitest-chef-handler :: Run Minitest suites as Chef report handlers +minitest-ci :: CI reporter plugin for MiniTest. +minitest-colorize :: Colorize MiniTest output and show failing tests instantly. +minitest-context :: Defines contexts for code reuse in MiniTest + specs that share common expectations. +minitest-debugger :: Wraps assert so failed assertions drop into + the ruby debugger. +minitest-display :: Patches MiniTest to allow for an easily configurable output. +minitest-emoji :: Print out emoji for your test passes, fails, and skips. +minitest-english :: Semantically symmetric aliases for assertions and expectations. +minitest-excludes :: Clean API for excluding certain tests you + don't want to run under certain conditions. +minitest-firemock :: Makes your MiniTest mocks more resilient. +minitest-great_expectations :: Generally useful additions to minitest's assertions and expectations +minitest-growl :: Test notifier for minitest via growl. +minitest-implicit-subject :: Implicit declaration of the test subject. +minitest-instrument :: Instrument ActiveSupport::Notifications when + test method is executed +minitest-instrument-db :: Store information about speed of test + execution provided by minitest-instrument in database +minitest-libnotify :: Test notifier for minitest via libnotify. +minitest-macruby :: Provides extensions to minitest for macruby UI testing. +minitest-matchers :: Adds support for RSpec-style matchers to minitest. +minitest-metadata :: Annotate tests with metadata (key-value). +minitest-mongoid :: Mongoid assertion matchers for MiniTest +minitest-must_not :: Provides must_not as an alias for wont in MiniTest +minitest-nc :: Test notifier for minitest via Mountain Lion's Notification Center +minitest-predicates :: Adds support for .predicate? methods +minitest-rails :: MiniTest integration for Rails 3.x +minitest-rails-capybara :: Capybara integration for MiniTest::Rails +minitest-reporters :: Create customizable MiniTest output formats +minitest-should_syntax :: RSpec-style +x.should == y+ assertions for MiniTest +minitest-shouldify :: Adding all manner of shoulds to MiniTest (bad idea) +minitest-spec-context :: Provides rspec-ish context method to MiniTest::Spec +minitest-spec-magic :: Minitest::Spec extensions for Rails and beyond +minitest-spec-rails :: Drop in MiniTest::Spec superclass for ActiveSupport::TestCase. +minitest-stub-const :: Stub constants for the duration of a block +minitest-tags :: add tags for minitest +minitest-wscolor :: Yet another test colorizer. +minitest_owrapper :: Get tests results as a TestResult object. +minitest_should :: Shoulda style syntax for minitest test::unit. +minitest_tu_shim :: minitest_tu_shim bridges between test/unit and minitest. +mongoid-minitest :: MiniTest matchers for Mongoid. +pry-rescue :: A pry plugin w/ minitest support. See pry-rescue/minitest.rb. + +== Unknown Extensions: + +Authors... Please send me a pull request with a description of your minitest extension. + +* assay-minitest +* detroit-minitest +* em-minitest-spec +* flexmock-minitest +* guard-minitest +* guard-minitest-decisiv +* minitest-activemodel +* minitest-ar-assertions +* minitest-capybara-unit +* minitest-colorer +* minitest-deluxe +* minitest-extra-assertions +* minitest-rails-shoulda +* minitest-spec +* minitest-spec-should +* minitest-sugar +* minitest_should +* mongoid-minitest +* spork-minitest + +== REQUIREMENTS: + +* Ruby 1.8, maybe even 1.6 or lower. No magic is involved. + +== INSTALL: + + sudo gem install minitest + +On 1.9, you already have it. To get newer candy you can still install +the gem, but you'll need to activate the gem explicitly to use it: + + require 'rubygems' + gem 'minitest' # ensures you're using the gem, and not the built in MT + require 'minitest/autorun' + + # ... usual testing stuffs ... + +DO NOTE: There is a serious problem with the way that ruby 1.9/2.0 +packages their own gems. They install a gem specification file, but +don't install the gem contents in the gem path. This messes up +Gem.find_files and many other things (gem which, gem contents, etc). + +Just install minitest as a gem for real and you'll be happier. + +== LICENSE: + +(The MIT License) + +Copyright (c) Ryan Davis, seattle.rb + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/jni/ruby/test/lib/minitest/autorun.rb b/jni/ruby/test/lib/minitest/autorun.rb new file mode 100644 index 0000000..88b1e05 --- /dev/null +++ b/jni/ruby/test/lib/minitest/autorun.rb @@ -0,0 +1,13 @@ +# encoding: utf-8 + +begin + require 'rubygems' + gem 'minitest' +rescue Gem::LoadError + # do nothing +end + +require 'minitest/unit' +require 'minitest/mock' + +MiniTest::Unit.autorun diff --git a/jni/ruby/test/lib/minitest/benchmark.rb b/jni/ruby/test/lib/minitest/benchmark.rb new file mode 100644 index 0000000..5195fc0 --- /dev/null +++ b/jni/ruby/test/lib/minitest/benchmark.rb @@ -0,0 +1,417 @@ +# encoding: utf-8 + +require 'minitest/unit' + +class MiniTest::Unit # :nodoc: + def run_benchmarks # :nodoc: + _run_anything :benchmark + end + + def benchmark_suite_header suite # :nodoc: + "\n#{suite}\t#{suite.bench_range.join("\t")}" + end + + class TestCase + ## + # Returns a set of ranges stepped exponentially from +min+ to + # +max+ by powers of +base+. Eg: + # + # bench_exp(2, 16, 2) # => [2, 4, 8, 16] + + def self.bench_exp min, max, base = 10 + min = (Math.log10(min) / Math.log10(base)).to_i + max = (Math.log10(max) / Math.log10(base)).to_i + + (min..max).map { |m| base ** m }.to_a + end + + ## + # Returns a set of ranges stepped linearly from +min+ to +max+ by + # +step+. Eg: + # + # bench_linear(20, 40, 10) # => [20, 30, 40] + + def self.bench_linear min, max, step = 10 + (min..max).step(step).to_a + rescue LocalJumpError # 1.8.6 + r = []; (min..max).step(step) { |n| r << n }; r + end + + ## + # Returns the benchmark methods (methods that start with bench_) + # for that class. + + def self.benchmark_methods # :nodoc: + public_instance_methods(true).grep(/^bench_/).map { |m| m.to_s }.sort + end + + ## + # Returns all test suites that have benchmark methods. + + def self.benchmark_suites + TestCase.test_suites.reject { |s| s.benchmark_methods.empty? } + end + + ## + # Specifies the ranges used for benchmarking for that class. + # Defaults to exponential growth from 1 to 10k by powers of 10. + # Override if you need different ranges for your benchmarks. + # + # See also: ::bench_exp and ::bench_linear. + + def self.bench_range + bench_exp 1, 10_000 + end + + ## + # Runs the given +work+, gathering the times of each run. Range + # and times are then passed to a given +validation+ proc. Outputs + # the benchmark name and times in tab-separated format, making it + # easy to paste into a spreadsheet for graphing or further + # analysis. + # + # Ranges are specified by ::bench_range. + # + # Eg: + # + # def bench_algorithm + # validation = proc { |x, y| ... } + # assert_performance validation do |n| + # @obj.algorithm(n) + # end + # end + + def assert_performance validation, &work + range = self.class.bench_range + + io.print "#{__name__}" + + times = [] + + range.each do |x| + GC.start + t0 = Time.now + instance_exec(x, &work) + t = Time.now - t0 + + io.print "\t%9.6f" % t + times << t + end + io.puts + + validation[range, times] + end + + ## + # Runs the given +work+ and asserts that the times gathered fit to + # match a constant rate (eg, linear slope == 0) within a given + # +threshold+. Note: because we're testing for a slope of 0, R^2 + # is not a good determining factor for the fit, so the threshold + # is applied against the slope itself. As such, you probably want + # to tighten it from the default. + # + # See http://www.graphpad.com/curvefit/goodness_of_fit.htm for + # more details. + # + # Fit is calculated by #fit_linear. + # + # Ranges are specified by ::bench_range. + # + # Eg: + # + # def bench_algorithm + # assert_performance_constant 0.9999 do |n| + # @obj.algorithm(n) + # end + # end + + def assert_performance_constant threshold = 0.99, &work + validation = proc do |range, times| + a, b, rr = fit_linear range, times + assert_in_delta 0, b, 1 - threshold + [a, b, rr] + end + + assert_performance validation, &work + end + + ## + # Runs the given +work+ and asserts that the times gathered fit to + # match a exponential curve within a given error +threshold+. + # + # Fit is calculated by #fit_exponential. + # + # Ranges are specified by ::bench_range. + # + # Eg: + # + # def bench_algorithm + # assert_performance_exponential 0.9999 do |n| + # @obj.algorithm(n) + # end + # end + + def assert_performance_exponential threshold = 0.99, &work + assert_performance validation_for_fit(:exponential, threshold), &work + end + + ## + # Runs the given +work+ and asserts that the times gathered fit to + # match a logarithmic curve within a given error +threshold+. + # + # Fit is calculated by #fit_logarithmic. + # + # Ranges are specified by ::bench_range. + # + # Eg: + # + # def bench_algorithm + # assert_performance_logarithmic 0.9999 do |n| + # @obj.algorithm(n) + # end + # end + + def assert_performance_logarithmic threshold = 0.99, &work + assert_performance validation_for_fit(:logarithmic, threshold), &work + end + + ## + # Runs the given +work+ and asserts that the times gathered fit to + # match a straight line within a given error +threshold+. + # + # Fit is calculated by #fit_linear. + # + # Ranges are specified by ::bench_range. + # + # Eg: + # + # def bench_algorithm + # assert_performance_linear 0.9999 do |n| + # @obj.algorithm(n) + # end + # end + + def assert_performance_linear threshold = 0.99, &work + assert_performance validation_for_fit(:linear, threshold), &work + end + + ## + # Runs the given +work+ and asserts that the times gathered curve + # fit to match a power curve within a given error +threshold+. + # + # Fit is calculated by #fit_power. + # + # Ranges are specified by ::bench_range. + # + # Eg: + # + # def bench_algorithm + # assert_performance_power 0.9999 do |x| + # @obj.algorithm + # end + # end + + def assert_performance_power threshold = 0.99, &work + assert_performance validation_for_fit(:power, threshold), &work + end + + ## + # Takes an array of x/y pairs and calculates the general R^2 value. + # + # See: http://en.wikipedia.org/wiki/Coefficient_of_determination + + def fit_error xys + y_bar = sigma(xys) { |x, y| y } / xys.size.to_f + ss_tot = sigma(xys) { |x, y| (y - y_bar) ** 2 } + ss_err = sigma(xys) { |x, y| (yield(x) - y) ** 2 } + + 1 - (ss_err / ss_tot) + end + + ## + # To fit a functional form: y = ae^(bx). + # + # Takes x and y values and returns [a, b, r^2]. + # + # See: http://mathworld.wolfram.com/LeastSquaresFittingExponential.html + + def fit_exponential xs, ys + n = xs.size + xys = xs.zip(ys) + sxlny = sigma(xys) { |x,y| x * Math.log(y) } + slny = sigma(xys) { |x,y| Math.log(y) } + sx2 = sigma(xys) { |x,y| x * x } + sx = sigma xs + + c = n * sx2 - sx ** 2 + a = (slny * sx2 - sx * sxlny) / c + b = ( n * sxlny - sx * slny ) / c + + return Math.exp(a), b, fit_error(xys) { |x| Math.exp(a + b * x) } + end + + ## + # To fit a functional form: y = a + b*ln(x). + # + # Takes x and y values and returns [a, b, r^2]. + # + # See: http://mathworld.wolfram.com/LeastSquaresFittingLogarithmic.html + + def fit_logarithmic xs, ys + n = xs.size + xys = xs.zip(ys) + slnx2 = sigma(xys) { |x,y| Math.log(x) ** 2 } + slnx = sigma(xys) { |x,y| Math.log(x) } + sylnx = sigma(xys) { |x,y| y * Math.log(x) } + sy = sigma(xys) { |x,y| y } + + c = n * slnx2 - slnx ** 2 + b = ( n * sylnx - sy * slnx ) / c + a = (sy - b * slnx) / n + + return a, b, fit_error(xys) { |x| a + b * Math.log(x) } + end + + + ## + # Fits the functional form: a + bx. + # + # Takes x and y values and returns [a, b, r^2]. + # + # See: http://mathworld.wolfram.com/LeastSquaresFitting.html + + def fit_linear xs, ys + n = xs.size + xys = xs.zip(ys) + sx = sigma xs + sy = sigma ys + sx2 = sigma(xs) { |x| x ** 2 } + sxy = sigma(xys) { |x,y| x * y } + + c = n * sx2 - sx**2 + a = (sy * sx2 - sx * sxy) / c + b = ( n * sxy - sx * sy ) / c + + return a, b, fit_error(xys) { |x| a + b * x } + end + + ## + # To fit a functional form: y = ax^b. + # + # Takes x and y values and returns [a, b, r^2]. + # + # See: http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html + + def fit_power xs, ys + n = xs.size + xys = xs.zip(ys) + slnxlny = sigma(xys) { |x, y| Math.log(x) * Math.log(y) } + slnx = sigma(xs) { |x | Math.log(x) } + slny = sigma(ys) { | y| Math.log(y) } + slnx2 = sigma(xs) { |x | Math.log(x) ** 2 } + + b = (n * slnxlny - slnx * slny) / (n * slnx2 - slnx ** 2); + a = (slny - b * slnx) / n + + return Math.exp(a), b, fit_error(xys) { |x| (Math.exp(a) * (x ** b)) } + end + + ## + # Enumerates over +enum+ mapping +block+ if given, returning the + # sum of the result. Eg: + # + # sigma([1, 2, 3]) # => 1 + 2 + 3 => 7 + # sigma([1, 2, 3]) { |n| n ** 2 } # => 1 + 4 + 9 => 14 + + def sigma enum, &block + enum = enum.map(&block) if block + enum.inject { |sum, n| sum + n } + end + + ## + # Returns a proc that calls the specified fit method and asserts + # that the error is within a tolerable threshold. + + def validation_for_fit msg, threshold + proc do |range, times| + a, b, rr = send "fit_#{msg}", range, times + assert_operator rr, :>=, threshold + [a, b, rr] + end + end + end +end + +class MiniTest::Spec + ## + # This is used to define a new benchmark method. You usually don't + # use this directly and is intended for those needing to write new + # performance curve fits (eg: you need a specific polynomial fit). + # + # See ::bench_performance_linear for an example of how to use this. + + def self.bench name, &block + define_method "bench_#{name.gsub(/\W+/, '_')}", &block + end + + ## + # Specifies the ranges used for benchmarking for that class. + # + # bench_range do + # bench_exp(2, 16, 2) + # end + # + # See Unit::TestCase.bench_range for more details. + + def self.bench_range &block + return super unless block + + meta = (class << self; self; end) + meta.send :define_method, "bench_range", &block + end + + ## + # Create a benchmark that verifies that the performance is linear. + # + # describe "my class" do + # bench_performance_linear "fast_algorithm", 0.9999 do |n| + # @obj.fast_algorithm(n) + # end + # end + + def self.bench_performance_linear name, threshold = 0.99, &work + bench name do + assert_performance_linear threshold, &work + end + end + + ## + # Create a benchmark that verifies that the performance is constant. + # + # describe "my class" do + # bench_performance_constant "zoom_algorithm!" do |n| + # @obj.zoom_algorithm!(n) + # end + # end + + def self.bench_performance_constant name, threshold = 0.99, &work + bench name do + assert_performance_constant threshold, &work + end + end + + ## + # Create a benchmark that verifies that the performance is exponential. + # + # describe "my class" do + # bench_performance_exponential "algorithm" do |n| + # @obj.algorithm(n) + # end + # end + + def self.bench_performance_exponential name, threshold = 0.99, &work + bench name do + assert_performance_exponential threshold, &work + end + end +end diff --git a/jni/ruby/test/lib/minitest/mock.rb b/jni/ruby/test/lib/minitest/mock.rb new file mode 100644 index 0000000..19acaff --- /dev/null +++ b/jni/ruby/test/lib/minitest/mock.rb @@ -0,0 +1,195 @@ +# encoding: utf-8 + +class MockExpectationError < StandardError; end # :nodoc: + +## +# A simple and clean mock object framework. + +module MiniTest # :nodoc: + + ## + # All mock objects are an instance of Mock + + class Mock + alias :__respond_to? :respond_to? + + skip_methods = %w(object_id respond_to_missing? inspect === to_s) + + instance_methods.each do |m| + undef_method m unless skip_methods.include?(m.to_s) || m =~ /^__/ + end + + def initialize # :nodoc: + @expected_calls = Hash.new { |calls, name| calls[name] = [] } + @actual_calls = Hash.new { |calls, name| calls[name] = [] } + end + + ## + # Expect that method +name+ is called, optionally with +args+ or a + # +blk+, and returns +retval+. + # + # @mock.expect(:meaning_of_life, 42) + # @mock.meaning_of_life # => 42 + # + # @mock.expect(:do_something_with, true, [some_obj, true]) + # @mock.do_something_with(some_obj, true) # => true + # + # @mock.expect(:do_something_else, true) do |a1, a2| + # a1 == "buggs" && a2 == :bunny + # end + # + # +args+ is compared to the expected args using case equality (ie, the + # '===' operator), allowing for less specific expectations. + # + # @mock.expect(:uses_any_string, true, [String]) + # @mock.uses_any_string("foo") # => true + # @mock.verify # => true + # + # @mock.expect(:uses_one_string, true, ["foo"] + # @mock.uses_one_string("bar") # => true + # @mock.verify # => raises MockExpectationError + + def expect(name, retval, args=[], &blk) + if block_given? + raise ArgumentError, "args ignored when block given" unless args.empty? + @expected_calls[name] << { :retval => retval, :block => blk } + else + raise ArgumentError, "args must be an array" unless Array === args + @expected_calls[name] << { :retval => retval, :args => args } + end + self + end + + def __call name, data # :nodoc: + case data + when Hash then + "#{name}(#{data[:args].inspect[1..-2]}) => #{data[:retval].inspect}" + else + data.map { |d| __call name, d }.join ", " + end + end + + ## + # Verify that all methods were called as expected. Raises + # +MockExpectationError+ if the mock object was not called as + # expected. + + def verify + @expected_calls.each do |name, calls| + calls.each do |expected| + msg1 = "expected #{__call name, expected}" + msg2 = "#{msg1}, got [#{__call name, @actual_calls[name]}]" + + raise MockExpectationError, msg2 if + @actual_calls.has_key?(name) and + not @actual_calls[name].include?(expected) + + raise MockExpectationError, msg1 unless + @actual_calls.has_key?(name) and + @actual_calls[name].include?(expected) + end + end + true + end + + def method_missing(sym, *args) # :nodoc: + unless @expected_calls.has_key?(sym) then + raise NoMethodError, "unmocked method %p, expected one of %p" % + [sym, @expected_calls.keys.sort_by(&:to_s)] + end + + index = @actual_calls[sym].length + expected_call = @expected_calls[sym][index] + + unless expected_call then + raise MockExpectationError, "No more expects available for %p: %p" % + [sym, args] + end + + expected_args, retval, val_block = + expected_call.values_at(:args, :retval, :block) + + if val_block then + raise MockExpectationError, "mocked method %p failed block w/ %p" % + [sym, args] unless val_block.call(args) + + # keep "verify" happy + @actual_calls[sym] << expected_call + return retval + end + + if expected_args.size != args.size then + raise ArgumentError, "mocked method %p expects %d arguments, got %d" % + [sym, expected_args.size, args.size] + end + + fully_matched = expected_args.zip(args).all? { |mod, a| + mod === a or mod == a + } + + unless fully_matched then + raise MockExpectationError, "mocked method %p called with unexpected arguments %p" % + [sym, args] + end + + @actual_calls[sym] << { + :retval => retval, + :args => expected_args.zip(args).map { |mod, a| mod === a ? mod : a } + } + + retval + end + + def respond_to?(sym, include_private = false) # :nodoc: + return true if @expected_calls.has_key?(sym.to_sym) + return __respond_to?(sym, include_private) + end + end +end + +class Object # :nodoc: + + ## + # Add a temporary stubbed method replacing +name+ for the duration + # of the +block+. If +val_or_callable+ responds to #call, then it + # returns the result of calling it, otherwise returns the value + # as-is. Cleans up the stub at the end of the +block+. The method + # +name+ must exist before stubbing. + # + # def test_stale_eh + # obj_under_test = Something.new + # refute obj_under_test.stale? + # + # Time.stub :now, Time.at(0) do + # assert obj_under_test.stale? + # end + # end + + def stub name, val_or_callable, &block + new_name = "__minitest_stub__#{name}" + + metaclass = class << self; self; end + + if respond_to? name and not methods.map(&:to_s).include? name.to_s then + metaclass.send :define_method, name do |*args| + super(*args) + end + end + + metaclass.send :alias_method, new_name, name + + metaclass.send :define_method, name do |*args| + if val_or_callable.respond_to? :call then + val_or_callable.call(*args) + else + val_or_callable + end + end + + yield self + ensure + metaclass.send :undef_method, name + metaclass.send :alias_method, name, new_name + metaclass.send :undef_method, new_name + end +end diff --git a/jni/ruby/test/lib/minitest/unit.rb b/jni/ruby/test/lib/minitest/unit.rb new file mode 100644 index 0000000..0a6558d --- /dev/null +++ b/jni/ruby/test/lib/minitest/unit.rb @@ -0,0 +1,1419 @@ +# encoding: utf-8 + +require "optparse" +require "rbconfig" +require "leakchecker" + +## +# Minimal (mostly drop-in) replacement for test-unit. +# +# :include: README.txt + +module MiniTest + + def self.const_missing name # :nodoc: + case name + when :MINI_DIR then + msg = "MiniTest::MINI_DIR was removed. Don't violate other's internals." + warn "WAR\NING: #{msg}" + warn "WAR\NING: Used by #{caller.first}." + const_set :MINI_DIR, "bad value" + else + super + end + end + + ## + # Assertion base class + + class Assertion < Exception; end + + ## + # Assertion raised when skipping a test + + class Skip < Assertion; end + + class << self + ## + # Filter object for backtraces. + + attr_accessor :backtrace_filter + end + + class BacktraceFilter # :nodoc: + def filter bt + return ["No backtrace"] unless bt + + new_bt = [] + + unless $DEBUG then + bt.each do |line| + break if line =~ /lib\/minitest/ + new_bt << line + end + + new_bt = bt.reject { |line| line =~ /lib\/minitest/ } if new_bt.empty? + new_bt = bt.dup if new_bt.empty? + else + new_bt = bt.dup + end + + new_bt + end + end + + self.backtrace_filter = BacktraceFilter.new + + def self.filter_backtrace bt # :nodoc: + backtrace_filter.filter bt + end + + ## + # MiniTest Assertions. All assertion methods accept a +msg+ which is + # printed if the assertion fails. + + module Assertions + UNDEFINED = Object.new # :nodoc: + + def UNDEFINED.inspect # :nodoc: + "UNDEFINED" # again with the rdoc bugs... :( + end + + ## + # Returns the diff command to use in #diff. Tries to intelligently + # figure out what diff to use. + + def self.diff + @diff = if (RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ && + system("diff.exe", __FILE__, __FILE__)) then + "diff.exe -u" + elsif Minitest::Unit::Guard.maglev? then # HACK + "diff -u" + elsif system("gdiff", __FILE__, __FILE__) + "gdiff -u" # solaris and kin suck + elsif system("diff", __FILE__, __FILE__) + "diff -u" + else + nil + end unless defined? @diff + + @diff + end + + ## + # 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_a = nil + tempfile_b = nil + + Tempfile.open("expect") do |a| + tempfile_a = a + a.puts expect + a.flush + + Tempfile.open("butwas") do |b| + tempfile_b = 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 in the #{klass}#inspect output.\n", + "You should look at the implementation of #== on ", + "#{klass} or its members.\n", + expect, + ].join + end + end + end + + result + ensure + tempfile_a.close! if tempfile_a + tempfile_b.close! if tempfile_b + 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.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 + mu_pp(obj).gsub(/\\n/, "\n").gsub(/:0x[a-fA-F0-9]{4,}/m, ':0xXXXXXX') + end + + def _assertions= n # :nodoc: + @_assertions = n + end + + def _assertions # :nodoc: + @_assertions ||= 0 + end + + ## + # Fails unless +test+ is a true value. + + def assert test, msg = nil + msg ||= "Failed assertion, no message given." + self._assertions += 1 + unless test then + msg = msg.call if Proc === msg + raise MiniTest::Assertion, msg + end + true + end + + ## + # Fails unless +obj+ is empty. + + def assert_empty obj, msg = nil + msg = message(msg) { "Expected #{mu_pp(obj)} to be empty" } + assert_respond_to obj, :empty? + assert obj.empty?, msg + end + + ## + # 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. + # + # See also: MiniTest::Assertions.diff + + def assert_equal exp, act, msg = nil + msg = message(msg, "") { diff exp, act } + assert exp == act, msg + end + + ## + # For comparing Floats. Fails unless +exp+ and +act+ are within +delta+ + # of each other. + # + # assert_in_delta Math::PI, (22.0 / 7.0), 0.01 + + def assert_in_delta exp, act, delta = 0.001, msg = nil + n = (exp - act).abs + msg = message(msg) { + "Expected |#{exp} - #{act}| (#{n}) to be <= #{delta}" + } + assert delta >= n, msg + end + + ## + # For comparing Floats. Fails unless +exp+ and +act+ have a relative + # error less than +epsilon+. + + def assert_in_epsilon a, b, epsilon = 0.001, msg = nil + assert_in_delta a, b, [a.abs, b.abs].min * epsilon, msg + end + + ## + # Fails unless +collection+ includes +obj+. + + def assert_includes collection, obj, msg = nil + msg = message(msg) { + "Expected #{mu_pp(collection)} to include #{mu_pp(obj)}" + } + assert_respond_to collection, :include? + assert collection.include?(obj), msg + end + + ## + # Fails unless +obj+ is an instance of +cls+. + + def assert_instance_of cls, obj, msg = nil + msg = message(msg) { + "Expected #{mu_pp(obj)} to be an instance of #{cls}, not #{obj.class}" + } + + assert obj.instance_of?(cls), msg + end + + ## + # Fails unless +obj+ is a kind of +cls+. + + def assert_kind_of cls, obj, msg = nil # TODO: merge with instance_of + msg = message(msg) { + "Expected #{mu_pp(obj)} to be a kind of #{cls}, not #{obj.class}" } + + assert obj.kind_of?(cls), msg + end + + ## + # Fails unless +matcher+ <tt>=~</tt> +obj+. + + def assert_match matcher, obj, msg = nil + msg = message(msg) { "Expected #{mu_pp matcher} to match #{mu_pp obj}" } + assert_respond_to matcher, :"=~" + matcher = Regexp.new Regexp.escape matcher if String === matcher + assert matcher =~ obj, msg + end + + ## + # Fails unless +obj+ is nil + + def assert_nil obj, msg = nil + msg = message(msg) { "Expected #{mu_pp(obj)} to be nil" } + assert obj.nil?, msg + end + + ## + # For testing with binary operators. + # + # assert_operator 5, :<=, 4 + + def assert_operator o1, op, o2 = UNDEFINED, msg = nil + return assert_predicate o1, op, msg if UNDEFINED == o2 + msg = message(msg) { "Expected #{mu_pp(o1)} to be #{op} #{mu_pp(o2)}" } + assert o1.__send__(op, o2), msg + 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. Pass in a regexp if you want + # to pattern match. + # + # NOTE: this uses #capture_io, not #capture_subprocess_io. + # + # See also: #assert_silent + + def assert_output stdout = nil, stderr = nil + out, err = capture_io do + yield + end + + err_msg = Regexp === stderr ? :assert_match : :assert_equal if stderr + out_msg = Regexp === stdout ? :assert_match : :assert_equal if stdout + + y = send err_msg, stderr, err, "In stderr" if err_msg + x = send out_msg, stdout, out, "In stdout" if out_msg + + (!stdout || x) && (!stderr || y) + end + + ## + # For testing with predicates. + # + # assert_predicate str, :empty? + # + # This is really meant for specs and is front-ended by assert_operator: + # + # str.must_be :empty? + + def assert_predicate o1, op, msg = nil + msg = message(msg) { "Expected #{mu_pp(o1)} to be #{op}" } + assert o1.__send__(op), msg + end + + ## + # Fails unless the block raises one of +exp+. Returns the + # exception matched so you can check the message, attributes, etc. + + def assert_raises *exp + msg = "#{exp.pop}.\n" if String === exp.last + + 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, "#{msg}#{mu_pp(exp)} exception expected, not") + } + + return e + end + + exp = exp.first if exp.size == 1 + + flunk "#{msg}#{mu_pp(exp)} expected but nothing was raised." + end + + ## + # Fails unless +obj+ responds to +meth+. + + def assert_respond_to obj, meth, msg = nil + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}" + } + assert obj.respond_to?(meth), msg + end + + ## + # Fails unless +exp+ and +act+ are #equal? + + def assert_same exp, act, msg = nil + msg = message(msg) { + data = [mu_pp(act), act.object_id, mu_pp(exp), exp.object_id] + "Expected %s (oid=%d) to be the same as %s (oid=%d)" % data + } + assert exp.equal?(act), msg + end + + ## + # +send_ary+ is a receiver, message and arguments. + # + # Fails unless the call returns a true value + # TODO: I should prolly remove this from specs + + def assert_send send_ary, m = nil + recv, msg, *args = send_ary + m = message(m) { + "Expected #{mu_pp(recv)}.#{msg}(*#{mu_pp(args)}) to return true" } + assert recv.__send__(msg, *args), m + 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 + default = "Expected #{mu_pp(sym)} to have been thrown" + caught = true + catch(sym) do + begin + yield + rescue ThreadError => e # wtf?!? 1.8 + threads == suck + default += ", not \:#{e.message[/uncaught throw \`(\w+?)\'/, 1]}" + rescue ArgumentError => e # 1.9 exception + default += ", not #{e.message.split(/ /).last}" + rescue NameError => e # 1.8 exception + default += ", not #{e.name.inspect}" + end + caught = false + end + + assert caught, message(msg) { default } + end + + ## + # Captures $stdout and $stderr into strings: + # + # out, err = capture_io do + # puts "Some info" + # warn "You did a bad thing" + # end + # + # assert_match %r%info%, out + # assert_match %r%bad%, err + # + # NOTE: For efficiency, this method uses StringIO and does not + # capture IO for subprocesses. Use #capture_subprocess_io for + # that. + + def capture_io + require 'stringio' + + captured_stdout, captured_stderr = StringIO.new, StringIO.new + + synchronize do + orig_stdout, orig_stderr = $stdout, $stderr + $stdout, $stderr = captured_stdout, captured_stderr + + begin + yield + ensure + $stdout = orig_stdout + $stderr = orig_stderr + end + end + + return captured_stdout.string, captured_stderr.string + end + + ## + # Captures $stdout and $stderr into strings, using Tempfile to + # ensure that subprocess IO is captured as well. + # + # out, err = capture_subprocess_io do + # system "echo Some info" + # system "echo You did a bad thing 1>&2" + # end + # + # assert_match %r%info%, out + # assert_match %r%bad%, err + # + # NOTE: This method is approximately 10x slower than #capture_io so + # only use it when you need to test the output of a subprocess. + + def capture_subprocess_io + require 'tempfile' + + captured_stdout, captured_stderr = Tempfile.new("out"), Tempfile.new("err") + + synchronize do + orig_stdout, orig_stderr = $stdout.dup, $stderr.dup + $stdout.reopen captured_stdout + $stderr.reopen captured_stderr + + begin + yield + + $stdout.rewind + $stderr.rewind + + [captured_stdout.read, captured_stderr.read] + ensure + $stdout.reopen orig_stdout + $stderr.reopen orig_stderr + orig_stdout.close + orig_stderr.close + captured_stdout.close! + captured_stderr.close! + end + end + end + + ## + # Returns details for exception +e+ + + def exception_details e, msg + [ + "#{msg}", + "Class: <#{e.class}>", + "Message: <#{e.message.inspect}>", + "---Backtrace---", + "#{MiniTest::filter_backtrace(e.backtrace).join("\n")}", + "---------------", + ].join "\n" + end + + ## + # Fails with +msg+ + + def flunk msg = nil + msg ||= "Epic Fail!" + assert false, msg + end + + ## + # Returns a proc that will output +msg+ along with the default message. + + def message msg = nil, ending = ".", &default + proc { + msg = msg.call.chomp(".") if Proc === msg + custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty? + "#{custom_message}#{default.call}#{ending}" + } + end + + ## + # used for counting assertions + + def pass msg = nil + assert true + end + + ## + # Fails if +test+ is a true value + + def refute test, msg = nil + msg ||= "Failed refutation, no message given" + not assert(! test, msg) + end + + ## + # Fails if +obj+ is empty. + + def refute_empty obj, msg = nil + msg = message(msg) { "Expected #{mu_pp(obj)} to not be empty" } + assert_respond_to obj, :empty? + refute obj.empty?, msg + end + + ## + # Fails if <tt>exp == act</tt>. + # + # For floats use refute_in_delta. + + def refute_equal exp, act, msg = nil + msg = message(msg) { + "Expected #{mu_pp(act)} to not be equal to #{mu_pp(exp)}" + } + refute exp == act, msg + end + + ## + # For comparing Floats. Fails if +exp+ is within +delta+ of +act+. + # + # refute_in_delta Math::PI, (22.0 / 7.0) + + def refute_in_delta exp, act, delta = 0.001, msg = nil + n = (exp - act).abs + msg = message(msg) { + "Expected |#{exp} - #{act}| (#{n}) to not be <= #{delta}" + } + refute delta >= n, msg + end + + ## + # For comparing Floats. Fails if +exp+ and +act+ have a relative error + # less than +epsilon+. + + def refute_in_epsilon a, b, epsilon = 0.001, msg = nil + refute_in_delta a, b, a * epsilon, msg + end + + ## + # Fails if +collection+ includes +obj+. + + def refute_includes collection, obj, msg = nil + msg = message(msg) { + "Expected #{mu_pp(collection)} to not include #{mu_pp(obj)}" + } + assert_respond_to collection, :include? + refute collection.include?(obj), msg + end + + ## + # Fails if +obj+ is an instance of +cls+. + + def refute_instance_of cls, obj, msg = nil + msg = message(msg) { + "Expected #{mu_pp(obj)} to not be an instance of #{cls}" + } + refute obj.instance_of?(cls), msg + end + + ## + # Fails if +obj+ is a kind of +cls+. + + def refute_kind_of cls, obj, msg = nil # TODO: merge with instance_of + msg = message(msg) { "Expected #{mu_pp(obj)} to not be a kind of #{cls}" } + refute obj.kind_of?(cls), msg + end + + ## + # Fails if +matcher+ <tt>=~</tt> +obj+. + + def refute_match matcher, obj, msg = nil + msg = message(msg) {"Expected #{mu_pp matcher} to not match #{mu_pp obj}"} + assert_respond_to matcher, :"=~" + matcher = Regexp.new Regexp.escape matcher if String === matcher + refute matcher =~ obj, msg + end + + ## + # Fails if +obj+ is nil. + + def refute_nil obj, msg = nil + msg = message(msg) { "Expected #{mu_pp(obj)} to not be nil" } + refute obj.nil?, msg + end + + ## + # Fails if +o1+ is not +op+ +o2+. Eg: + # + # refute_operator 1, :>, 2 #=> pass + # refute_operator 1, :<, 2 #=> fail + + def refute_operator o1, op, o2 = UNDEFINED, msg = nil + return refute_predicate o1, op, msg if UNDEFINED == o2 + msg = message(msg) { "Expected #{mu_pp(o1)} to not be #{op} #{mu_pp(o2)}"} + refute o1.__send__(op, o2), msg + end + + ## + # For testing with predicates. + # + # refute_predicate str, :empty? + # + # This is really meant for specs and is front-ended by refute_operator: + # + # str.wont_be :empty? + + def refute_predicate o1, op, msg = nil + msg = message(msg) { "Expected #{mu_pp(o1)} to not be #{op}" } + refute o1.__send__(op), msg + end + + ## + # Fails if +obj+ responds to the message +meth+. + + def refute_respond_to obj, meth, msg = nil + msg = message(msg) { "Expected #{mu_pp(obj)} to not respond to #{meth}" } + + refute obj.respond_to?(meth), msg + end + + ## + # Fails if +exp+ is the same (by object identity) as +act+. + + def refute_same exp, act, msg = nil + msg = message(msg) { + data = [mu_pp(act), act.object_id, mu_pp(exp), exp.object_id] + "Expected %s (oid=%d) to not be the same as %s (oid=%d)" % data + } + refute exp.equal?(act), msg + end + + ## + # Skips the current test. Gets listed at the end of the run but + # doesn't cause a failure exit code. + + def skip msg = nil, bt = caller + msg ||= "Skipped, no message given" + @skip = true + raise MiniTest::Skip, msg, bt + end + + ## + # Was this testcase skipped? Meant for #teardown. + + def skipped? + defined?(@skip) and @skip + end + + ## + # Takes a block and wraps it with the runner's shared mutex. + + def synchronize + Minitest::Unit.runner.synchronize do + yield + end + end + end + + class Unit # :nodoc: + VERSION = "4.7.5" # :nodoc: + + attr_accessor :report, :failures, :errors, :skips # :nodoc: + attr_accessor :assertion_count # :nodoc: + attr_writer :test_count # :nodoc: + attr_accessor :start_time # :nodoc: + attr_accessor :help # :nodoc: + attr_accessor :verbose # :nodoc: + attr_writer :options # :nodoc: + + ## + # :attr: + # + # if true, installs an "INFO" signal handler (only available to BSD and + # OS X users) which prints diagnostic information about the test run. + # + # This is auto-detected by default but may be overridden by custom + # runners. + + attr_accessor :info_signal + + ## + # Lazy accessor for options. + + def options + @options ||= {} + end + + @@installed_at_exit ||= false + @@out = $stdout + @@after_tests = [] + + ## + # A simple hook allowing you to run a block of code after _all_ of + # the tests are done. Eg: + # + # MiniTest::Unit.after_tests { p $debugging_info } + + def self.after_tests &block + @@after_tests << block + end + + ## + # Registers MiniTest::Unit to run tests at process exit + + def self.autorun + at_exit { + # don't run if there was a non-exit exception + next if $! and not $!.kind_of? SystemExit + + # the order here is important. The at_exit handler must be + # installed before anyone else gets a chance to install their + # own, that way we can be assured that our exit will be last + # to run (at_exit stacks). + exit_code = nil + + at_exit { + @@after_tests.reverse_each(&:call) + exit false if exit_code && exit_code != 0 + } + + 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 + + ## + # 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 + + ## + # Return the IO for output. + + def output + self.class.output + end + + def puts *a # :nodoc: + output.puts(*a) + end + + def print *a # :nodoc: + output.print(*a) + end + + def test_count # :nodoc: + @test_count ||= 0 + end + + ## + # Runner for a given +type+ (eg, test vs bench). + + 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 + + ## + # Runs all the +suites+ for a given +type+. + # + + def _run_suites suites, type + suites.map { |suite| _run_suite suite, type } + end + + ## + # Run a single +suite+ for a given +type+. + + 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 =~ /\/(.*)\// + + all_test_methods = suite.send "#{type}_methods" + + filtered_test_methods = all_test_methods.find_all { |m| + filter === m || filter === "#{suite}##{m}" + } + + leakchecker = LeakChecker.new + + assertions = filtered_test_methods.map { |method| + inst = suite.new method + inst._assertions = 0 + + print "#{suite}##{method} = " if @verbose + + start_time = Time.now if @verbose + result = inst.run self + + print "%.2f s = " % (Time.now - start_time) if @verbose + print result + puts if @verbose + $stdout.flush + + leakchecker.check("#{inst.class}\##{inst.__name__}") + + inst._assertions + } + + return assertions.size, assertions.inject(0) { |sum, n| sum + n } + end + + ## + # Record the result of a single test. Makes it very easy to gather + # information. Eg: + # + # class StatisticsRecorder < MiniTest::Unit + # def record suite, method, assertions, time, error + # # ... record the results somewhere ... + # end + # end + # + # MiniTest::Unit.runner = StatisticsRecorder.new + # + # NOTE: record might be sent more than once per test. It will be + # sent once with the results from the test itself. If there is a + # failure or error in teardown, it will be sent again with the + # error or failure. + + def record suite, method, assertions, time, error + end + + def location e # :nodoc: + last_before_assertion = "" + e.backtrace.reverse_each do |s| + break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/ + last_before_assertion = s + end + last_before_assertion.sub(/:in .*$/, '') + end + + ## + # Writes status for failed test +meth+ in +klass+ which finished with + # exception +e+ + + def puke klass, meth, e + e = case e + when MiniTest::Skip then + @skips += 1 + return "S" unless @verbose + "Skipped:\n#{klass}##{meth} [#{location e}]:\n#{e.message}\n" + when MiniTest::Assertion then + @failures += 1 + "Failure:\n#{klass}##{meth} [#{location e}]:\n#{e.message}\n" + else + @errors += 1 + bt = MiniTest::filter_backtrace(e.backtrace).join "\n " + "Error:\n#{klass}##{meth}:\n#{e.class}: #{e.message}\n #{bt}\n" + end + @report << e + e[0, 1] + end + + def initialize # :nodoc: + @report = [] + @errors = @failures = @skips = 0 + @verbose = false + @mutex = defined?(Mutex) ? Mutex.new : nil + @info_signal = Signal.list['INFO'] + end + + def synchronize # :nodoc: + if @mutex then + @mutex.synchronize { yield } + else + yield + end + end + + def process_args args = [] # :nodoc: + options = {} + orig_args = args.dup + + OptionParser.new do |opts| + opts.banner = 'minitest options:' + opts.version = MiniTest::Unit::VERSION + + opts.on '-h', '--help', 'Display this help.' do + puts opts + exit + end + + opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m| + options[:seed] = m.to_i + end + + opts.on '-v', '--verbose', "Verbose. Show progress processing files." do + options[:verbose] = true + end + + opts.on '-n', '--name PATTERN', "Filter test names on pattern (e.g. /foo/)" do |a| + options[:filter] = a + end + + opts.parse! args + orig_args -= args + end + + unless options[:seed] then + srand + options[:seed] = srand % 0xFFFF + orig_args << "--seed" << options[:seed].to_s + end + + srand options[:seed] + + self.verbose = options[:verbose] + @help = orig_args.map { |s| s =~ /[\s|&<>$()]/ ? s.inspect : s }.join " " + + options + end + + ## + # Begins the full test run. Delegates to +runner+'s #_run method. + + def run args = [] + self.class.runner._run(args) + end + + ## + # Top level driver, controls all output and filtering. + + def _run args = [] + args = process_args args # ARGH!! blame test/unit process_args + self.options.merge! args + + puts "Run options: #{help}" + + self.class.plugins.each do |plugin| + send plugin + break unless report.empty? + end + + return failures + errors if self.test_count > 0 # or return nil... + rescue Interrupt + abort 'Interrupted' + end + + ## + # Runs test suites matching +filter+. + + def run_tests + _run_anything :test + end + + ## + # Writes status to +io+ + + 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 + + ## + # Provides a simple set of guards that you can use in your tests + # to skip execution if it is not applicable. These methods are + # mixed into TestCase as both instance and class methods so you + # can use them inside or outside of the test methods. + # + # def test_something_for_mri + # skip "bug 1234" if jruby? + # # ... + # end + # + # if windows? then + # # ... lots of test methods ... + # end + + module Guard + + ## + # Is this running on jruby? + + def jruby? platform = RUBY_PLATFORM + "java" == platform + end + + ## + # Is this running on mri? + + def maglev? platform = defined?(RUBY_ENGINE) && RUBY_ENGINE + "maglev" == platform + end + + module_function :maglev? + + ## + # Is this running on mri? + + def mri? platform = RUBY_DESCRIPTION + /^ruby/ =~ platform + end + + ## + # Is this running on rubinius? + + def rubinius? platform = defined?(RUBY_ENGINE) && RUBY_ENGINE + "rbx" == platform + end + + ## + # Is this running on windows? + + def windows? platform = RUBY_PLATFORM + /mswin|mingw/ =~ platform + end + end + + ## + # Provides before/after hooks for setup and teardown. These are + # meant for library writers, NOT for regular test authors. See + # #before_setup for an example. + + module LifecycleHooks + ## + # Runs before every test, after setup. This hook is meant for + # libraries to extend minitest. It is not meant to be used by + # test developers. + # + # See #before_setup for an example. + + def after_setup; end + + ## + # Runs before every test, before setup. This hook is meant for + # libraries to extend minitest. It is not meant to be used by + # test developers. + # + # As a simplistic example: + # + # module MyMinitestPlugin + # def before_setup + # super + # # ... stuff to do before setup is run + # end + # + # def after_setup + # # ... stuff to do after setup is run + # super + # end + # + # def before_teardown + # super + # # ... stuff to do before teardown is run + # end + # + # def after_teardown + # # ... stuff to do after teardown is run + # super + # end + # end + # + # class MiniTest::Unit::TestCase + # include MyMinitestPlugin + # end + + def before_setup; end + + ## + # Runs after every test, before teardown. This hook is meant for + # libraries to extend minitest. It is not meant to be used by + # test developers. + # + # See #before_setup for an example. + + def before_teardown; end + + ## + # Runs after every test, after teardown. This hook is meant for + # libraries to extend minitest. It is not meant to be used by + # test developers. + # + # See #before_setup for an example. + + def after_teardown; end + end + + ## + # Subclass TestCase to create your own tests. Typically you'll want a + # TestCase subclass per implementation class. + # + # See MiniTest::Assertions + + class TestCase + include LifecycleHooks + include Guard + extend Guard + + attr_reader :__name__ # :nodoc: + + PASSTHROUGH_EXCEPTIONS = [NoMemoryError, SignalException, + Interrupt, SystemExit] # :nodoc: + + ## + # Runs the tests reporting the status to +runner+ + + def run runner + trap "INFO" do + runner.report.each_with_index do |msg, i| + warn "\n%3d) %s" % [i + 1, msg] + end + warn '' + time = runner.start_time ? Time.now - runner.start_time : 0 + warn "Current Test: %s#%s %.2fs" % [self.class, self.__name__, time] + runner.status $stderr + end if runner.info_signal + + start_time = Time.now + + result = "" + begin + @passed = nil + self.before_setup + self.setup + self.after_setup + self.run_test self.__name__ + result = "." unless io? + time = Time.now - start_time + runner.record self.class, self.__name__, self._assertions, time, nil + @passed = true + rescue *PASSTHROUGH_EXCEPTIONS + raise + rescue Exception => e + @passed = Skip === e + time = Time.now - start_time + runner.record self.class, self.__name__, self._assertions, time, e + result = runner.puke self.class, self.__name__, e + ensure + %w{ before_teardown teardown after_teardown }.each do |hook| + begin + self.send hook + rescue *PASSTHROUGH_EXCEPTIONS + raise + rescue Exception => e + @passed = false + runner.record self.class, self.__name__, self._assertions, time, e + result = runner.puke self.class, self.__name__, e + end + end + trap 'INFO', 'DEFAULT' if runner.info_signal + end + result + end + + alias :run_test :__send__ + + def initialize name # :nodoc: + @__name__ = name + @__io__ = nil + @passed = nil + @@current = self # FIX: make thread local + end + + def self.current # :nodoc: + @@current # FIX: make thread local + end + + ## + # Return the output IO object + + def io + @__io__ = true + MiniTest::Unit.output + end + + ## + # Have we hooked up the IO yet? + + 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 + undef_method :test_order if method_defined? :test_order + define_method :test_order do :alpha end + end + end + + ## + # Make diffs for this TestCase use #pretty_inspect so that diff + # in assert_equal can be more details. NOTE: this is much slower + # than the regular inspect but much more usable for complex + # objects. + + def self.make_my_diffs_pretty! + require 'pp' + + define_method :mu_pp do |o| + o.pretty_inspect + end + end + + def self.inherited klass # :nodoc: + @@test_suites[klass] = true + super + end + + def self.test_order # :nodoc: + :random + end + + def self.test_suites # :nodoc: + @@test_suites.keys.sort_by { |ts| ts.name.to_s } + end + + def self.test_methods # :nodoc: + methods = public_instance_methods(true).grep(/^test/).map { |m| m.to_s } + + case self.test_order + when :parallel + max = methods.size + ParallelEach.new methods.sort.sort_by { rand max } + when :random then + max = methods.size + methods.sort.sort_by { rand max } + when :alpha, :sorted then + methods.sort + else + raise "Unknown test_order: #{self.test_order.inspect}" + end + end + + ## + # Returns true if the test passed. + + def passed? + @passed + end + + ## + # Runs before every test. Use this to set up before each test + # run. + + def setup; end + + ## + # Runs after every test. Use this to clean up after each test + # run. + + def teardown; end + + include MiniTest::Assertions + end # class TestCase + end # class Unit + + Test = Unit::TestCase +end # module MiniTest + +Minitest = MiniTest # :nodoc: because ugh... I typo this all the time diff --git a/jni/ruby/test/lib/profile_test_all.rb b/jni/ruby/test/lib/profile_test_all.rb new file mode 100644 index 0000000..08de7bb --- /dev/null +++ b/jni/ruby/test/lib/profile_test_all.rb @@ -0,0 +1,90 @@ +# +# purpose: +# Profile memory usage of each tests. +# +# usage: +# RUBY_TEST_ALL_PROFILE=[file] make test-all +# +# output: +# [file] specified by RUBY_TEST_ALL_PROFILE +# If [file] is 'true', then it is ./test_all_profile +# +# collected information: +# - ObjectSpace.memsize_of_all +# - GC.stat +# - /proc/meminfo (some fields, if exists) +# - /proc/self/status (some fields, if exists) +# - /proc/self/statm (if exists) +# + +require 'objspace' + +class MiniTest::Unit::TestCase + alias orig_run run + + file = ENV['RUBY_TEST_ALL_PROFILE'] + file = 'test-all-profile-result' if file == 'true' + TEST_ALL_PROFILE_OUT = open(file, 'w') + TEST_ALL_PROFILE_GC_STAT_HASH = {} + TEST_ALL_PROFILE_BANNER = ['name'] + TEST_ALL_PROFILE_PROCS = [] + + def self.add *name, &b + TEST_ALL_PROFILE_BANNER.concat name + TEST_ALL_PROFILE_PROCS << b + end + + add 'failed?' do |result, tc| + result << (tc.passed? ? 0 : 1) + end + + add 'memsize_of_all' do |result, *| + result << ObjectSpace.memsize_of_all + end + + add *GC.stat.keys do |result, *| + GC.stat(TEST_ALL_PROFILE_GC_STAT_HASH) + result.concat TEST_ALL_PROFILE_GC_STAT_HASH.values + end + + def self.add_proc_meminfo file, fields + return unless FileTest.exist?(file) + regexp = /(#{fields.join("|")}):\s*(\d+) kB/ + # check = {}; fields.each{|e| check[e] = true} + add *fields do |result, *| + text = File.read(file) + text.scan(regexp){ + # check.delete $1 + result << $2 + '' + } + # raise check.inspect unless check.empty? + end + end + + add_proc_meminfo '/proc/meminfo', %w(MemTotal MemFree) + add_proc_meminfo '/proc/self/status', %w(VmPeak VmSize VmHWM VmRSS) + + if FileTest.exist?('/proc/self/statm') + add *%w(size resident share text lib data dt) do |result, *| + result.concat File.read('/proc/self/statm').split(/\s+/) + end + end + + def memprofile_test_all_result_result + result = ["#{self.class}\##{self.__name__.to_s.gsub(/\s+/, '')}"] + TEST_ALL_PROFILE_PROCS.each{|proc| + proc.call(result, self) + } + result.join("\t") + end + + def run runner + result = orig_run(runner) + TEST_ALL_PROFILE_OUT.puts memprofile_test_all_result_result + TEST_ALL_PROFILE_OUT.flush + result + end + + TEST_ALL_PROFILE_OUT.puts TEST_ALL_PROFILE_BANNER.join("\t") +end diff --git a/jni/ruby/test/lib/test/unit.rb b/jni/ruby/test/lib/test/unit.rb new file mode 100644 index 0000000..629f3e7 --- /dev/null +++ b/jni/ruby/test/lib/test/unit.rb @@ -0,0 +1,896 @@ +begin + gem 'minitest', '< 5.0.0' if defined? Gem +rescue Gem::LoadError +end +require 'minitest/unit' +require 'test/unit/assertions' +require_relative '../envutil' +require 'test/unit/testcase' +require 'optparse' + +# See Test::Unit +module Test + ## + # Test::Unit is an implementation of the xUnit testing framework for Ruby. + # + # If you are writing new test code, please use MiniTest instead of Test::Unit. + # + # Test::Unit has been left in the standard library to support legacy test + # suites. + module Unit + TEST_UNIT_IMPLEMENTATION = 'test/unit compatibility layer using minitest' # :nodoc: + + module RunCount # :nodoc: all + @@run_count = 0 + + def self.have_run? + @@run_count.nonzero? + end + + def run(*) + @@run_count += 1 + super + end + + def run_once + return if have_run? + return if $! # don't run if there was an exception + yield + end + module_function :run_once + end + + module Options # :nodoc: all + def initialize(*, &block) + @init_hook = block + @options = nil + super(&nil) + end + + def option_parser + @option_parser ||= OptionParser.new + end + + def process_args(args = []) + return @options if @options + orig_args = args.dup + options = {} + opts = option_parser + setup_options(opts, options) + opts.parse!(args) + orig_args -= args + args = @init_hook.call(args, options) if @init_hook + non_options(args, options) + @help = orig_args.map { |s| s =~ /[\s|&<>$()]/ ? s.inspect : s }.join " " + @options = options + if @options[:parallel] + @files = args + @args = orig_args + end + options + end + + private + def setup_options(opts, options) + opts.separator 'minitest options:' + opts.version = MiniTest::Unit::VERSION + + options[:retry] = true + options[:job_status] = nil + options[:hide_skip] = true + + opts.on '-h', '--help', 'Display this help.' do + puts opts + exit + end + + opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m| + options[:seed] = m + end + + opts.on '-v', '--verbose', "Verbose. Show progress processing files." do + options[:verbose] = true + self.verbose = options[:verbose] + end + + opts.on '-n', '--name PATTERN', "Filter test names on pattern." do |a| + options[:filter] = a + end + + opts.on '--jobs-status [TYPE]', [:normal, :replace], + "Show status of jobs every file; Disabled when --jobs isn't specified." do |type| + options[:job_status] = type || :normal + end + + opts.on '-j N', '--jobs N', "Allow run tests with N jobs at once" do |a| + if /^t/ =~ a + options[:testing] = true # For testing + options[:parallel] = a[1..-1].to_i + else + options[:parallel] = a.to_i + end + end + + opts.on '--separate', "Restart job process after one testcase has done" do + options[:parallel] ||= 1 + options[:separate] = true + end + + opts.on '--retry', "Retry running testcase when --jobs specified" do + options[:retry] = true + end + + opts.on '--no-retry', "Disable --retry" do + options[:retry] = false + end + + opts.on '--ruby VAL', "Path to ruby; It'll have used at -j option" do |a| + options[:ruby] = a.split(/ /).reject(&:empty?) + end + + opts.on '-q', '--hide-skip', 'Hide skipped tests' do + options[:hide_skip] = true + end + + opts.on '--show-skip', 'Show skipped tests' do + options[:hide_skip] = false + end + + opts.on '--color[=WHEN]', + [:always, :never, :auto], + "colorize the output. WHEN defaults to 'always'", "or can be 'never' or 'auto'." do |c| + options[:color] = c || :always + end + + opts.on '--tty[=WHEN]', + [:yes, :no], + "force to output tty control. WHEN defaults to 'yes'", "or can be 'no'." do |c| + @tty = c != :no + end + end + + def non_options(files, options) + begin + require "rbconfig" + rescue LoadError + warn "#{caller(1)[0]}: warning: Parallel running disabled because can't get path to ruby; run specify with --ruby argument" + options[:parallel] = nil + else + options[:ruby] ||= [RbConfig.ruby] + end + + true + end + end + + module GlobOption # :nodoc: all + @@testfile_prefix = "test" + + def setup_options(parser, options) + super + parser.on '-b', '--basedir=DIR', 'Base directory of test suites.' do |dir| + options[:base_directory] = dir + end + parser.on '-x', '--exclude PATTERN', 'Exclude test files on pattern.' do |pattern| + (options[:reject] ||= []) << pattern + end + end + + def non_options(files, options) + paths = [options.delete(:base_directory), nil].uniq + if reject = options.delete(:reject) + reject_pat = Regexp.union(reject.map {|r| /#{r}/ }) + end + files.map! {|f| + f = f.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR + ((paths if /\A\.\.?(?:\z|\/)/ !~ f) || [nil]).any? do |prefix| + if prefix + path = f.empty? ? prefix : "#{prefix}/#{f}" + else + next if f.empty? + path = f + end + if !(match = Dir["#{path}/**/#{@@testfile_prefix}_*.rb"]).empty? + if reject + match.reject! {|n| + n[(prefix.length+1)..-1] if prefix + reject_pat =~ n + } + end + break match + elsif !reject or reject_pat !~ f and File.exist? path + break path + end + end or + raise ArgumentError, "file not found: #{f}" + } + files.flatten! + super(files, options) + end + end + + module LoadPathOption # :nodoc: all + def setup_options(parser, options) + super + parser.on '-Idirectory', 'Add library load path' do |dirs| + dirs.split(':').each { |d| $LOAD_PATH.unshift d } + end + end + end + + module GCStressOption # :nodoc: all + def setup_options(parser, options) + super + parser.on '--[no-]gc-stress', 'Set GC.stress as true' do |flag| + options[:gc_stress] = flag + end + end + + def non_options(files, options) + if options.delete(:gc_stress) + MiniTest::Unit::TestCase.class_eval do + oldrun = instance_method(:run) + define_method(:run) do |runner| + begin + gc_stress, GC.stress = GC.stress, true + oldrun.bind(self).call(runner) + ensure + GC.stress = gc_stress + end + end + end + end + super + end + end + + module RequireFiles # :nodoc: all + def non_options(files, options) + return false if !super + errors = {} + result = false + files.each {|f| + d = File.dirname(path = File.realpath(f)) + unless $:.include? d + $: << d + end + begin + require path unless options[:parallel] + result = true + rescue LoadError + next if errors[$!.message] + errors[$!.message] = true + puts "#{f}: #{$!}" + end + } + result + end + end + + class Runner < MiniTest::Unit # :nodoc: all + include Test::Unit::Options + include Test::Unit::GlobOption + include Test::Unit::LoadPathOption + include Test::Unit::GCStressOption + include Test::Unit::RunCount + + class Worker + def self.launch(ruby,args=[]) + io = IO.popen([*ruby, + "#{File.dirname(__FILE__)}/unit/parallel.rb", + *args], "rb+") + new(io, io.pid, :waiting) + end + + attr_reader :quit_called + + def initialize(io, pid, status) + @io = io + @pid = pid + @status = status + @file = nil + @real_file = nil + @loadpath = [] + @hooks = {} + @quit_called = false + end + + def puts(*args) + @io.puts(*args) + end + + def run(task,type) + @file = File.basename(task, ".rb") + @real_file = task + begin + puts "loadpath #{[Marshal.dump($:-@loadpath)].pack("m0")}" + @loadpath = $:.dup + puts "run #{task} #{type}" + @status = :prepare + rescue Errno::EPIPE + died + rescue IOError + raise unless ["stream closed","closed stream"].include? $!.message + died + end + end + + def hook(id,&block) + @hooks[id] ||= [] + @hooks[id] << block + self + end + + def read + res = (@status == :quit) ? @io.read : @io.gets + res && res.chomp + end + + def close + @io.close unless @io.closed? + self + rescue IOError + end + + def quit + return if @io.closed? + @quit_called = true + @io.puts "quit" + @io.close + end + + def kill + Process.kill(:KILL, @pid) + rescue Errno::ESRCH + end + + def died(*additional) + @status = :quit + @io.close + + call_hook(:dead,*additional) + end + + def to_s + if @file + "#{@pid}=#{@file}" + else + "#{@pid}:#{@status.to_s.ljust(7)}" + end + end + + attr_reader :io, :pid + attr_accessor :status, :file, :real_file, :loadpath + + private + + def call_hook(id,*additional) + @hooks[id] ||= [] + @hooks[id].each{|hook| hook[self,additional] } + self + end + + end + + class << self; undef autorun; end + + @@stop_auto_run = false + def self.autorun + at_exit { + Test::Unit::RunCount.run_once { + exit(Test::Unit::Runner.new.run(ARGV) || true) + } unless @@stop_auto_run + } unless @@installed_at_exit + @@installed_at_exit = true + end + + def after_worker_down(worker, e=nil, c=false) + return unless @options[:parallel] + return if @interrupt + warn e if e + @need_quit = true + warn "" + warn "Some worker was crashed. It seems ruby interpreter's bug" + warn "or, a bug of test/unit/parallel.rb. try again without -j" + warn "option." + warn "" + STDERR.flush + exit c + end + + def terminal_width + unless @terminal_width ||= nil + begin + require 'io/console' + width = $stdout.winsize[1] + rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF, Errno::EINVAL + width = ENV["COLUMNS"].to_i.nonzero? || 80 + end + width -= 1 if /mswin|mingw/ =~ RUBY_PLATFORM + @terminal_width = width + end + @terminal_width + end + + def del_status_line + @status_line_size ||= 0 + unless @options[:job_status] == :replace + $stdout.puts + return + end + print "\r"+" "*@status_line_size+"\r" + $stdout.flush + @status_line_size = 0 + end + + def put_status(line) + unless @options[:job_status] == :replace + print(line) + return + end + @status_line_size ||= 0 + del_status_line + $stdout.flush + line = line[0...terminal_width] + print line + $stdout.flush + @status_line_size = line.size + end + + def add_status(line) + unless @options[:job_status] == :replace + print(line) + return + end + @status_line_size ||= 0 + line = line[0...(terminal_width-@status_line_size)] + print line + $stdout.flush + @status_line_size += line.size + end + + def jobs_status + return unless @options[:job_status] + puts "" unless @options[:verbose] or @options[:job_status] == :replace + status_line = @workers.map(&:to_s).join(" ") + update_status(status_line) or (puts; nil) + end + + def del_jobs_status + return unless @options[:job_status] == :replace && @status_line_size.nonzero? + del_status_line + end + + def after_worker_quit(worker) + return unless @options[:parallel] + return if @interrupt + @workers.delete(worker) + @dead_workers << worker + @ios = @workers.map(&:io) + end + + def launch_worker + begin + worker = Worker.launch(@options[:ruby],@args) + rescue => e + abort "ERROR: Failed to launch job process - #{e.class}: #{e.message}" + end + worker.hook(:dead) do |w,info| + after_worker_quit w + after_worker_down w, *info if !info.empty? && !worker.quit_called + end + @workers << worker + @ios << worker.io + @workers_hash[worker.io] = worker + worker + end + + def delete_worker(worker) + @workers_hash.delete worker.io + @workers.delete worker + @ios.delete worker.io + end + + def quit_workers + return if @workers.empty? + @workers.reject! do |worker| + begin + timeout(1) do + worker.quit + end + rescue Errno::EPIPE + rescue Timeout::Error + end + worker.close + end + + return if @workers.empty? + begin + timeout(0.2 * @workers.size) do + Process.waitall + end + rescue Timeout::Error + @workers.each do |worker| + worker.kill + end + @worker.clear + end + end + + def start_watchdog + Thread.new do + while stat = Process.wait2 + break if @interrupt # Break when interrupt + pid, stat = stat + w = (@workers + @dead_workers).find{|x| pid == x.pid } + next unless w + w = w.dup + if w.status != :quit && !w.quit_called? + # Worker down + w.died(nil, !stat.signaled? && stat.exitstatus) + end + end + end + end + + def deal(io, type, result, rep, shutting_down = false) + worker = @workers_hash[io] + cmd = worker.read + cmd.sub!(/\A\.+/, '') + case cmd + when '' + # just only dots, ignore + when /^okay$/ + worker.status = :running + jobs_status + when /^ready(!)?$/ + bang = $1 + worker.status = :ready + + return nil unless task = @tasks.shift + if @options[:separate] and not bang + worker.quit + worker = add_worker + end + worker.run(task, type) + @test_count += 1 + + jobs_status + when /^done (.+?)$/ + begin + r = Marshal.load($1.unpack("m")[0]) + rescue + print "unknown object: #{$1.unpack("m")[0].dump}" + return true + end + result << r[0..1] unless r[0..1] == [nil,nil] + rep << {file: worker.real_file, report: r[2], result: r[3], testcase: r[5]} + $:.push(*r[4]).uniq! + return true + when /^p (.+?)$/ + del_jobs_status + print $1.unpack("m")[0] + jobs_status if @options[:job_status] == :replace + when /^after (.+?)$/ + @warnings << Marshal.load($1.unpack("m")[0]) + when /^bye (.+?)$/ + after_worker_down worker, Marshal.load($1.unpack("m")[0]) + when /^bye$/, nil + if shutting_down || worker.quit_called + after_worker_quit worker + else + after_worker_down worker + end + else + print "unknown command: #{cmd.dump}\n" + end + return false + end + + def _run_parallel suites, type, result + if @options[:parallel] < 1 + warn "Error: parameter of -j option should be greater than 0." + return + end + + # Require needed things for parallel running + require 'thread' + require 'timeout' + @tasks = @files.dup # Array of filenames. + @need_quit = false + @dead_workers = [] # Array of dead workers. + @warnings = [] + @total_tests = @tasks.size.to_s(10) + rep = [] # FIXME: more good naming + + @workers = [] # Array of workers. + @workers_hash = {} # out-IO => worker + @ios = [] # Array of worker IOs + begin + # Thread: watchdog + watchdog = start_watchdog + + @options[:parallel].times {launch_worker} + + while _io = IO.select(@ios)[0] + break if _io.any? do |io| + @need_quit or + (deal(io, type, result, rep).nil? and + !@workers.any? {|x| [:running, :prepare].include? x.status}) + end + end + rescue Interrupt => ex + @interrupt = ex + return result + ensure + watchdog.kill if watchdog + if @interrupt + @ios.select!{|x| @workers_hash[x].status == :running } + while !@ios.empty? && (__io = IO.select(@ios,[],[],10)) + __io[0].reject! {|io| deal(io, type, result, rep, true)} + end + end + + quit_workers + + unless @interrupt || !@options[:retry] || @need_quit + @options[:parallel] = false + suites, rep = rep.partition {|r| r[:testcase] && r[:file] && r[:report].any? {|e| !e[2].is_a?(MiniTest::Skip)}} + suites.map {|r| r[:file]}.uniq.each {|file| require file} + suites.map! {|r| eval("::"+r[:testcase])} + del_status_line or puts + unless suites.empty? + puts "Retrying..." + _run_suites(suites, type) + end + end + unless @options[:retry] + del_status_line or puts + end + unless rep.empty? + rep.each do |r| + r[:report].each do |f| + puke(*f) if f + end + end + if @options[:retry] + @errors += rep.map{|x| x[:result][0] }.inject(:+) + @failures += rep.map{|x| x[:result][1] }.inject(:+) + @skips += rep.map{|x| x[:result][2] }.inject(:+) + end + end + unless @warnings.empty? + warn "" + @warnings.uniq! {|w| w[1].message} + @warnings.each do |w| + warn "#{w[0]}: #{w[1].message} (#{w[1].class})" + end + warn "" + end + end + end + + def _run_suites suites, type + _prepare_run(suites, type) + @interrupt = nil + result = [] + GC.start + if @options[:parallel] + _run_parallel suites, type, result + else + suites.each {|suite| + begin + result << _run_suite(suite, type) + rescue Interrupt => e + @interrupt = e + break + end + } + end + report.reject!{|r| r.start_with? "Skipped:" } if @options[:hide_skip] + report.sort_by!{|r| r.start_with?("Skipped:") ? 0 : \ + (r.start_with?("Failure:") ? 1 : 2) } + result + end + + alias mini_run_suite _run_suite + + def output + (@output ||= nil) || super + end + + def _prepare_run(suites, type) + options[:job_status] ||= :replace if @tty && !@verbose + case options[:color] + when :always + color = true + when :auto, nil + color = @options[:job_status] == :replace && /dumb/ !~ ENV["TERM"] + else + color = false + end + if color + # dircolors-like style + colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:]*)/)] : {} + @passed_color = "\e[#{colors["pass"] || "32"}m" + @failed_color = "\e[#{colors["fail"] || "31"}m" + @skipped_color = "\e[#{colors["skip"] || "33"}m" + @reset_color = "\e[m" + else + @passed_color = @failed_color = @skipped_color = @reset_color = "" + end + if color or @options[:job_status] == :replace + @verbose = !options[:parallel] + @output = StatusLineOutput.new(self) + end + if /\A\/(.*)\/\z/ =~ (filter = options[:filter]) + filter = Regexp.new($1) + end + type = "#{type}_methods" + total = if filter + suites.inject(0) {|n, suite| n + suite.send(type).grep(filter).size} + else + suites.inject(0) {|n, suite| n + suite.send(type).size} + end + @test_count = 0 + @total_tests = total.to_s(10) + end + + def new_test(s) + @test_count += 1 + update_status(s) + end + + def update_status(s) + count = @test_count.to_s(10).rjust(@total_tests.size) + put_status("#{@passed_color}[#{count}/#{@total_tests}]#{@reset_color} #{s}") + end + + def _print(s); $stdout.print(s); end + def succeed; del_status_line; end + + def failed(s) + sep = "\n" + @report_count ||= 0 + report.each do |msg| + if msg.start_with? "Skipped:" + if @options[:hide_skip] + del_status_line + next + end + color = @skipped_color + else + color = @failed_color + end + msg = msg.split(/$/, 2) + $stdout.printf("%s%s%3d) %s%s%s\n", + sep, color, @report_count += 1, + msg[0], @reset_color, msg[1]) + sep = nil + end + report.clear + end + + # Overriding of MiniTest::Unit#puke + def puke klass, meth, e + # TODO: + # this overriding is for minitest feature that skip messages are + # hidden when not verbose (-v), note this is temporally. + n = report.size + rep = super + if MiniTest::Skip === e and /no message given\z/ =~ e.message + report.slice!(n..-1) + rep = "." + end + rep + end + + def initialize + super + @tty = $stdout.tty? + end + + def status(*args) + result = super + raise @interrupt if @interrupt + result + end + + def run(*args) + result = super + puts "\nruby -v: #{RUBY_DESCRIPTION}" + result + end + end + + class StatusLineOutput < Struct.new(:runner) # :nodoc: all + def puts(*a) $stdout.puts(*a) unless a.empty? end + def respond_to_missing?(*a) $stdout.respond_to?(*a) end + def method_missing(*a, &b) $stdout.__send__(*a, &b) end + + def print(s) + case s + when /\A(.*\#.*) = \z/ + runner.new_test($1) + when /\A(.* s) = \z/ + runner.add_status(" = "+$1.chomp) + when /\A\.+\z/ + runner.succeed + when /\A[EFS]\z/ + runner.failed(s) + else + $stdout.print(s) + end + end + end + + class AutoRunner # :nodoc: all + class Runner < Test::Unit::Runner + include Test::Unit::RequireFiles + end + + attr_accessor :to_run, :options + + def initialize(force_standalone = false, default_dir = nil, argv = ARGV) + @force_standalone = force_standalone + @runner = Runner.new do |files, options| + options[:base_directory] ||= default_dir + files << default_dir if files.empty? and default_dir + @to_run = files + yield self if block_given? + files + end + Runner.runner = @runner + @options = @runner.option_parser + if @force_standalone + @options.banner.sub!(/\[options\]/, '\& tests...') + end + @argv = argv + end + + def process_args(*args) + @runner.process_args(*args) + !@to_run.empty? + end + + def run + if @force_standalone and not process_args(@argv) + abort @options.banner + end + @runner.run(@argv) || true + end + + def self.run(*args) + new(*args).run + end + end + + class ProxyError < StandardError # :nodoc: all + def initialize(ex) + @message = ex.message + @backtrace = ex.backtrace + end + + attr_accessor :message, :backtrace + end + end +end + +module MiniTest # :nodoc: all + class Unit + end +end + +class MiniTest::Unit::TestCase # :nodoc: all + undef run_test + RUN_TEST_TRACE = "#{__FILE__}:#{__LINE__+3}:in `run_test'".freeze + def run_test(name) + progname, $0 = $0, "#{$0}: #{self.class}##{name}" + self.__send__(name) + ensure + $@.delete(RUN_TEST_TRACE) if $@ + $0 = progname + end +end + +Test::Unit::Runner.autorun diff --git a/jni/ruby/test/lib/test/unit/assertions.rb b/jni/ruby/test/lib/test/unit/assertions.rb new file mode 100644 index 0000000..727c54c --- /dev/null +++ b/jni/ruby/test/lib/test/unit/assertions.rb @@ -0,0 +1,465 @@ +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 || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) {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 UncaughtThrowError => e + thrown = e.tag + end + msg = message(msg) { + "Expected #{mu_pp(tag)} to have been thrown"\ + "#{%Q[, not #{thrown}] if thrown}" + } + assert(false, msg) + end + assert(true) + ret + end + + # :call-seq: + # assert_equal( expected, actual, failure_message = nil ) + # + #Tests if +expected+ is equal to +actual+. + # + #An optional failure message may be provided as the final argument. + def assert_equal(exp, act, msg = nil) + msg = message(msg) { + exp_str = mu_pp(exp) + act_str = mu_pp(act) + exp_comment = '' + act_comment = '' + if exp_str == act_str + if (exp.is_a?(String) && act.is_a?(String)) || + (exp.is_a?(Regexp) && act.is_a?(Regexp)) + exp_comment = " (#{exp.encoding})" + act_comment = " (#{act.encoding})" + elsif exp.is_a?(Float) && act.is_a?(Float) + exp_str = "%\#.#{Float::DIG+2}g" % exp + act_str = "%\#.#{Float::DIG+2}g" % act + elsif exp.is_a?(Time) && act.is_a?(Time) + if exp.subsec * 1000_000_000 == exp.nsec + exp_comment = " (#{exp.nsec}[ns])" + else + exp_comment = " (subsec=#{exp.subsec})" + end + if act.subsec * 1000_000_000 == act.nsec + act_comment = " (#{act.nsec}[ns])" + else + act_comment = " (subsec=#{act.subsec})" + end + elsif exp.class != act.class + # a subclass of Range, for example. + exp_comment = " (#{exp.class})" + act_comment = " (#{act.class})" + end + elsif !Encoding.compatible?(exp_str, act_str) + if exp.is_a?(String) && act.is_a?(String) + exp_str = exp.dump + act_str = act.dump + exp_comment = " (#{exp.encoding})" + act_comment = " (#{act.encoding})" + else + exp_str = exp_str.dump + act_str = act_str.dump + end + end + "<#{exp_str}>#{exp_comment} expected but was\n<#{act_str}>#{act_comment}" + } + assert(exp == act, msg) + end + + # :call-seq: + # assert_not_nil( expression, failure_message = nil ) + # + #Tests if +expression+ is not nil. + # + #An optional failure message may be provided as the final argument. + def assert_not_nil(exp, msg=nil) + msg = message(msg) { "<#{mu_pp(exp)}> expected to not be nil" } + assert(!exp.nil?, msg) + end + + # :call-seq: + # assert_not_equal( expected, actual, failure_message = nil ) + # + #Tests if +expected+ is not equal to +actual+. + # + #An optional failure message may be provided as the final argument. + def assert_not_equal(exp, act, msg=nil) + msg = message(msg) { "<#{mu_pp(exp)}> expected to be != to\n<#{mu_pp(act)}>" } + assert(exp != act, msg) + end + + # :call-seq: + # assert_no_match( regexp, string, failure_message = nil ) + # + #Tests if the given Regexp does not match a given String. + # + #An optional failure message may be provided as the final argument. + def assert_no_match(regexp, string, msg=nil) + assert_instance_of(Regexp, regexp, "The first argument to assert_no_match should be a Regexp.") + self._assertions -= 1 + msg = message(msg) { "<#{mu_pp(regexp)}> expected to not match\n<#{mu_pp(string)}>" } + assert(regexp !~ string, msg) + end + + # :call-seq: + # assert_not_same( expected, actual, failure_message = nil ) + # + #Tests if +expected+ is not the same object as +actual+. + #This test uses Object#equal? to test equality. + # + #An optional failure message may be provided as the final argument. + # + # assert_not_same("x", "x") #Succeeds + def assert_not_same(expected, actual, message="") + msg = message(msg) { build_message(message, <<EOT, expected, expected.__id__, actual, actual.__id__) } +<?> +with id <?> expected to not be equal\\? to +<?> +with id <?>. +EOT + assert(!actual.equal?(expected), msg) + end + + # :call-seq: + # assert_respond_to( object, method, failure_message = nil ) + # + #Tests if the given Object responds to +method+. + # + #An optional failure message may be provided as the final argument. + # + # assert_respond_to("hello", :reverse) #Succeeds + # assert_respond_to("hello", :does_not_exist) #Fails + def assert_respond_to obj, (meth, priv), msg = nil + if priv + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv}" + } + return assert obj.respond_to?(meth, priv), msg + end + #get rid of overcounting + super if !caller[0].rindex(MINI_DIR, 0) || !obj.respond_to?(meth) + end + + # :call-seq: + # assert_send( +send_array+, failure_message = nil ) + # + # Passes if the method send returns a true value. + # + # +send_array+ is composed of: + # * A receiver + # * A method + # * Arguments to the method + # + # Example: + # assert_send(["Hello world", :include?, "Hello"]) # -> pass + # assert_send(["Hello world", :include?, "Goodbye"]) # -> fail + def assert_send send_ary, m = nil + recv, msg, *args = send_ary + m = message(m) { + if args.empty? + argsstr = "" + else + (argsstr = mu_pp(args)).sub!(/\A\[(.*)\]\z/m, '(\1)') + end + "Expected #{mu_pp(recv)}.#{msg}#{argsstr} to return true" + } + assert recv.__send__(msg, *args), m + end + + # :call-seq: + # assert_not_send( +send_array+, failure_message = nil ) + # + # Passes if the method send doesn't return a true value. + # + # +send_array+ is composed of: + # * A receiver + # * A method + # * Arguments to the method + # + # Example: + # assert_not_send([[1, 2], :member?, 1]) # -> fail + # assert_not_send([[1, 2], :member?, 4]) # -> pass + def assert_not_send send_ary, m = nil + recv, msg, *args = send_ary + m = message(m) { + if args.empty? + argsstr = "" + else + (argsstr = mu_pp(args)).sub!(/\A\[(.*)\]\z/m, '(\1)') + end + "Expected #{mu_pp(recv)}.#{msg}#{argsstr} to return false" + } + assert !recv.__send__(msg, *args), m + end + + ms = instance_methods(true).map {|sym| sym.to_s } + ms.grep(/\Arefute_/) do |m| + mname = ('assert_not_' << m.to_s[/.*?_(.*)/, 1]) + alias_method(mname, m) unless ms.include? mname + end + alias assert_include assert_includes + alias assert_not_include assert_not_includes + + def assert_all?(obj, m = nil, &blk) + failed = [] + obj.each do |*a, &b| + unless blk.call(*a, &b) + failed << (a.size > 1 ? a : a[0]) + end + end + assert(failed.empty?, message(m) {failed.pretty_inspect}) + end + + def assert_not_all?(obj, m = nil, &blk) + failed = [] + obj.each do |*a, &b| + if blk.call(*a, &b) + failed << a.size > 1 ? a : a[0] + end + end + assert(failed.empty?, message(m) {failed.pretty_inspect}) + end + + def build_message(head, template=nil, *arguments) #:nodoc: + template &&= template.chomp + template.gsub(/\G((?:[^\\]|\\.)*?)(\\)?\?/) { $1 + ($2 ? "?" : mu_pp(arguments.shift)) } + end + + def message(msg = nil, *args, &default) # :nodoc: + if Proc === msg + super(nil, *args) do + ary = [msg.call, (default.call if default)].compact.reject(&:empty?) + if 1 < ary.length + ary[0...-1] = ary[0...-1].map {|str| str.sub(/(?<!\.)\z/, '.') } + end + ary.join("\n") + end + else + super + end + end + end + end +end diff --git a/jni/ruby/test/lib/test/unit/parallel.rb b/jni/ruby/test/lib/test/unit/parallel.rb new file mode 100644 index 0000000..ea809f2 --- /dev/null +++ b/jni/ruby/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.readpartial(1024)) + _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 if orig_stdin + $stdout = orig_stdout if 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/jni/ruby/test/lib/test/unit/testcase.rb b/jni/ruby/test/lib/test/unit/testcase.rb new file mode 100644 index 0000000..984f08d --- /dev/null +++ b/jni/ruby/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 diff --git a/jni/ruby/test/lib/tracepointchecker.rb b/jni/ruby/test/lib/tracepointchecker.rb new file mode 100644 index 0000000..4612e62 --- /dev/null +++ b/jni/ruby/test/lib/tracepointchecker.rb @@ -0,0 +1,118 @@ +module TracePointChecker + STATE = { + count: 0, + running: false, + } + + module ZombieTraceHunter + def before_setup + @tracepoint_captured_stat = TracePoint.stat.map{|k, (activated, _deleted)| [k, activated]} + + super + end + + def after_teardown + super + + # detect zombie traces. + assert_equal( + @tracepoint_captured_stat, + TracePoint.stat.map{|k, (activated, _deleted)| [k, activated]}, + "The number of active trace events was changed" + ) + # puts "TracePoint - deleted: #{deleted}" if deleted > 0 + + TracePointChecker.check if STATE[:running] + end + end + + MAIN_THREAD = Thread.current + TRACES = [] + + def self.prefix event + case event + when :call, :return + :n + when :c_call, :c_return + :c + when :b_call, :b_return + :b + end + end + + def self.clear_call_stack + Thread.current[:call_stack] = [] + end + + def self.call_stack + stack = Thread.current[:call_stack] + stack = clear_call_stack unless stack + stack + end + + def self.verbose_out label, method + puts label => call_stack, :count => STATE[:count], :method => method + end + + def self.method_label tp + "#{prefix(tp.event)}##{tp.method_id}" + end + + def self.start verbose: false, stop_at_failure: false + call_events = %i(a_call) + return_events = %i(a_return) + clear_call_stack + + STATE[:running] = true + + TRACES << TracePoint.new(*call_events){|tp| + next if Thread.current != MAIN_THREAD + + method = method_label(tp) + call_stack.push method + STATE[:count] += 1 + + verbose_out :psuh, method if verbose + } + + TRACES << TracePoint.new(*return_events){|tp| + next if Thread.current != MAIN_THREAD + STATE[:count] += 1 + + method = "#{prefix(tp.event)}##{tp.method_id}" + verbose_out :pop1, method if verbose + + stored_method = call_stack.pop + next if stored_method.nil? + + verbose_out :pop2, method if verbose + + if stored_method != method + stop if stop_at_failure + RubyVM::SDR() if defined? RubyVM::SDR() + call_stack.clear + raise "#{stored_method} is expected, but #{method} (count: #{STATE[:count]})" + end + } + + TRACES.each{|trace| trace.enable} + end + + def self.stop + STATE[:running] = true + TRACES.each{|trace| trace.disable} + TRACES.clear + end + + def self.check + TRACES.each{|trace| + raise "trace #{trace} should not be deactivated" unless trace.enabled? + } + end +end + +class ::Test::Unit::TestCase + include TracePointChecker::ZombieTraceHunter +end + +# TracePointChecker.start verbose: false diff --git a/jni/ruby/test/lib/with_different_ofs.rb b/jni/ruby/test/lib/with_different_ofs.rb new file mode 100644 index 0000000..76dfa68 --- /dev/null +++ b/jni/ruby/test/lib/with_different_ofs.rb @@ -0,0 +1,17 @@ +module DifferentOFS + module WithDifferentOFS + def setup + super + @ofs, $, = $,, "-" + end + def teardown + $, = @ofs + super + end + end + + def self.extended(klass) + super(klass) + klass.const_set(:DifferentOFS, Class.new(klass).class_eval {include WithDifferentOFS}).name + end +end |