From 9279dd8c61d4fa1d76f1c448cd2c60bbd7413a6a Mon Sep 17 00:00:00 2001 From: Kir Shatrov Date: Sat, 26 Dec 2020 13:23:54 +0300 Subject: [PATCH 01/36] Use frozen strings --- lib/webrick/accesslog.rb | 2 +- lib/webrick/cgi.rb | 2 +- lib/webrick/compat.rb | 2 +- lib/webrick/config.rb | 2 +- lib/webrick/cookie.rb | 4 ++-- lib/webrick/htmlutils.rb | 2 +- lib/webrick/httpauth.rb | 2 +- lib/webrick/httpauth/authenticator.rb | 2 +- lib/webrick/httpauth/basicauth.rb | 2 +- lib/webrick/httpauth/digestauth.rb | 2 +- lib/webrick/httpauth/htdigest.rb | 2 +- lib/webrick/httpauth/htgroup.rb | 2 +- lib/webrick/httpauth/htpasswd.rb | 2 +- lib/webrick/httpauth/userdb.rb | 2 +- lib/webrick/httpproxy.rb | 2 +- lib/webrick/httprequest.rb | 4 ++-- lib/webrick/httpresponse.rb | 12 ++++++------ lib/webrick/https.rb | 2 +- lib/webrick/httpserver.rb | 4 ++-- lib/webrick/httpservlet.rb | 2 +- lib/webrick/httpservlet/abstract.rb | 2 +- lib/webrick/httpservlet/cgi_runner.rb | 4 ++-- lib/webrick/httpservlet/cgihandler.rb | 2 +- lib/webrick/httpservlet/erbhandler.rb | 2 +- lib/webrick/httpservlet/filehandler.rb | 10 +++++----- lib/webrick/httpservlet/prochandler.rb | 2 +- lib/webrick/httpstatus.rb | 2 +- lib/webrick/httputils.rb | 6 +++--- lib/webrick/httpversion.rb | 2 +- lib/webrick/log.rb | 14 +++++++------- lib/webrick/server.rb | 2 +- lib/webrick/ssl.rb | 2 +- lib/webrick/utils.rb | 4 ++-- lib/webrick/version.rb | 2 +- 34 files changed, 56 insertions(+), 56 deletions(-) diff --git a/lib/webrick/accesslog.rb b/lib/webrick/accesslog.rb index e4849637..fccfd653 100644 --- a/lib/webrick/accesslog.rb +++ b/lib/webrick/accesslog.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # accesslog.rb -- Access log handling utilities # diff --git a/lib/webrick/cgi.rb b/lib/webrick/cgi.rb index bb0ae2fc..f22480b8 100644 --- a/lib/webrick/cgi.rb +++ b/lib/webrick/cgi.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # cgi.rb -- Yet another CGI library # diff --git a/lib/webrick/compat.rb b/lib/webrick/compat.rb index c497a193..842da1eb 100644 --- a/lib/webrick/compat.rb +++ b/lib/webrick/compat.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # compat.rb -- cross platform compatibility # diff --git a/lib/webrick/config.rb b/lib/webrick/config.rb index 9f2ab44f..d67375c8 100644 --- a/lib/webrick/config.rb +++ b/lib/webrick/config.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # config.rb -- Default configurations. # diff --git a/lib/webrick/cookie.rb b/lib/webrick/cookie.rb index 5fd3bfb2..7b187822 100644 --- a/lib/webrick/cookie.rb +++ b/lib/webrick/cookie.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # cookie.rb -- Cookie class # @@ -92,7 +92,7 @@ def expires # The cookie string suitable for use in an HTTP header def to_s - ret = "" + ret = +"" ret << @name << "=" << @value ret << "; " << "Version=" << @version.to_s if @version > 0 ret << "; " << "Domain=" << @domain if @domain diff --git a/lib/webrick/htmlutils.rb b/lib/webrick/htmlutils.rb index ed9f4ac0..7ff0bde2 100644 --- a/lib/webrick/htmlutils.rb +++ b/lib/webrick/htmlutils.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # htmlutils.rb -- HTMLUtils Module # diff --git a/lib/webrick/httpauth.rb b/lib/webrick/httpauth.rb index f8bf09a6..4f5321c8 100644 --- a/lib/webrick/httpauth.rb +++ b/lib/webrick/httpauth.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httpauth.rb -- HTTP access authentication # diff --git a/lib/webrick/httpauth/authenticator.rb b/lib/webrick/httpauth/authenticator.rb index 8f0eaa3a..a6ee28de 100644 --- a/lib/webrick/httpauth/authenticator.rb +++ b/lib/webrick/httpauth/authenticator.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # httpauth/authenticator.rb -- Authenticator mix-in module. # diff --git a/lib/webrick/httpauth/basicauth.rb b/lib/webrick/httpauth/basicauth.rb index 7d0a9cfc..cc8137af 100644 --- a/lib/webrick/httpauth/basicauth.rb +++ b/lib/webrick/httpauth/basicauth.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httpauth/basicauth.rb -- HTTP basic access authentication # diff --git a/lib/webrick/httpauth/digestauth.rb b/lib/webrick/httpauth/digestauth.rb index 3cf12899..25ce8ba0 100644 --- a/lib/webrick/httpauth/digestauth.rb +++ b/lib/webrick/httpauth/digestauth.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httpauth/digestauth.rb -- HTTP digest access authentication # diff --git a/lib/webrick/httpauth/htdigest.rb b/lib/webrick/httpauth/htdigest.rb index 93b18e2c..c7e853b7 100644 --- a/lib/webrick/httpauth/htdigest.rb +++ b/lib/webrick/httpauth/htdigest.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httpauth/htdigest.rb -- Apache compatible htdigest file # diff --git a/lib/webrick/httpauth/htgroup.rb b/lib/webrick/httpauth/htgroup.rb index e06c441b..108c9d0b 100644 --- a/lib/webrick/httpauth/htgroup.rb +++ b/lib/webrick/httpauth/htgroup.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httpauth/htgroup.rb -- Apache compatible htgroup file # diff --git a/lib/webrick/httpauth/htpasswd.rb b/lib/webrick/httpauth/htpasswd.rb index abca3053..9a48e57d 100644 --- a/lib/webrick/httpauth/htpasswd.rb +++ b/lib/webrick/httpauth/htpasswd.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httpauth/htpasswd -- Apache compatible htpasswd file # diff --git a/lib/webrick/httpauth/userdb.rb b/lib/webrick/httpauth/userdb.rb index 7a17715c..0d0f72b1 100644 --- a/lib/webrick/httpauth/userdb.rb +++ b/lib/webrick/httpauth/userdb.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # httpauth/userdb.rb -- UserDB mix-in module. # diff --git a/lib/webrick/httpproxy.rb b/lib/webrick/httpproxy.rb index 7607c3df..196682ec 100644 --- a/lib/webrick/httpproxy.rb +++ b/lib/webrick/httpproxy.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httpproxy.rb -- HTTPProxy Class # diff --git a/lib/webrick/httprequest.rb b/lib/webrick/httprequest.rb index d34eac7e..fe95aad9 100644 --- a/lib/webrick/httprequest.rb +++ b/lib/webrick/httprequest.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httprequest.rb -- HTTPRequest Class # @@ -171,7 +171,7 @@ def initialize(config) @accept_charset = [] @accept_encoding = [] @accept_language = [] - @body = "" + @body = +"" @addr = @peeraddr = nil @attributes = {} diff --git a/lib/webrick/httpresponse.rb b/lib/webrick/httpresponse.rb index ba4494ab..c7238dc0 100644 --- a/lib/webrick/httpresponse.rb +++ b/lib/webrick/httpresponse.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httpresponse.rb -- HTTPResponse Class # @@ -332,7 +332,7 @@ def remove_body_tempfile # :nodoc: def send_header(socket) # :nodoc: if @http_version.major > 0 - data = status_line() + data = status_line().dup @header.each{|key, value| tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase } data << "#{tmp}: #{check_header(value)}" << CRLF @@ -419,7 +419,7 @@ def check_header(header_value) # :stopdoc: def error_body(backtrace, ex, host, port) - @body = '' + @body = +'' @body << <<-_end_of_html_ @@ -453,11 +453,11 @@ def send_body_io(socket) if @request_method == "HEAD" # do nothing elsif chunked? - buf = '' + buf = +'' begin @body.readpartial(@buffer_size, buf) size = buf.bytesize - data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}" + data = +"#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}" socket.write(data) data.clear @sent_size += size @@ -539,7 +539,7 @@ def write(buf) socket = @socket @resp.instance_eval { size = buf.bytesize - data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}" + data = +"#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}" socket.write(data) data.clear @sent_size += size diff --git a/lib/webrick/https.rb b/lib/webrick/https.rb index b0a49bc4..7f00b306 100644 --- a/lib/webrick/https.rb +++ b/lib/webrick/https.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # https.rb -- SSL/TLS enhancement for HTTPServer # diff --git a/lib/webrick/httpserver.rb b/lib/webrick/httpserver.rb index e85d0593..0d261bf0 100644 --- a/lib/webrick/httpserver.rb +++ b/lib/webrick/httpserver.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httpserver.rb -- HTTPServer Class # @@ -285,7 +285,7 @@ def compile end def normalize(dir) - ret = dir ? dir.dup : "" + ret = dir ? dir.dup : +"" ret.sub!(%r|/+\z|, "") ret end diff --git a/lib/webrick/httpservlet.rb b/lib/webrick/httpservlet.rb index da49a140..4cb1822c 100644 --- a/lib/webrick/httpservlet.rb +++ b/lib/webrick/httpservlet.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httpservlet.rb -- HTTPServlet Utility File # diff --git a/lib/webrick/httpservlet/abstract.rb b/lib/webrick/httpservlet/abstract.rb index bccb0918..6fae4de9 100644 --- a/lib/webrick/httpservlet/abstract.rb +++ b/lib/webrick/httpservlet/abstract.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httpservlet.rb -- HTTPServlet Module # diff --git a/lib/webrick/httpservlet/cgi_runner.rb b/lib/webrick/httpservlet/cgi_runner.rb index 0398c167..b0948ae3 100644 --- a/lib/webrick/httpservlet/cgi_runner.rb +++ b/lib/webrick/httpservlet/cgi_runner.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # cgi_runner.rb -- CGI launcher. # @@ -10,7 +10,7 @@ # $IPR: cgi_runner.rb,v 1.9 2002/09/25 11:33:15 gotoyuzo Exp $ def sysread(io, size) - buf = "" + buf = +"" while size > 0 tmp = io.sysread(size) buf << tmp diff --git a/lib/webrick/httpservlet/cgihandler.rb b/lib/webrick/httpservlet/cgihandler.rb index 4457770b..450aa380 100644 --- a/lib/webrick/httpservlet/cgihandler.rb +++ b/lib/webrick/httpservlet/cgihandler.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # cgihandler.rb -- CGIHandler Class # diff --git a/lib/webrick/httpservlet/erbhandler.rb b/lib/webrick/httpservlet/erbhandler.rb index cd09e5f2..3b232f7f 100644 --- a/lib/webrick/httpservlet/erbhandler.rb +++ b/lib/webrick/httpservlet/erbhandler.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # erbhandler.rb -- ERBHandler Class # diff --git a/lib/webrick/httpservlet/filehandler.rb b/lib/webrick/httpservlet/filehandler.rb index 010df0e9..7ab88bca 100644 --- a/lib/webrick/httpservlet/filehandler.rb +++ b/lib/webrick/httpservlet/filehandler.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # filehandler.rb -- FileHandler Module # @@ -481,9 +481,9 @@ def set_dir_list(req, res) elsif !namewidth or (namewidth = namewidth.to_i) < 2 namewidth = 25 end - query = query.inject('') {|s, (k, v)| s << '&' << HTMLUtils::escape("#{k}=#{v}")} + query = query.inject('') {|s, (k, v)| s << '&' << HTMLUtils::escape("#{k}=#{v}")}.dup - type = "text/html" + type = +"text/html" case enc = Encoding.find('filesystem') when Encoding::US_ASCII, Encoding::ASCII_8BIT else @@ -492,7 +492,7 @@ def set_dir_list(req, res) res['content-type'] = type title = "Index of #{HTMLUtils::escape(req.path)}" - res.body = <<-_end_of_html_ + res.body = +<<-_end_of_html_ @@ -528,7 +528,7 @@ def set_dir_list(req, res) else dname = name end - s = "#{HTMLUtils::escape(dname)}" + s = +"#{HTMLUtils::escape(dname)}" s << "" << (time ? time.strftime("%Y/%m/%d %H:%M") : "") << "" s << "" << (size >= 0 ? size.to_s : "-") << "\n" res.body << s diff --git a/lib/webrick/httpservlet/prochandler.rb b/lib/webrick/httpservlet/prochandler.rb index 599ffc43..dca5d2d0 100644 --- a/lib/webrick/httpservlet/prochandler.rb +++ b/lib/webrick/httpservlet/prochandler.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # prochandler.rb -- ProcHandler Class # diff --git a/lib/webrick/httpstatus.rb b/lib/webrick/httpstatus.rb index c811f219..c21c1d46 100644 --- a/lib/webrick/httpstatus.rb +++ b/lib/webrick/httpstatus.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # httpstatus.rb -- HTTPStatus Class # diff --git a/lib/webrick/httputils.rb b/lib/webrick/httputils.rb index f1b9ddf9..456384e7 100644 --- a/lib/webrick/httputils.rb +++ b/lib/webrick/httputils.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # httputils.rb -- HTTPUtils Module # @@ -231,7 +231,7 @@ def dequote(str) # Quotes and escapes quotes in +str+ def quote(str) - '"' << str.gsub(/[\\\"]/o, "\\\1") << '"' + +'"' << str.gsub(/[\\\"]/o, "\\\1") << '"' end module_function :quote @@ -495,7 +495,7 @@ def unescape_form(str) # Escapes path +str+ def escape_path(str) - result = "" + result = +"" str.scan(%r{/([^/]*)}).each{|i| result << "/" << _escape(i[0], UNESCAPED_PCHAR) } diff --git a/lib/webrick/httpversion.rb b/lib/webrick/httpversion.rb index 8a251944..63495903 100644 --- a/lib/webrick/httpversion.rb +++ b/lib/webrick/httpversion.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # HTTPVersion.rb -- presentation of HTTP version # diff --git a/lib/webrick/log.rb b/lib/webrick/log.rb index 2c1fdfe6..4de4bee1 100644 --- a/lib/webrick/log.rb +++ b/lib/webrick/log.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # log.rb -- Log Class # @@ -86,15 +86,15 @@ def <<(obj) end # Shortcut for logging a FATAL message - def fatal(msg) log(FATAL, "FATAL " << format(msg)); end + def fatal(msg) log(FATAL, "FATAL " + format(msg)); end # Shortcut for logging an ERROR message - def error(msg) log(ERROR, "ERROR " << format(msg)); end + def error(msg) log(ERROR, "ERROR " + format(msg)); end # Shortcut for logging a WARN message - def warn(msg) log(WARN, "WARN " << format(msg)); end + def warn(msg) log(WARN, "WARN " + format(msg)); end # Shortcut for logging an INFO message - def info(msg) log(INFO, "INFO " << format(msg)); end + def info(msg) log(INFO, "INFO " + format(msg)); end # Shortcut for logging a DEBUG message - def debug(msg) log(DEBUG, "DEBUG " << format(msg)); end + def debug(msg) log(DEBUG, "DEBUG " + format(msg)); end # Will the logger output FATAL messages? def fatal?; @level >= FATAL; end @@ -118,7 +118,7 @@ def debug?; @level >= DEBUG; end # * Otherwise it will return +arg+.inspect. def format(arg) if arg.is_a?(Exception) - "#{arg.class}: #{AccessLog.escape(arg.message)}\n\t" << + +"#{arg.class}: #{AccessLog.escape(arg.message)}\n\t" << arg.backtrace.join("\n\t") << "\n" elsif arg.respond_to?(:to_str) AccessLog.escape(arg.to_str) diff --git a/lib/webrick/server.rb b/lib/webrick/server.rb index fd6b7a61..478b1ac9 100644 --- a/lib/webrick/server.rb +++ b/lib/webrick/server.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # server.rb -- GenericServer Class # diff --git a/lib/webrick/ssl.rb b/lib/webrick/ssl.rb index e448095a..0c81c9eb 100644 --- a/lib/webrick/ssl.rb +++ b/lib/webrick/ssl.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # ssl.rb -- SSL/TLS enhancement for GenericServer # diff --git a/lib/webrick/utils.rb b/lib/webrick/utils.rb index a96d6f03..e4902d0b 100644 --- a/lib/webrick/utils.rb +++ b/lib/webrick/utils.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # utils.rb -- Miscellaneous utilities # @@ -78,7 +78,7 @@ def create_listeners(address, port) # Generates a random string of length +len+ def random_string(len) rand_max = RAND_CHARS.bytesize - ret = "" + ret = +"" len.times{ ret << RAND_CHARS[rand(rand_max)] } ret end diff --git a/lib/webrick/version.rb b/lib/webrick/version.rb index b62988bd..a4cfd504 100644 --- a/lib/webrick/version.rb +++ b/lib/webrick/version.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # version.rb -- version and release date # From 61d2a06af26b2c4c9011f5c2775d4919ff17829a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 16 Feb 2021 21:00:26 +0900 Subject: [PATCH 02/36] Removed test-suite of ruby repository --- test/lib/envutil.rb | 295 ------- test/lib/find_executable.rb | 22 - test/lib/leakchecker.rb | 203 ----- test/lib/minitest/autorun.rb | 14 - test/lib/minitest/mock.rb | 196 ----- test/lib/minitest/unit.rb | 1417 ------------------------------ test/lib/test/unit.rb | 1177 ------------------------- test/lib/test/unit/assertions.rb | 940 -------------------- test/lib/test/unit/parallel.rb | 208 ----- test/lib/test/unit/testcase.rb | 36 - 10 files changed, 4508 deletions(-) delete mode 100644 test/lib/envutil.rb delete mode 100644 test/lib/find_executable.rb delete mode 100644 test/lib/leakchecker.rb delete mode 100644 test/lib/minitest/autorun.rb delete mode 100644 test/lib/minitest/mock.rb delete mode 100644 test/lib/minitest/unit.rb delete mode 100644 test/lib/test/unit.rb delete mode 100644 test/lib/test/unit/assertions.rb delete mode 100644 test/lib/test/unit/parallel.rb delete mode 100644 test/lib/test/unit/testcase.rb diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb deleted file mode 100644 index d76ed660..00000000 --- a/test/lib/envutil.rb +++ /dev/null @@ -1,295 +0,0 @@ -# -*- coding: us-ascii -*- -# frozen_string_literal: true -require "open3" -require "timeout" -require_relative "find_executable" -begin - require 'rbconfig' -rescue LoadError -end -begin - require "rbconfig/sizeof" -rescue LoadError -end - -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" - - DEFAULT_SIGNALS = Signal.list - DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM - - class << self - attr_accessor :subprocess_timeout_scale - end - - def apply_timeout_scale(t) - if scale = EnvUtil.subprocess_timeout_scale - t * scale - else - t - end - end - module_function :apply_timeout_scale - - def timeout(sec, klass = nil, message = nil, &blk) - return yield(sec) if sec == nil or sec.zero? - sec = apply_timeout_scale(sec) - Timeout.timeout(sec, klass, message, &blk) - end - module_function :timeout - - def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, - encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error, - stdout_filter: nil, stderr_filter: nil, - signal: :TERM, - rubybin: EnvUtil.rubybin, - **opt) - timeout = apply_timeout_scale(timeout) - reprieve = apply_timeout_scale(reprieve) if reprieve - - 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)) - timeout_error = nil - else - signals = Array(signal).select do |sig| - DEFAULT_SIGNALS[sig.to_s] or - DEFAULT_SIGNALS[Signal.signame(sig)] rescue false - end - signals |= [:ABRT, :KILL] - case pgroup = opt[:pgroup] - when 0, true - pgroup = -pid - when nil, false - pgroup = pid - end - while signal = signals.shift - begin - Process.kill signal, pgroup - rescue Errno::EINVAL - next - rescue Errno::ESRCH - break - end - if signals.empty? or !reprieve - Process.wait(pid) - else - begin - Timeout.timeout(reprieve) {Process.wait(pid)} - rescue Timeout::Error - end - end - end - status = $? - end - stdout = th_stdout.value if capture_stdout - stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout - out_p.close if capture_stdout - err_p.close if capture_stderr && capture_stderr != :merge_to_stdout - status ||= Process.wait2(pid)[1] - stdout = stdout_filter.call(stdout) if stdout_filter - stderr = stderr_filter.call(stderr) if stderr_filter - if timeout_error - bt = caller_locations - msg = "execution of #{bt.shift.label} expired" - msg = Test::Unit::Assertions::FailDesc[status, msg, [stdout, stderr].join("\n")].() - raise timeout_error, msg, bt.map(&:to_s) - end - 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 - 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 = "".dup) - 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' - @ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME'] - - def self.diagnostic_reports(signame, pid, now) - return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame) - cmd = File.basename(rubybin) - cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == 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, pid, now) - end - end - - def self.gc_stress_to_class? - unless defined?(@gc_stress_to_class) - _, _, status = invoke_ruby(["-e""exit GC.respond_to?(:add_stress_to_class)"]) - @gc_stress_to_class = status.success? - end - @gc_stress_to_class - end -end - -if defined?(RbConfig) - module RbConfig - @ruby = EnvUtil.rubybin - class << self - undef ruby if method_defined?(:ruby) - attr_reader :ruby - end - dir = File.dirname(ruby) - CONFIG['bindir'] = dir - Gem::ConfigMap[:bindir] = dir if defined?(Gem::ConfigMap) - end -end diff --git a/test/lib/find_executable.rb b/test/lib/find_executable.rb deleted file mode 100644 index 89c6fb8f..00000000 --- a/test/lib/find_executable.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true -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/test/lib/leakchecker.rb b/test/lib/leakchecker.rb deleted file mode 100644 index be4b8368..00000000 --- a/test/lib/leakchecker.rb +++ /dev/null @@ -1,203 +0,0 @@ -# frozen_string_literal: true -class LeakChecker - def initialize - @fd_info = find_fds - @tempfile_info = find_tempfiles - @thread_info = find_threads - @env_info = find_env - end - - def check(test_name) - leaks = [ - check_fd_leak(test_name), - check_thread_leak(test_name), - check_tempfile_leak(test_name), - check_env(test_name) - ] - GC.start if leaks.any? - end - - def find_fds - if IO.respond_to?(:console) and (m = IO.method(:console)).arity.nonzero? - m[:close] - end - 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 - 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 = ''.dup - 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.instance_variable_defined?(:@tmpfile) and t.path - } - [count, tempfiles] - end - end - - def check_tempfile_leak(test_name) - return false 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 && 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 find_env - ENV.to_h - end - - def check_env(test_name) - old_env = @env_info - new_env = ENV.to_h - return false if old_env == new_env - (old_env.keys | new_env.keys).sort.each {|k| - if old_env.has_key?(k) - if new_env.has_key?(k) - if old_env[k] != new_env[k] - puts "Environment variable changed: #{test_name} : #{k.inspect} changed : #{old_env[k].inspect} -> #{new_env[k].inspect}" - end - else - puts "Environment variable changed: #{test_name} : #{k.inspect} deleted" - end - else - if new_env.has_key?(k) - puts "Environment variable changed: #{test_name} : #{k.inspect} added" - else - flunk "unreachable" - end - end - } - @env_info = new_env - return true - end - - def puts(*a) - MiniTest::Unit.output.puts(*a) - end -end diff --git a/test/lib/minitest/autorun.rb b/test/lib/minitest/autorun.rb deleted file mode 100644 index 84409662..00000000 --- a/test/lib/minitest/autorun.rb +++ /dev/null @@ -1,14 +0,0 @@ -# encoding: utf-8 -# frozen_string_literal: true - -begin - require 'rubygems' - gem 'minitest' -rescue Gem::LoadError - # do nothing -end - -require 'minitest/unit' -require 'minitest/mock' - -MiniTest::Unit.autorun diff --git a/test/lib/minitest/mock.rb b/test/lib/minitest/mock.rb deleted file mode 100644 index 224b06cb..00000000 --- a/test/lib/minitest/mock.rb +++ /dev/null @@ -1,196 +0,0 @@ -# encoding: utf-8 -# frozen_string_literal: true - -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/test/lib/minitest/unit.rb b/test/lib/minitest/unit.rb deleted file mode 100644 index 672fd152..00000000 --- a/test/lib/minitest/unit.rb +++ /dev/null @@ -1,1417 +0,0 @@ -# encoding: utf-8 -# frozen_string_literal: true - -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 - ## - # 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 exp == act 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+ =~ +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 = (predicate = true; nil), msg = nil - return assert_predicate o1, op, msg if predicate - 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 - alias capture_output capture_io - - ## - # 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 exp == act. - # - # 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+ =~ +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 = (predicate = true; nil), msg = nil - return refute_predicate o1, op, msg if predicate - 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? - - puts - puts "# Running #{type}s:" - puts - - @test_count, @assertion_count = 0, 0 - test_count = assertion_count = 0 - sync = output.respond_to? :"sync=" # stupid emacs - old_sync, output.sync = output.sync, true if sync - - count = 0 - begin - start = Time.now - - results = _run_suites suites, type - - @test_count = results.inject(0) { |sum, (tc, _)| sum + tc } - @assertion_count = results.inject(0) { |sum, (_, ac)| sum + ac } - test_count += @test_count - assertion_count += @assertion_count - t = Time.now - start - count += 1 - unless @repeat_count - puts - puts - end - puts "Finished%s %ss in %.6fs, %.4f tests/s, %.4f assertions/s.\n" % - [(@repeat_count ? "(#{count}/#{@repeat_count}) " : ""), type, - t, @test_count.fdiv(t), @assertion_count.fdiv(t)] - end while @repeat_count && count < @repeat_count && - report.empty? && failures.zero? && errors.zero? - - output.sync = old_sync if sync - - report.each_with_index do |msg, i| - puts "\n%3d) %s" % [i + 1, msg] - end - - puts - @test_count = test_count - @assertion_count = assertion_count - - 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 - - unless defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # compiler process is wrongly considered as leaked - leakchecker.check("#{inst.class}\##{inst.__name__}") - end - - 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.b}\n #{bt}\n" - end - @report << e - e[0, 1] - end - - def initialize # :nodoc: - @report = [] - @errors = @failures = @skips = 0 - @verbose = false - @mutex = Thread::Mutex.new - @info_signal = Signal.list['INFO'] - @repeat_count = nil - 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 - - ## - # 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/test/lib/test/unit.rb b/test/lib/test/unit.rb deleted file mode 100644 index c280c685..00000000 --- a/test/lib/test/unit.rb +++ /dev/null @@ -1,1177 +0,0 @@ -# frozen_string_literal: true -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) - @run_options = orig_args - @help = orig_args.map { |s| s =~ /[\s|&<>$()]/ ? s.inspect : s }.join " " - @options = options - end - - private - def setup_options(opts, options) - opts.separator '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 - 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 method names on pattern: /REGEXP/, !/REGEXP/ or STRING" do |a| - (options[:filter] ||= []) << a - end - - opts.on '--test-order=random|alpha|sorted', [:random, :alpha, :sorted] do |a| - MiniTest::Unit::TestCase.test_order = a - end - end - - def non_options(files, options) - filter = options[:filter] - if filter - pos_pat = /\A\/(.*)\/\z/ - neg_pat = /\A!\/(.*)\/\z/ - negative, positive = filter.partition {|s| neg_pat =~ s} - if positive.empty? - filter = nil - elsif negative.empty? and positive.size == 1 and pos_pat !~ positive[0] - filter = positive[0] - else - filter = Regexp.union(*positive.map! {|s| Regexp.new(s[pos_pat, 1] || "\\A#{Regexp.quote(s)}\\z")}) - end - unless negative.empty? - negative = Regexp.union(*negative.map! {|s| Regexp.new(s[neg_pat, 1])}) - filter = /\A(?=.*#{filter})(?!.*#{negative})/ - end - if Regexp === filter - # bypass conversion in minitest - def filter.=~(other) # :nodoc: - super unless Regexp === other - end - end - options[:filter] = filter - end - true - end - end - - module Parallel # :nodoc: all - def process_args(args = []) - return @options if @options - options = super - if @options[:parallel] - @files = args - end - options - end - - def non_options(files, options) - @jobserver = nil - if !options[:parallel] and - /(?:\A|\s)--jobserver-(?:auth|fds)=(\d+),(\d+)/ =~ ENV["MAKEFLAGS"] - begin - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - rescue - r.close if r - nil - else - @jobserver = [r, w] - options[:parallel] ||= 1 - end - end - super - end - - def status(*args) - result = super - raise @interrupt if @interrupt - result - end - - private - def setup_options(opts, options) - super - - opts.separator "parallel test options:" - - options[:retry] = true - - opts.on '-j N', '--jobs N', /\A(t)?(\d+)\z/, "Allow run tests with N jobs at once" do |_, t, a| - options[:testing] = true & t # For testing - options[:parallel] = a.to_i - 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 which is used at -j option" do |a| - options[:ruby] = a.split(/ /).reject(&:empty?) - end - end - - class Worker - def self.launch(ruby,args=[]) - io = IO.popen([*ruby, "-W1", - "#{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/ =~ $!.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" - end - - def kill - Process.kill(:KILL, @pid) - rescue Errno::ESRCH - end - - def died(*additional) - @status = :quit - @io.close - status = $? - if status and status.signaled? - additional[0] ||= SignalException.new(status.termsig) - end - - call_hook(:dead,*additional) - end - - def to_s - if @file and @status != :ready - "#{@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 - - def flush_job_tokens - if @jobserver - r, w = @jobserver.shift(2) - @jobserver = nil - w << @job_tokens.slice!(0..-1) - r.close - w.close - end - end - - def after_worker_down(worker, e=nil, c=false) - return unless @options[:parallel] - return if @interrupt - flush_job_tokens - warn e if e - real_file = worker.real_file and warn "running file: #{real_file}" - @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 after_worker_quit(worker) - return unless @options[:parallel] - return if @interrupt - worker.close - if @jobserver and (token = @job_tokens.slice!(0)) - @jobserver[1] << token - end - @workers.delete(worker) - @dead_workers << worker - @ios = @workers.map(&:io) - end - - def launch_worker - begin - worker = Worker.launch(@options[:ruby], @run_options) - 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.timeout(1) do - worker.quit - end - rescue Errno::EPIPE - rescue Timeout::Error - end - worker.close - end - - return if @workers.empty? - begin - Timeout.timeout(0.2 * @workers.size) do - Process.waitall - end - rescue Timeout::Error - @workers.each do |worker| - worker.kill - end - @worker.clear - end - end - - FakeClass = Struct.new(:name) - def fake_class(name) - (@fake_classes ||= {})[name] ||= FakeClass.new(name) - end - - def deal(io, type, result, rep, shutting_down = false) - worker = @workers_hash[io] - cmd = worker.read - cmd.sub!(/\A\.+/, '') if cmd # read may return nil - case cmd - when '' - # just only dots, ignore - when /^okay$/ - worker.status = :running - when /^ready(!)?$/ - bang = $1 - worker.status = :ready - - unless task = @tasks.shift - worker.quit - return nil - end - if @options[:separate] and not bang - worker.quit - worker = add_worker - end - worker.run(task, type) - @test_count += 1 - - jobs_status(worker) - 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! - jobs_status(worker) if @options[:job_status] == :replace - return true - when /^record (.+?)$/ - begin - r = Marshal.load($1.unpack("m")[0]) - rescue => e - print "unknown record: #{e.message} #{$1.unpack("m")[0].dump}" - return true - end - record(fake_class(r[0]), *r[1..-1]) - when /^p (.+?)$/ - del_jobs_status - print $1.unpack("m")[0] - jobs_status(worker) 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 - @job_tokens = String.new(encoding: Encoding::ASCII_8BIT) if @jobserver - begin - [@tasks.size, @options[:parallel]].min.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 - if @jobserver and @job_tokens and !@tasks.empty? and !@workers.any? {|x| x.status == :ready} - t = @jobserver[0].read_nonblock([@tasks.size, @options[:parallel]].min, exception: false) - if String === t - @job_tokens << t - t.size.times {launch_worker} - end - end - end - rescue Interrupt => ex - @interrupt = ex - return result - ensure - 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 - flush_job_tokens - - unless @interrupt || !@options[:retry] || @need_quit - parallel = @options[:parallel] - @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 "\n""Retrying..." - _run_suites(suites, type) - end - @options[:parallel] = parallel - 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 - del_status_line - result - end - end - - module Skipping # :nodoc: all - def failed(s) - super if !s or @options[:hide_skip] - end - - private - def setup_options(opts, options) - super - - opts.separator "skipping options:" - - options[:hide_skip] = true - - 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 - end - - private - def _run_suites(suites, type) - result = super - 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) } - failed(nil) - result - end - end - - module Statistics - def update_list(list, rec, max) - if i = list.empty? ? 0 : list.bsearch_index {|*a| yield(*a)} - list[i, 0] = [rec] - list[max..-1] = [] if list.size >= max - end - end - - def record(suite, method, assertions, time, error) - if @options.values_at(:longest, :most_asserted).any? - @tops ||= {} - rec = [suite.name, method, assertions, time, error] - if max = @options[:longest] - update_list(@tops[:longest] ||= [], rec, max) {|_,_,_,t,_|t 0 - end - $stdout.flush if flush - @status_line_size = 0 - end - - def add_status(line) - @status_line_size ||= 0 - if @options[:job_status] == :replace - line = line[0...(terminal_width-@status_line_size)] - end - print line - @status_line_size += line.size - end - - def jobs_status(worker) - return if !@options[:job_status] or @options[:verbose] - if @options[:job_status] == :replace - status_line = @workers.map(&:to_s).join(" ") - else - status_line = worker.to_s - end - 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 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 = (@tty || @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+)=([^:\n]*)/)] : {} - begin - File.read(File.join(__dir__, "../../colors")).scan(/(\w+)=([^:\n]*)/) do |n, c| - colors[n] ||= c - end - rescue - end - @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] - end - @output = Output.new(self) unless @options[:testing] - filter = options[:filter] - 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) - del_status_line(false) - print(@passed_color) - add_status("[#{count}/#{@total_tests}]") - print(@reset_color) - add_status(" #{s}") - $stdout.print "\r" if @options[:job_status] == :replace and !@verbose - $stdout.flush - end - - def _print(s); $stdout.print(s); end - def succeed; del_status_line; end - - def failed(s) - return if s and @options[:job_status] != :replace - 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 - - def initialize - super - @tty = $stdout.tty? - end - - def run(*args) - result = super - puts "\nruby -v: #{RUBY_DESCRIPTION}" - result - end - - private - def setup_options(opts, options) - super - - opts.separator "status line options:" - - options[:job_status] = nil - - opts.on '--jobs-status [TYPE]', [:normal, :replace, :none], - "Show status of jobs every file; Disabled when --jobs isn't specified." do |type| - options[:job_status] = (type || :normal if type != :none) - 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 - - class Output < 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") - when /\A\.+\z/ - runner.succeed - when /\A[EFS]\z/ - runner.failed(s) - else - $stdout.print(s) - end - end - end - end - - module LoadPathOption # :nodoc: all - 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 - - super - end - - def setup_options(parser, options) - super - parser.separator "load path options:" - parser.on '-Idirectory', 'Add library load path' do |dirs| - dirs.split(':').each { |d| $LOAD_PATH.unshift d } - end - end - end - - module GlobOption # :nodoc: all - @@testfile_prefix = "test" - @@testfile_suffix = "test" - - def setup_options(parser, options) - super - parser.separator "globbing options:" - parser.on '-b', '--basedir=DIR', 'Base directory of test suites.' do |dir| - options[:base_directory] = dir - end - parser.on '-x', '--exclude REGEXP', '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"#{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"] + Dir["#{path}/**/*_#{@@testfile_suffix}.rb"]).uniq).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 GCStressOption # :nodoc: all - def setup_options(parser, options) - super - parser.separator "GC options:" - 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 - - module RepeatOption # :nodoc: all - def setup_options(parser, options) - super - options[:repeat_count] = nil - parser.separator "repeat options:" - parser.on '--repeat-count=NUM', "Number of times to repeat", Integer do |n| - options[:repeat_count] = n - end - end - - def _run_anything(type) - @repeat_count = @options[:repeat_count] - super - end - end - - module ExcludesOption # :nodoc: all - class ExcludedMethods < Struct.new(:excludes) - def exclude(name, reason) - excludes[name] = reason - end - - def exclude_from(klass) - excludes = self.excludes - pattern = excludes.keys.grep(Regexp).tap {|k| - break (Regexp.new(k.join('|')) unless k.empty?) - } - klass.class_eval do - public_instance_methods(false).each do |method| - if excludes[method] or (pattern and pattern =~ method) - remove_method(method) - end - end - public_instance_methods(true).each do |method| - if excludes[method] or (pattern and pattern =~ method) - undef_method(method) - end - end - end - end - - def self.load(dirs, name) - return unless dirs and name - instance = nil - dirs.each do |dir| - path = File.join(dir, name.gsub(/::/, '/') + ".rb") - begin - src = File.read(path) - rescue Errno::ENOENT - nil - else - instance ||= new({}) - instance.instance_eval(src, path) - end - end - instance - end - end - - def setup_options(parser, options) - super - if excludes = ENV["EXCLUDES"] - excludes = excludes.split(File::PATH_SEPARATOR) - end - options[:excludes] = excludes || [] - parser.separator "excludes options:" - parser.on '-X', '--excludes-dir DIRECTORY', "Directory name of exclude files" do |d| - options[:excludes].concat d.split(File::PATH_SEPARATOR) - end - end - - def _run_suite(suite, type) - if ex = ExcludedMethods.load(@options[:excludes], suite.name) - ex.exclude_from(suite) - end - super - end - end - - module SubprocessOption - def setup_options(parser, options) - super - parser.separator "subprocess options:" - parser.on '--subprocess-timeout-scale NUM', "Scale subprocess timeout", Float do |scale| - raise OptionParser::InvalidArgument, "timeout scale must be positive" unless scale > 0 - options[:timeout_scale] = scale - end - if scale = options[:timeout_scale] or - (scale = ENV["RUBY_TEST_SUBPROCESS_TIMEOUT_SCALE"] and (scale = scale.to_f) > 0) - EnvUtil.subprocess_timeout_scale = scale - end - end - end - - class Runner < MiniTest::Unit # :nodoc: all - include Test::Unit::Options - include Test::Unit::StatusLine - include Test::Unit::Parallel - include Test::Unit::Statistics - include Test::Unit::Skipping - include Test::Unit::GlobOption - include Test::Unit::RepeatOption - include Test::Unit::LoadPathOption - include Test::Unit::GCStressOption - include Test::Unit::ExcludesOption - include Test::Unit::SubprocessOption - include Test::Unit::RunCount - - 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 - - alias mini_run_suite _run_suite - - # 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 - 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 - test_order = self.test_order - class << self - attr_writer :test_order - undef test_order - end - def self.test_order - defined?(@test_order) ? @test_order : superclass.test_order - end - self.test_order = test_order - 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/test/lib/test/unit/assertions.rb b/test/lib/test/unit/assertions.rb deleted file mode 100644 index ee6a758f..00000000 --- a/test/lib/test/unit/assertions.rb +++ /dev/null @@ -1,940 +0,0 @@ -# frozen_string_literal: true -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 - - def assert_raises(*exp, &b) - raise NoMethodError, "use assert_raise", caller - 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 = m = nil - EnvUtil.with_default_internal(expected.encoding) do - ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do - yield - end - m = ex.message - end - msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} - - if assert == :assert_equal - assert_equal(expected, m, msg) - else - msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" } - assert expected =~ m, 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 - 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, < -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) - unless priv.empty? - msg = message(msg) { - "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}" - } - return assert obj.respond_to?(meth, *priv), msg - end - #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) - return if obj.respond_to?(meth) - end - super(obj, meth, msg) - end - - # :call-seq: - # assert_not_respond_to( object, method, failure_message = nil ) - # - #Tests if the given Object does not respond to +method+. - # - #An optional failure message may be provided as the final argument. - # - # assert_not_respond_to("hello", :reverse) #Fails - # assert_not_respond_to("hello", :does_not_exist) #Succeeds - def assert_not_respond_to(obj, (meth, *priv), msg = nil) - unless priv.empty? - msg = message(msg) { - "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}" - } - return assert !obj.respond_to?(meth, *priv), msg - end - #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) - return unless obj.respond_to?(meth) - end - refute_respond_to(obj, meth, msg) - 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_'.dup << 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 - - # compatibility with test-unit - alias pend skip - - if defined?(RubyVM::InstructionSequence) - def syntax_check(code, fname, line) - code = code.dup.force_encoding(Encoding::UTF_8) - RubyVM::InstructionSequence.compile(code, fname, fname, line) - :ok - end - else - def syntax_check(code, fname, line) - code = code.b - code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { - "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n" - } - code = code.force_encoding(Encoding::UTF_8) - catch {|tag| eval(code, binding, fname, line - 1)} - end - end - - def prepare_syntax_check(code, fname = caller_locations(2, 1)[0], mesg = fname.to_s, verbose: nil) - verbose, $VERBOSE = $VERBOSE, verbose - case - when Array === fname - fname, line = *fname - when defined?(fname.path) && defined?(fname.lineno) - fname, line = fname.path, fname.lineno - else - line = 1 - end - yield(code, fname, line, mesg) - ensure - $VERBOSE = verbose - end - - def assert_valid_syntax(code, *args) - prepare_syntax_check(code, *args) do |src, fname, line, mesg| - yield if defined?(yield) - assert_nothing_raised(SyntaxError, mesg) do - assert_equal(:ok, syntax_check(src, fname, line), mesg) - end - end - end - - def assert_syntax_error(code, error, *args) - prepare_syntax_check(code, *args) do |src, fname, line, mesg| - yield if defined?(yield) - e = assert_raise(SyntaxError, mesg) do - syntax_check(src, fname, line) - end - assert_match(error, e.message, mesg) - e - end - 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, pid, now) - if signame - sigdesc = "SIG#{signame} (#{sigdesc})" - end - if status.coredump? - sigdesc = "#{sigdesc} (core dumped)" - end - full_message = ''.dup - message = message.call if Proc === message - if message and !message.empty? - full_message << message << "\n" - end - full_message << "pid #{pid}" - full_message << " exit #{status.exitstatus}" if status.exited? - full_message << " killed by #{sigdesc}" if sigdesc - if out and !out.empty? - full_message << "\n" << out.b.gsub(/^/, '| ') - full_message.sub!(/(? 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 unless SystemExit === res - end - - # really is it succeed? - unless ignore_stderr - # the body of assert_separately must not output anything to detect error - assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr]) - end - assert(status.success?, FailDesc[status, "assert_separately failed", stderr]) - raise marshal_error if marshal_error - end - - def assert_warning(pat, msg = nil) - stderr = EnvUtil.verbose_warning { - EnvUtil.with_default_internal(pat.encoding) { - yield - } - } - msg = message(msg) {diff pat, stderr} - assert(pat === stderr, msg) - end - - def assert_warn(*args) - assert_warning(*args) {$VERBOSE = false; yield} - end - - def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) - require_relative '../../memory_status' - raise MiniTest::Skip, "unsupported platform" unless defined?(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? Memory::NO_MEMORY_LEAK_ENVS then - envs ||= {} - newenvs = envs.merge(Memory::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(status.success?, FailDesc[status, message, err]) - ([: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_cpu_usage_low(msg = nil, pct: 0.01) - require 'benchmark' - - tms = Benchmark.measure(msg || '') { yield } - max = pct * tms.real - if tms.real < 0.1 # TIME_QUANTUM_USEC in thread_pthread.c - warn "test #{msg || 'assert_cpu_usage_low'} too short to be accurate" - end - - # kernel resolution can limit the minimum time we can measure - # [ruby-core:81540] - min_hz = windows? ? 67 : 100 - min_measurable = 1.0 / min_hz - min_measurable *= 1.10 # add a little (10%) to account for misc. overheads - if max < min_measurable - max = min_measurable - end - - assert_operator tms.total, :<=, max, msg - 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 ".dup << 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 - - class AllFailures - attr_reader :failures - - def initialize - @count = 0 - @failures = {} - end - - def for(key) - @count += 1 - yield - rescue Exception => e - @failures[key] = [@count, e] - end - - def foreach(*keys) - keys.each do |key| - @count += 1 - begin - yield key - rescue Exception => e - @failures[key] = [@count, e] - end - end - end - - def message - i = 0 - total = @count.to_s - fmt = "%#{total.size}d" - @failures.map {|k, (n, v)| - "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.message.b.gsub(/^/, ' | ')}" - }.join("\n") - end - - def pass? - @failures.empty? - end - end - - def assert_all_assertions(msg = nil) - all = AllFailures.new - yield all - ensure - assert(all.pass?, message(msg) {all.message.chomp(".")}) - end - alias all_assertions assert_all_assertions - - def assert_all_assertions_foreach(msg = nil, *keys, &block) - all = AllFailures.new - all.foreach(*keys, &block) - ensure - assert(all.pass?, message(msg) {all.message.chomp(".")}) - end - alias all_assertions_foreach assert_all_assertions_foreach - - 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(/(? e - begin - trace = e.backtrace || ['unknown method'] - 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: - @stdout.write(args.empty? ? "#{res}\n" : "#{res} #{args.pack("m0")}\n") - 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 - - def record(suite, method, assertions, time, error) # :nodoc: - case error - when nil - when MiniTest::Assertion, MiniTest::Skip - case error.cause - when nil, MiniTest::Assertion, MiniTest::Skip - else - bt = error.backtrace - error = error.class.new(error.message) - error.set_backtrace(bt) - end - else - error = ProxyError.new(error) - end - _report "record", Marshal.dump([suite.name, method, assertions, time, error]) - super - end - end - end -end - -if $0 == __FILE__ - module Test - module Unit - class TestCase < MiniTest::Unit::TestCase # :nodoc: all - undef on_parallel_worker? - def on_parallel_worker? - true - end - end - end - end - require 'rubygems' - module Gem # :nodoc: - end - class Gem::TestCase < MiniTest::Unit::TestCase # :nodoc: - @@project_dir = File.expand_path('../../../../..', __FILE__) - end - - Test::Unit::Worker.new.run(ARGV) -end diff --git a/test/lib/test/unit/testcase.rb b/test/lib/test/unit/testcase.rb deleted file mode 100644 index 58cfbcab..00000000 --- a/test/lib/test/unit/testcase.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true -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) - super - 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 From 9b40c7955f114c89b21dd762cd4ae5161b6dce99 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 16 Feb 2021 21:34:30 +0900 Subject: [PATCH 03/36] Use test-unit and CoreAssertions instead of deep copy from ruby core repository --- Rakefile | 13 +- test/lib/core_assertions.rb | 763 ++++++++++++++++++++++++++++++++++++ test/lib/envutil.rb | 365 +++++++++++++++++ test/lib/find_executable.rb | 22 ++ test/lib/helper.rb | 4 + 5 files changed, 1164 insertions(+), 3 deletions(-) create mode 100644 test/lib/core_assertions.rb create mode 100644 test/lib/envutil.rb create mode 100644 test/lib/find_executable.rb create mode 100644 test/lib/helper.rb diff --git a/Rakefile b/Rakefile index 43a5a3f5..d81dd1f4 100644 --- a/Rakefile +++ b/Rakefile @@ -2,9 +2,16 @@ require "bundler/gem_tasks" require "rake/testtask" Rake::TestTask.new(:test) do |t| - t.libs << "test" << "test/lib" - t.libs << "lib" - t.test_files = FileList['test/**/test_*.rb'] + t.libs << "test/lib" + t.ruby_opts << "-rhelper" + t.test_files = FileList["test/**/test_*.rb"] +end + +task :sync_tool do + require 'fileutils' + FileUtils.cp "../ruby/tool/lib/test/unit/core_assertions.rb", "./test/lib" + FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib" + FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib" end task :default => :test diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb new file mode 100644 index 00000000..118c0d11 --- /dev/null +++ b/test/lib/core_assertions.rb @@ -0,0 +1,763 @@ +# frozen_string_literal: true + +module Test + module Unit + module Assertions + def _assertions= n # :nodoc: + @_assertions = n + end + + def _assertions # :nodoc: + @_assertions ||= 0 + end + + ## + # Returns a proc that will output +msg+ along with the default message. + + def message msg = nil, ending = nil, &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 + end + + module CoreAssertions + if defined?(MiniTest) + require_relative '../../envutil' + # for ruby core testing + include MiniTest::Assertions + + # Compatibility hack for assert_raise + Test::Unit::AssertionFailedError = MiniTest::Assertion + else + module MiniTest + class Assertion < Exception; end + class Skip < Assertion; end + end + + require 'pp' + require_relative 'envutil' + include Test::Unit::Assertions + end + + def mu_pp(obj) #:nodoc: + obj.pretty_inspect.chomp + end + + def assert_file + AssertFile + end + + FailDesc = proc do |status, message = "", out = ""| + now = Time.now + proc do + EnvUtil.failure_description(status, now, message, out) + end + end + + def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, + success: nil, **opt) + args = Array(args).dup + args.insert((Hash === args[0] ? 1 : 0), '--disable=gems') + stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt) + desc = FailDesc[status, message, stderr] + 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 + all_assertions(desc) do |a| + [["stdout", test_stdout, stdout], ["stderr", test_stderr, stderr]].each do |key, exp, act| + a.for(key) do + if exp.is_a?(Regexp) + assert_match(exp, act) + elsif exp.all? {|e| String === e} + assert_equal(exp, act.lines.map {|l| l.chomp }) + else + assert_pattern_list(exp, act) + end + end + end + unless success.nil? + a.for("success?") do + if success + assert_predicate(status, :success?) + else + assert_not_predicate(status, :success?) + end + end + end + end + status + end + end + + if defined?(RubyVM::InstructionSequence) + def syntax_check(code, fname, line) + code = code.dup.force_encoding(Encoding::UTF_8) + RubyVM::InstructionSequence.compile(code, fname, fname, line) + :ok + ensure + raise if SyntaxError === $! + end + else + def syntax_check(code, fname, line) + code = code.b + code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { + "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n" + } + code = code.force_encoding(Encoding::UTF_8) + catch {|tag| eval(code, binding, fname, line - 1)} + end + end + + def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) + # TODO: consider choosing some appropriate limit for MJIT and stop skipping this once it does not randomly fail + pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? + + require_relative '../../memory_status' + raise MiniTest::Skip, "unsupported platform" unless defined?(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? Memory::NO_MEMORY_LEAK_ENVS then + envs ||= {} + newenvs = envs.merge(Memory::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(status.success?, FailDesc[status, message, err]) + ([: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 + pend + 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)}>\n" + + "Backtrace:\n" + + e.backtrace.map{|frame| " #{frame}"}.join("\n") + } + raise MiniTest::Assertion, msg.call, bt + else + raise + end + end + end + + def prepare_syntax_check(code, fname = nil, mesg = nil, verbose: nil) + fname ||= caller_locations(2, 1)[0] + mesg ||= fname.to_s + verbose, $VERBOSE = $VERBOSE, verbose + case + when Array === fname + fname, line = *fname + when defined?(fname.path) && defined?(fname.lineno) + fname, line = fname.path, fname.lineno + else + line = 1 + end + yield(code, fname, line, message(mesg) { + if code.end_with?("\n") + "```\n#{code}```\n" + else + "```\n#{code}\n```\n""no-newline" + end + }) + ensure + $VERBOSE = verbose + end + + def assert_valid_syntax(code, *args, **opt) + prepare_syntax_check(code, *args, **opt) do |src, fname, line, mesg| + yield if defined?(yield) + assert_nothing_raised(SyntaxError, mesg) do + assert_equal(:ok, syntax_check(src, fname, line), mesg) + end + end + 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 + + def assert_ruby_status(args, test_stdin="", message=nil, **opt) + out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt) + desc = FailDesc[status, message, out] + assert(!status.signaled?, desc) + message ||= "ruby exit status is not success:" + assert(status.success?, desc) + end + + ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") + + def separated_runner(out = nil) + out = out ? IO.new(out, 'w') : STDOUT + at_exit { + out.puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}" + } + Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) if defined?(Test::Unit::Runner) + end + + 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 + capture_stdout = true + unless /mswin|mingw/ =~ RUBY_PLATFORM + capture_stdout = false + opt[:out] = MiniTest::Unit.output if defined?(MiniTest::Unit) + res_p, res_c = IO.pipe + opt[res_c.fileno] = res_c.fileno + end + src = < marshal_error + ignore_stderr = nil + res = nil + end + if res and !(SystemExit === 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(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr]) + end + assert(status.success?, FailDesc[status, "assert_separately failed", stderr]) + raise marshal_error if marshal_error + end + + # Run Ractor-related test without influencing the main test suite + def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt) + return unless defined?(Ractor) + + require = "require #{require.inspect}" if require + if require_relative + dir = File.dirname(caller_locations[0,1][0].absolute_path) + full_path = File.expand_path(require_relative, dir) + require = "#{require}; require #{full_path.inspect}" + end + + assert_separately(args, file, line, <<~RUBY, ignore_stderr: ignore_stderr, **opt) + #{require} + previous_verbose = $VERBOSE + $VERBOSE = nil + Ractor.new {} # trigger initial warning + $VERBOSE = previous_verbose + #{src} + RUBY + 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_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 { + flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"}) + } + + return e + ensure + unless e + exp = exp.first if exp.size == 1 + + flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"}) + end + end + 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 = m = nil + EnvUtil.with_default_internal(expected.encoding) do + ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do + yield + end + m = ex.message + end + msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} + + if assert == :assert_equal + assert_equal(expected, m, msg) + else + msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" } + assert expected =~ m, msg + block.binding.eval("proc{|_|$~=_}").call($~) + end + ex + 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_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) + unless priv.empty? + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}" + } + return assert obj.respond_to?(meth, *priv), msg + end + #get rid of overcounting + if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + return if obj.respond_to?(meth) + end + super(obj, meth, msg) + end + + # :call-seq: + # assert_not_respond_to( object, method, failure_message = nil ) + # + #Tests if the given Object does not respond to +method+. + # + #An optional failure message may be provided as the final argument. + # + # assert_not_respond_to("hello", :reverse) #Fails + # assert_not_respond_to("hello", :does_not_exist) #Succeeds + def assert_not_respond_to(obj, (meth, *priv), msg = nil) + unless priv.empty? + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}" + } + return assert !obj.respond_to?(meth, *priv), msg + end + #get rid of overcounting + if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + return unless obj.respond_to?(meth) + end + refute_respond_to(obj, meth, msg) + 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 + + def assert_warning(pat, msg = nil) + result = nil + stderr = EnvUtil.with_default_internal(pat.encoding) { + EnvUtil.verbose_warning { + result = yield + } + } + msg = message(msg) {diff pat, stderr} + assert(pat === stderr, msg) + result + end + + def assert_warn(*args) + assert_warning(*args) {$VERBOSE = false; yield} + end + + def assert_deprecated_warning(mesg = /deprecated/) + assert_warning(mesg) do + Warning[:deprecated] = true + yield + end + end + + def assert_deprecated_warn(mesg = /deprecated/) + assert_warn(mesg) do + Warning[:deprecated] = true + yield + end + end + + class << (AssertFile = Struct.new(:failure_message).new) + include CoreAssertions + 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 ".dup << 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 + + class AllFailures + attr_reader :failures + + def initialize + @count = 0 + @failures = {} + end + + def for(key) + @count += 1 + yield + rescue Exception => e + @failures[key] = [@count, e] + end + + def foreach(*keys) + keys.each do |key| + @count += 1 + begin + yield key + rescue Exception => e + @failures[key] = [@count, e] + end + end + end + + def message + i = 0 + total = @count.to_s + fmt = "%#{total.size}d" + @failures.map {|k, (n, v)| + v = v.message + "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.b.gsub(/^/, ' | ').force_encoding(v.encoding)}" + }.join("\n") + end + + def pass? + @failures.empty? + 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, $!] + th = nil + end + end + values + ensure + if th&.alive? + th.raise(Timeout::Error.new) + th.join rescue errs << [th, $!] + end + if !errs.empty? + msg = "exceptions on #{errs.length} threads:\n" + + errs.map {|t, err| + "#{t.inspect}:\n" + + RUBY_VERSION >= "2.5.0" ? err.full_message(highlight: false, order: :top) : err.message + }.join("\n---\n") + if message + msg = "#{message}\n#{msg}" + end + raise MiniTest::Assertion, msg + end + end + + def assert_all_assertions(msg = nil) + all = AllFailures.new + yield all + ensure + assert(all.pass?, message(msg) {all.message.chomp(".")}) + end + alias all_assertions assert_all_assertions + + 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(/(? Date: Tue, 16 Feb 2021 21:34:39 +0900 Subject: [PATCH 04/36] Use Gemfile instead of Gem::Specification#add_development_dependency. --- Gemfile | 3 +++ webrick.gemspec | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index fa75df15..10284be2 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,6 @@ source 'https://rubygems.org' gemspec + +gem "rake" +gem "test-unit" diff --git a/webrick.gemspec b/webrick.gemspec index 2aab8560..f3f8370e 100644 --- a/webrick.gemspec +++ b/webrick.gemspec @@ -69,6 +69,4 @@ Gem::Specification.new do |s| "bug_tracker_uri" => "https://github.com/ruby/webrick/issues", } end - - s.add_development_dependency "rake" end From c8abb708c854641b2632d963acae555f39899c93 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 16 Feb 2021 21:38:14 +0900 Subject: [PATCH 05/36] Use Test::Unit::TestCase instead of Minitest::Unit::TestCase --- test/webrick/test_httpresponse.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/webrick/test_httpresponse.rb b/test/webrick/test_httpresponse.rb index 89a0f703..07648600 100644 --- a/test/webrick/test_httpresponse.rb +++ b/test/webrick/test_httpresponse.rb @@ -1,11 +1,10 @@ # frozen_string_literal: false require "webrick" -require "minitest/autorun" require "stringio" require "net/http" module WEBrick - class TestHTTPResponse < MiniTest::Unit::TestCase + class TestHTTPResponse < Test::Unit::TestCase class FakeLogger attr_reader :messages From 355221c561724eed5c21fe093b5c732884bb01a8 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 16 Feb 2021 21:54:01 +0900 Subject: [PATCH 06/36] Added module for helper methods --- test/webrick/utils.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/webrick/utils.rb b/test/webrick/utils.rb index 56d3a30e..d1a6ad52 100644 --- a/test/webrick/utils.rb +++ b/test/webrick/utils.rb @@ -39,6 +39,12 @@ class WEBrick::HTTPServlet::CGIHandler include Test::Unit::Assertions extend Test::Unit::Assertions + include Test::Unit::Util::Output + extend Test::Unit::Util::Output + + include Test::Unit::CoreAssertions + extend Test::Unit::CoreAssertions + module_function DefaultLogTester = lambda {|log, access_log| assert_equal([], log) } From f87aec09fce09c142d2aa5108987338114327e4c Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Tue, 2 Mar 2021 07:30:54 -0800 Subject: [PATCH 07/36] Revert "Allow empty POST and PUT requests without content length" This reverts commit 069e9b1908aad3a30a0dcf67b6d3bb13c3216d2c. Users reports this causes WEBrick to block forever if receiving a POST/PUT request with an empty body. --- lib/webrick/httprequest.rb | 2 +- test/webrick/test_httprequest.rb | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/webrick/httprequest.rb b/lib/webrick/httprequest.rb index fe95aad9..ff2c8a8a 100644 --- a/lib/webrick/httprequest.rb +++ b/lib/webrick/httprequest.rb @@ -522,7 +522,7 @@ def read_body(socket, block) if @remaining_size > 0 && @socket.eof? raise HTTPStatus::BadRequest, "invalid body size." end - elsif BODY_CONTAINABLE_METHODS.member?(@request_method) && !@socket.eof + elsif BODY_CONTAINABLE_METHODS.member?(@request_method) raise HTTPStatus::LengthRequired end return @body diff --git a/test/webrick/test_httprequest.rb b/test/webrick/test_httprequest.rb index 759ccbda..a594f14f 100644 --- a/test/webrick/test_httprequest.rb +++ b/test/webrick/test_httprequest.rb @@ -425,18 +425,6 @@ def test_continue_not_sent assert_equal l, msg.size end - def test_empty_post - msg = <<-_end_of_message_ - POST /path?foo=x;foo=y;foo=z;bar=1 HTTP/1.1 - Host: test.ruby-lang.org:8080 - Content-Type: application/x-www-form-urlencoded - - _end_of_message_ - req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) - req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) - req.body - end - def test_bad_messages param = "foo=1;foo=2;foo=3;bar=x" msg = <<-_end_of_message_ From 1e4ff940bea29210562b3a48b0352446f104e9ad Mon Sep 17 00:00:00 2001 From: Mathieu Jobin Date: Sat, 20 Mar 2021 13:07:44 +0900 Subject: [PATCH 08/36] more rubies --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1dee1d7..1c74a5f6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: name: build (${{ matrix.ruby }} / ${{ matrix.os }}) strategy: matrix: - ruby: [ 2.7, 2.6, 2.5, head ] + ruby: [ 3.0, 2.7, 2.6, 2.5, 2.4, 2.3, head ] os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: From 37e19c7aa9024f3c9fd07d0b7d5c7f954ddb5596 Mon Sep 17 00:00:00 2001 From: Mathieu Jobin Date: Sat, 20 Mar 2021 13:16:17 +0900 Subject: [PATCH 09/36] ruby 2.4 should be mininum now there is an argument error with 2.3 ........E =============================================================================== Error: test_slow_connect(TestWEBrickSSLServer): ArgumentError: wrong number of arguments (given 3, expected 1..2) /opt/hostedtoolcache/Ruby/2.3.8/x64/lib/ruby/2.3.0/timeout.rb:73:in `timeout' /home/runner/work/webrick/webrick/test/lib/envutil.rb:72:in `timeout' /home/runner/work/webrick/webrick/test/webrick/test_ssl_server.rb:58:in `test_slow_connect' 55: :SSLEnable => true, 56: :SSLCertName => "/C=JP/O=www.ruby-lang.org/CN=Ruby", 57: } => 58: EnvUtil.timeout(10) do 59: TestWEBrick.start_server(Echo, config) do |server, addr, port, log| 60: outer = TCPSocket.new(addr, port) 61: inner = TCPSocket.new(addr, port) =============================================================================== --- webrick.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrick.gemspec b/webrick.gemspec index f3f8370e..e97a9635 100644 --- a/webrick.gemspec +++ b/webrick.gemspec @@ -57,7 +57,7 @@ Gem::Specification.new do |s| "lib/webrick/version.rb", "webrick.gemspec", ] - s.required_ruby_version = ">= 2.3.0" + s.required_ruby_version = ">= 2.4.0" s.authors = ["TAKAHASHI Masayoshi", "GOTOU YUUZOU", "Eric Wong"] s.email = [nil, nil, 'normal@ruby-lang.org'] From cf826ae39ece35a520d01b9373e3b4d656aecdff Mon Sep 17 00:00:00 2001 From: Mathieu Jobin Date: Sat, 20 Mar 2021 13:16:49 +0900 Subject: [PATCH 10/36] 3.0 needs quoting and removing 2.3 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1c74a5f6..2abb1a20 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: name: build (${{ matrix.ruby }} / ${{ matrix.os }}) strategy: matrix: - ruby: [ 3.0, 2.7, 2.6, 2.5, 2.4, 2.3, head ] + ruby: [ "3.0", 2.7, 2.6, 2.5, 2.4, head ] os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: From 6fcdac375bbf33a9781718903fe1f3cee54afb89 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 14 Apr 2021 12:29:59 +0900 Subject: [PATCH 11/36] Use stable version of actions/checkout --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2abb1a20..adf47df1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@master + - uses: actions/checkout@v2 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From 3515081a51b91b730267ba2b224039ecfbf8bd7b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 14 Apr 2021 12:30:17 +0900 Subject: [PATCH 12/36] Don't need to install bundler manually --- .github/workflows/test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index adf47df1..1d93df90 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,8 +17,6 @@ jobs: with: ruby-version: ${{ matrix.ruby }} - name: Install dependencies - run: | - gem install bundler --no-document - bundle install + run: bundle install - name: Run test run: rake test From 88006dc379b221973fe0555ba14d837142fa3dd7 Mon Sep 17 00:00:00 2001 From: Kentaro Goto Date: Fri, 20 Aug 2021 20:22:31 +0900 Subject: [PATCH 13/36] Adds ico, fonts, videos, AVIF and WebP MIME type --- lib/webrick/httputils.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/webrick/httputils.rb b/lib/webrick/httputils.rb index 456384e7..4893ecb9 100644 --- a/lib/webrick/httputils.rb +++ b/lib/webrick/httputils.rb @@ -48,6 +48,7 @@ def normalize_path(path) "ai" => "application/postscript", "asc" => "text/plain", "avi" => "video/x-msvideo", + "avif" => "image/avif", "bin" => "application/octet-stream", "bmp" => "image/bmp", "class" => "application/octet-stream", @@ -65,6 +66,7 @@ def normalize_path(path) "gif" => "image/gif", "htm" => "text/html", "html" => "text/html", + "ico" => "image/x-icon", "jpe" => "image/jpeg", "jpeg" => "image/jpeg", "jpg" => "image/jpeg", @@ -74,9 +76,11 @@ def normalize_path(path) "lzh" => "application/octet-stream", "mjs" => "application/javascript", "mov" => "video/quicktime", + "mp4" => "video/mp4", "mpe" => "video/mpeg", "mpeg" => "video/mpeg", "mpg" => "video/mpeg", + "otf" => "font/otf", "pbm" => "image/x-portable-bitmap", "pdf" => "application/pdf", "pgm" => "image/x-portable-graymap", @@ -95,8 +99,14 @@ def normalize_path(path) "svg" => "image/svg+xml", "tif" => "image/tiff", "tiff" => "image/tiff", + "ttc" => "font/collection", + "ttf" => "font/ttf", "txt" => "text/plain", "wasm" => "application/wasm", + "webm" => "video/webm", + "webp" => "image/webp", + "woff" => "font/woff", + "woff2" => "font/woff2", "xbm" => "image/x-xbitmap", "xhtml" => "text/html", "xls" => "application/vnd.ms-excel", From bd04d3abab43e0f8ae05a47ae6e66188c9f2d090 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Mon, 23 Aug 2021 09:32:40 +0200 Subject: [PATCH 14/36] add mime type for .webmanifest extension https://developer.mozilla.org/en-US/docs/Web/Manifest#deploying_a_manifest_with_the_link_tag https://w3c.github.io/manifest/#media-type-registration --- lib/webrick/httputils.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/webrick/httputils.rb b/lib/webrick/httputils.rb index 4893ecb9..05597f9e 100644 --- a/lib/webrick/httputils.rb +++ b/lib/webrick/httputils.rb @@ -104,6 +104,7 @@ def normalize_path(path) "txt" => "text/plain", "wasm" => "application/wasm", "webm" => "video/webm", + "webmanifest" => "application/manifest+json", "webp" => "image/webp", "woff" => "font/woff", "woff2" => "font/woff2", From 2cd72103e38497ae24126dae8b8eeaa031e8ba75 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 11 Sep 2021 10:14:37 +0900 Subject: [PATCH 15/36] Bump up the latest version of CoreAssertions --- Rakefile | 2 +- test/lib/core_assertions.rb | 67 ++++++++++++++++++++----------------- test/lib/envutil.rb | 6 ++-- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/Rakefile b/Rakefile index d81dd1f4..5a7afabd 100644 --- a/Rakefile +++ b/Rakefile @@ -9,7 +9,7 @@ end task :sync_tool do require 'fileutils' - FileUtils.cp "../ruby/tool/lib/test/unit/core_assertions.rb", "./test/lib" + FileUtils.cp "../ruby/tool/lib/core_assertions.rb", "./test/lib" FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib" FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib" end diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb index 118c0d11..44715255 100644 --- a/test/lib/core_assertions.rb +++ b/test/lib/core_assertions.rb @@ -24,23 +24,8 @@ def message msg = nil, ending = nil, &default end module CoreAssertions - if defined?(MiniTest) - require_relative '../../envutil' - # for ruby core testing - include MiniTest::Assertions - - # Compatibility hack for assert_raise - Test::Unit::AssertionFailedError = MiniTest::Assertion - else - module MiniTest - class Assertion < Exception; end - class Skip < Assertion; end - end - - require 'pp' - require_relative 'envutil' - include Test::Unit::Assertions - end + require_relative 'envutil' + require 'pp' def mu_pp(obj) #:nodoc: obj.pretty_inspect.chomp @@ -117,8 +102,8 @@ def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: fal # TODO: consider choosing some appropriate limit for MJIT and stop skipping this once it does not randomly fail pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? - require_relative '../../memory_status' - raise MiniTest::Skip, "unsupported platform" unless defined?(Memory::Status) + require_relative 'memory_status' + raise Test::Unit::PendedError, "unsupported platform" unless defined?(Memory::Status) token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m" token_dump = token.dump @@ -126,7 +111,7 @@ def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: fal envs = args.shift if Array === args and Hash === args.first args = [ "--disable=gems", - "-r", File.expand_path("../../../memory_status", __FILE__), + "-r", File.expand_path("../memory_status", __FILE__), *args, "-v", "-", ] @@ -183,11 +168,11 @@ def assert_nothing_raised(*args) end begin line = __LINE__; yield - rescue MiniTest::Skip + rescue Test::Unit::PendedError raise rescue Exception => e bt = e.backtrace - as = e.instance_of?(MiniTest::Assertion) + as = e.instance_of?(Test::Unit::AssertionFailedError) if as ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o bt.reject! {|ln| ans =~ ln} @@ -199,7 +184,7 @@ def assert_nothing_raised(*args) "Backtrace:\n" + e.backtrace.map{|frame| " #{frame}"}.join("\n") } - raise MiniTest::Assertion, msg.call, bt + raise Test::Unit::AssertionFailedError, msg.call, bt else raise end @@ -260,9 +245,10 @@ def assert_ruby_status(args, test_stdin="", message=nil, **opt) ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") def separated_runner(out = nil) + include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) }) out = out ? IO.new(out, 'w') : STDOUT at_exit { - out.puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}" + out.puts [Marshal.dump($!)].pack('m'), "assertions=#{self._assertions}" } Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) if defined?(Test::Unit::Runner) end @@ -276,14 +262,14 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o capture_stdout = true unless /mswin|mingw/ =~ RUBY_PLATFORM capture_stdout = false - opt[:out] = MiniTest::Unit.output if defined?(MiniTest::Unit) + opt[:out] = Test::Unit::Runner.output if defined?(Test::Unit::Runner) res_p, res_c = IO.pipe - opt[res_c.fileno] = res_c.fileno + opt[:ios] = [res_c] end src = < e - return e if exp.include? MiniTest::Skip + rescue Test::Unit::PendedError => e + return e if exp.include? Test::Unit::PendedError raise e rescue Exception => e expected = exp.any? { |ex| @@ -477,7 +463,7 @@ def assert_raise_with_message(exception, expected, msg = nil, &block) ex end - MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc: + MINI_DIR = File.join(File.dirname(File.expand_path(__FILE__)), "minitest") #:nodoc: # :call-seq: # assert(test, [failure_message]) @@ -623,6 +609,7 @@ def assert_deprecated_warn(mesg = /deprecated/) end class << (AssertFile = Struct.new(:failure_message).new) + include Assertions include CoreAssertions def assert_file_predicate(predicate, *args) if /\Anot_/ =~ predicate @@ -713,10 +700,20 @@ def assert_join_threads(threads, message = nil) if message msg = "#{message}\n#{msg}" end - raise MiniTest::Assertion, msg + raise Test::Unit::AssertionFailedError, msg end end + 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_all_assertions(msg = nil) all = AllFailures.new yield all @@ -725,6 +722,14 @@ def assert_all_assertions(msg = nil) end alias all_assertions assert_all_assertions + def assert_all_assertions_foreach(msg = nil, *keys, &block) + all = AllFailures.new + all.foreach(*keys, &block) + ensure + assert(all.pass?, message(msg) {all.message.chomp(".")}) + end + alias all_assertions_foreach assert_all_assertions_foreach + def message(msg = nil, *args, &default) # :nodoc: if Proc === msg super(nil, *args) do diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb index 937e1128..0391b90c 100644 --- a/test/lib/envutil.rb +++ b/test/lib/envutil.rb @@ -125,7 +125,7 @@ def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1) def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error, - stdout_filter: nil, stderr_filter: nil, + stdout_filter: nil, stderr_filter: nil, ios: nil, signal: :TERM, rubybin: EnvUtil.rubybin, precommand: nil, **opt) @@ -141,6 +141,8 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = out_p.set_encoding(encoding) if out_p err_p.set_encoding(encoding) if err_p end + ios.each {|i, o = i|opt[i] = o} if ios + c = "C" child_env = {} LANG_ENVS.each {|lc| child_env[lc] = c} @@ -152,7 +154,7 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = end child_env['ASAN_OPTIONS'] = ENV['ASAN_OPTIONS'] if ENV['ASAN_OPTIONS'] args = [args] if args.kind_of?(String) - pid = spawn(child_env, *precommand, rubybin, *args, **opt) + pid = spawn(child_env, *precommand, rubybin, *args, opt) in_c.close out_c&.close out_c = nil From 5bd917ad7d3352185596ce2be95fa03f9e9c884f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 11 Sep 2021 10:19:29 +0900 Subject: [PATCH 16/36] Move specific depends of webrick repo to test helper --- test/lib/helper.rb | 5 +++++ test/webrick/utils.rb | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/lib/helper.rb b/test/lib/helper.rb index 909f8f98..8c8c7526 100644 --- a/test/lib/helper.rb +++ b/test/lib/helper.rb @@ -2,3 +2,8 @@ require_relative "core_assertions" Test::Unit::TestCase.include Test::Unit::CoreAssertions + +module TestWEBrick + include Test::Unit::Util::Output + extend Test::Unit::Util::Output +end diff --git a/test/webrick/utils.rb b/test/webrick/utils.rb index d1a6ad52..a8568d0a 100644 --- a/test/webrick/utils.rb +++ b/test/webrick/utils.rb @@ -38,10 +38,6 @@ class WEBrick::HTTPServlet::CGIHandler require "test/unit" unless defined?(Test::Unit) include Test::Unit::Assertions extend Test::Unit::Assertions - - include Test::Unit::Util::Output - extend Test::Unit::Util::Output - include Test::Unit::CoreAssertions extend Test::Unit::CoreAssertions From 5666f2d56afb8408e8307f7fff70edfcc464c6e8 Mon Sep 17 00:00:00 2001 From: Finn Gao <10386624+printfinn@users.noreply.github.com> Date: Tue, 26 Oct 2021 16:36:26 +0800 Subject: [PATCH 17/36] Typo --- lib/webrick/server.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/webrick/server.rb b/lib/webrick/server.rb index 478b1ac9..f085d5d2 100644 --- a/lib/webrick/server.rb +++ b/lib/webrick/server.rb @@ -102,7 +102,7 @@ def initialize(config={}, default=Config::General) @listeners = [] @shutdown_pipe = nil unless @config[:DoNotListen] - raise ArgumentError, "Port must an integer" unless @config[:Port].to_s == @config[:Port].to_i.to_s + raise ArgumentError, "Port must be an integer" unless @config[:Port].to_s == @config[:Port].to_i.to_s @config[:Port] = @config[:Port].to_i if @config[:Listen] From 7fd238fbc2003255e45629ae2c09a19b128c8c1d Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Tue, 26 Oct 2021 10:38:24 +0200 Subject: [PATCH 18/36] CI: use bundler-cache: true --- .github/workflows/test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1d93df90..f8844ac4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,6 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - - name: Install dependencies - run: bundle install + bundler-cache: true # 'bundle install' and cache - name: Run test - run: rake test + run: bundle exec rake test From 6f9021347bb1bf82c4791a72b191ac7763914e8a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 14 Dec 2021 20:29:32 -0800 Subject: [PATCH 19/36] s/RubyVM::JIT/RubyVM::MJIT/g --- test/lib/core_assertions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb index 44715255..bac3856a 100644 --- a/test/lib/core_assertions.rb +++ b/test/lib/core_assertions.rb @@ -100,7 +100,7 @@ def syntax_check(code, fname, line) def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) # TODO: consider choosing some appropriate limit for MJIT and stop skipping this once it does not randomly fail - pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? + pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? require_relative 'memory_status' raise Test::Unit::PendedError, "unsupported platform" unless defined?(Memory::Status) From b5c68768ed1be299349b740b7af0787e836781c0 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Mon, 3 Jan 2022 15:05:32 -0800 Subject: [PATCH 20/36] Fix invalid use of IP addresses in SNI Server Name Indication does not allow IP addresses (RFC 6066, section 3: `Literal IPv4 and IPv6 addresses are not permitted in "HostName".`). Recent versions of LibreSSL enforce this requirement, and the tests currently break when running on such versions. Use localhost instead of 127.0.0.1 to avoid the issue. This requires adding HTTPSNITest#connect, because s.hostname= is called before ssl_socket_connect. --- test/webrick/test_httpproxy.rb | 8 ++++---- test/webrick/test_https.rb | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/test/webrick/test_httpproxy.rb b/test/webrick/test_httpproxy.rb index 1c2f2fce..5a60abbf 100644 --- a/test/webrick/test_httpproxy.rb +++ b/test/webrick/test_httpproxy.rb @@ -282,7 +282,7 @@ def test_connect # 3. ------- GET or POST ----------> # key = TEST_KEY_RSA2048 - cert = make_certificate(key, "127.0.0.1") + cert = make_certificate(key, "localhost") s_config = { :SSLEnable =>true, :ServerName => "localhost", @@ -300,7 +300,7 @@ def test_connect res.body = "SSL #{req.request_method} #{req.path} #{req.body}" } TestWEBrick.start_httpproxy(config){|server, addr, port, log| - http = Net::HTTP.new("127.0.0.1", s_port, addr, port) + http = Net::HTTP.new("localhost", s_port, addr, port) http.use_ssl = true http.verify_callback = Proc.new do |preverify_ok, store_ctx| store_ctx.current_cert.to_der == cert.to_der @@ -398,7 +398,7 @@ def test_upstream_proxy # 3. ---------- GET or POST --------------> # key = TEST_KEY_RSA2048 - cert = make_certificate(key, "127.0.0.1") + cert = make_certificate(key, "localhost") s_config = { :SSLEnable =>true, :ServerName => "localhost", @@ -409,7 +409,7 @@ def test_upstream_proxy s_server.mount_proc("/"){|req2, res| res.body = "SSL #{req2.request_method} #{req2.path} #{req2.body}" } - http = Net::HTTP.new("127.0.0.1", s_port, addr, port, up_log.call + log.call + s_log.call) + http = Net::HTTP.new("localhost", s_port, addr, port, up_log.call + log.call + s_log.call) http.use_ssl = true http.verify_callback = Proc.new do |preverify_ok, store_ctx| store_ctx.current_cert.to_der == cert.to_der diff --git a/test/webrick/test_https.rb b/test/webrick/test_https.rb index ec0aac35..05f4058d 100644 --- a/test/webrick/test_https.rb +++ b/test/webrick/test_https.rb @@ -17,6 +17,11 @@ def empty_log.<<(str) class HTTPSNITest < ::Net::HTTP attr_accessor :sni_hostname + def connect + @address = 'localhost' if @address == '127.0.0.1' + super + end + def ssl_socket_connect(s, timeout) s.hostname = sni_hostname super From f81c2cc7c532a3d45d9a5f7032c508e18593424e Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Tue, 4 Jan 2022 09:20:25 -0800 Subject: [PATCH 21/36] Revert "Fix invalid use of IP addresses in SNI" This reverts commit b5c68768ed1be299349b740b7af0787e836781c0. The commit will not longer be needed after changes to net/http are merged to not set IP addresses as SNI server names. --- test/webrick/test_httpproxy.rb | 8 ++++---- test/webrick/test_https.rb | 5 ----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/test/webrick/test_httpproxy.rb b/test/webrick/test_httpproxy.rb index 5a60abbf..1c2f2fce 100644 --- a/test/webrick/test_httpproxy.rb +++ b/test/webrick/test_httpproxy.rb @@ -282,7 +282,7 @@ def test_connect # 3. ------- GET or POST ----------> # key = TEST_KEY_RSA2048 - cert = make_certificate(key, "localhost") + cert = make_certificate(key, "127.0.0.1") s_config = { :SSLEnable =>true, :ServerName => "localhost", @@ -300,7 +300,7 @@ def test_connect res.body = "SSL #{req.request_method} #{req.path} #{req.body}" } TestWEBrick.start_httpproxy(config){|server, addr, port, log| - http = Net::HTTP.new("localhost", s_port, addr, port) + http = Net::HTTP.new("127.0.0.1", s_port, addr, port) http.use_ssl = true http.verify_callback = Proc.new do |preverify_ok, store_ctx| store_ctx.current_cert.to_der == cert.to_der @@ -398,7 +398,7 @@ def test_upstream_proxy # 3. ---------- GET or POST --------------> # key = TEST_KEY_RSA2048 - cert = make_certificate(key, "localhost") + cert = make_certificate(key, "127.0.0.1") s_config = { :SSLEnable =>true, :ServerName => "localhost", @@ -409,7 +409,7 @@ def test_upstream_proxy s_server.mount_proc("/"){|req2, res| res.body = "SSL #{req2.request_method} #{req2.path} #{req2.body}" } - http = Net::HTTP.new("localhost", s_port, addr, port, up_log.call + log.call + s_log.call) + http = Net::HTTP.new("127.0.0.1", s_port, addr, port, up_log.call + log.call + s_log.call) http.use_ssl = true http.verify_callback = Proc.new do |preverify_ok, store_ctx| store_ctx.current_cert.to_der == cert.to_der diff --git a/test/webrick/test_https.rb b/test/webrick/test_https.rb index 05f4058d..ec0aac35 100644 --- a/test/webrick/test_https.rb +++ b/test/webrick/test_https.rb @@ -17,11 +17,6 @@ def empty_log.<<(str) class HTTPSNITest < ::Net::HTTP attr_accessor :sni_hostname - def connect - @address = 'localhost' if @address == '127.0.0.1' - super - end - def ssl_socket_connect(s, timeout) s.hostname = sni_hostname super From 77e9ea9aeed4525db34085f3cbc64147ab9cb532 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 25 Feb 2022 17:00:16 +0900 Subject: [PATCH 22/36] Revert "Update url of issue tracker." This reverts commit 082b6345c2fda320d9a3e6fa09fa7614dd3a5575. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c30fb1c6..0027072b 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To ## Contributing -Bug reports and Patch are welcome on https://bugs.ruby-lang.org/. +Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/webrick. ## License From 1bb4c9d4a8163ccb9b14241c42fe471953906398 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 25 Mar 2022 15:50:07 +0900 Subject: [PATCH 23/36] Added dependabot --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b18fd293 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' From 5793cd207814cb04779df56a091a00f7470532b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Mar 2022 06:50:27 +0000 Subject: [PATCH 24/36] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f8844ac4..0021fa89 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From f6301f2b208db70c53d1ccd59aba832b0d8f46e6 Mon Sep 17 00:00:00 2001 From: Benjamin Lee Date: Tue, 24 May 2022 18:57:15 -0700 Subject: [PATCH 25/36] remove unneeded bin/console and bin/setup files from gemspec --- webrick.gemspec | 2 -- 1 file changed, 2 deletions(-) diff --git a/webrick.gemspec b/webrick.gemspec index e97a9635..31423e9f 100644 --- a/webrick.gemspec +++ b/webrick.gemspec @@ -18,8 +18,6 @@ Gem::Specification.new do |s| "LICENSE.txt", "README.md", "Rakefile", - "bin/console", - "bin/setup", "lib/webrick.rb", "lib/webrick/accesslog.rb", "lib/webrick/cgi.rb", From 73c914e49ec1aba295c092cfdb4dcbc24f6caef3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 25 Nov 2022 14:22:37 +0900 Subject: [PATCH 26/36] Use class methods of `File` over `Kernel.open` and `IO.read` --- lib/webrick/httputils.rb | 2 +- test/webrick/test_filehandler.rb | 4 ++-- test/webrick/test_httprequest.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/webrick/httputils.rb b/lib/webrick/httputils.rb index 05597f9e..48aa1371 100644 --- a/lib/webrick/httputils.rb +++ b/lib/webrick/httputils.rb @@ -123,7 +123,7 @@ def normalize_path(path) def load_mime_types(file) # note: +file+ may be a "| command" for now; some people may # rely on this, but currently we do not use this method by default. - open(file){ |io| + File.open(file){ |io| hash = Hash.new io.each{ |line| next if /^#/ =~ line diff --git a/test/webrick/test_filehandler.rb b/test/webrick/test_filehandler.rb index 998e03f6..881fb54d 100644 --- a/test/webrick/test_filehandler.rb +++ b/test/webrick/test_filehandler.rb @@ -85,12 +85,12 @@ def test_make_partial_content "Content-Type: text/plain\r\n" \ "Content-Range: bytes 0-0/#{filesize}\r\n" \ "\r\n" \ - "#{IO.read(__FILE__, 1)}\r\n" \ + "#{File.read(__FILE__, 1)}\r\n" \ "--#{boundary}\r\n" \ "Content-Type: text/plain\r\n" \ "Content-Range: bytes #{off}-#{last}/#{filesize}\r\n" \ "\r\n" \ - "#{IO.read(__FILE__, 2, off)}\r\n" \ + "#{File.read(__FILE__, 2, off)}\r\n" \ "--#{boundary}--\r\n" assert_equal exp, body end diff --git a/test/webrick/test_httprequest.rb b/test/webrick/test_httprequest.rb index a594f14f..2ff08d63 100644 --- a/test/webrick/test_httprequest.rb +++ b/test/webrick/test_httprequest.rb @@ -245,7 +245,7 @@ def test_chunked _end_of_message_ msg.gsub!(/^ {6}/, "") - open(__FILE__){|io| + File.open(__FILE__){|io| while chunk = io.read(100) msg << chunk.size.to_s(16) << crlf msg << chunk << crlf From d42c291df4f2f5c31b9fdec1e503fac9e5f65b22 Mon Sep 17 00:00:00 2001 From: Ravi <33230211+bharjr01@users.noreply.github.com> Date: Thu, 26 Jan 2023 23:03:22 +0000 Subject: [PATCH 27/36] Accept put requests (#70) * Alias do_PUT method to handle PUT requests * Unit test to ensure PUT requests are handled --- lib/webrick/httpservlet/prochandler.rb | 1 + test/webrick/test_httpserver.rb | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/webrick/httpservlet/prochandler.rb b/lib/webrick/httpservlet/prochandler.rb index dca5d2d0..92e4f80d 100644 --- a/lib/webrick/httpservlet/prochandler.rb +++ b/lib/webrick/httpservlet/prochandler.rb @@ -40,6 +40,7 @@ def do_GET(request, response) end alias do_POST do_GET + alias do_PUT do_GET # :startdoc: end diff --git a/test/webrick/test_httpserver.rb b/test/webrick/test_httpserver.rb index 4133be85..56ac6774 100644 --- a/test/webrick/test_httpserver.rb +++ b/test/webrick/test_httpserver.rb @@ -540,4 +540,23 @@ def test_big_chunks end } end + + def test_accept_put_requests + TestWEBrick.start_httpserver {|server, addr, port, log| + server.mount_proc("/", lambda {|req, res| + res.status = 200 + assert_equal("abcde", req.body) + }) + Thread.pass while server.status != :Running + + Net::HTTP.start(addr, port) do |http| + req = Net::HTTP::Put.new("/") + req.body = "abcde" + http.request(req){|res| + assert_equal("200", res.code) + } + server.shutdown + end + } + end end From e4570039c135e13fadd93df4766b61407098577f Mon Sep 17 00:00:00 2001 From: John W Higgins Date: Thu, 26 Jan 2023 15:07:49 -0800 Subject: [PATCH 28/36] Move the host request parsing to a separate method. (#85) Allows for someone to override the parsing to accommodate "alternatives". One could write the following to allow underscores in host names. require 'webrick/httprequest' module WEBrick class HTTPRequest private def parse_host_request_line(host, scheme) uri = URI.parse("#{scheme}://#{host}") [uri.host, uri.port] end end end Also adding the "o" option to the regex so the regex is only built once. --- lib/webrick/httprequest.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/webrick/httprequest.rb b/lib/webrick/httprequest.rb index ff2c8a8a..680ac65a 100644 --- a/lib/webrick/httprequest.rb +++ b/lib/webrick/httprequest.rb @@ -491,8 +491,7 @@ def parse_uri(str, scheme="http") if @forwarded_host host, port = @forwarded_host, @forwarded_port elsif self["host"] - pattern = /\A(#{URI::REGEXP::PATTERN::HOST})(?::(\d+))?\z/n - host, port = *self['host'].scan(pattern)[0] + host, port = parse_host_request_line(self["host"]) elsif @addr.size > 0 host, port = @addr[2], @addr[1] else @@ -504,6 +503,11 @@ def parse_uri(str, scheme="http") return URI::parse(uri.to_s) end + def parse_host_request_line(host) + pattern = /\A(#{URI::REGEXP::PATTERN::HOST})(?::(\d+))?\z/no + host.scan(pattern)[0] + end + def read_body(socket, block) return unless socket if tc = self['transfer-encoding'] From 6cb9bf63a3900ded94aeeaf983df1ae89aef6939 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Thu, 26 Jan 2023 15:10:30 -0800 Subject: [PATCH 29/36] Only output prime information to $stderr if $VERBOSE (#88) This is currently done even in non-$VERBOSE mode. My guess as to the reason it is done is to give the user the impression that the server is doing something and not frozen, but I still think there should be a way to disable it. If the $VERBOSE flag isn't a good way to do that, let's offer some other way to do that. --- lib/webrick/ssl.rb | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/webrick/ssl.rb b/lib/webrick/ssl.rb index 0c81c9eb..6937f93b 100644 --- a/lib/webrick/ssl.rb +++ b/lib/webrick/ssl.rb @@ -95,18 +95,22 @@ module Utils # the issuer +cn+ and a +comment+ to be stored in the certificate. def create_self_signed_cert(bits, cn, comment) - rsa = OpenSSL::PKey::RSA.new(bits){|p, n| - case p - when 0; $stderr.putc "." # BN_generate_prime - when 1; $stderr.putc "+" # BN_generate_prime - when 2; $stderr.putc "*" # searching good prime, - # n = #of try, - # but also data from BN_generate_prime - when 3; $stderr.putc "\n" # found good prime, n==0 - p, n==1 - q, - # but also data from BN_generate_prime - else; $stderr.putc "*" # BN_generate_prime - end - } + rsa = if $VERBOSE + OpenSSL::PKey::RSA.new(bits){|p, n| + case p + when 0; $stderr.putc "." # BN_generate_prime + when 1; $stderr.putc "+" # BN_generate_prime + when 2; $stderr.putc "*" # searching good prime, + # n = #of try, + # but also data from BN_generate_prime + when 3; $stderr.putc "\n" # found good prime, n==0 - p, n==1 - q, + # but also data from BN_generate_prime + else; $stderr.putc "*" # BN_generate_prime + end + } + else + OpenSSL::PKey::RSA.new(bits) + end cert = OpenSSL::X509::Certificate.new cert.version = 2 cert.serial = 1 From 1c9d2f4fc65b27d3408d6325266450768f4ab980 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 26 Jan 2023 18:10:51 -0800 Subject: [PATCH 30/36] Better support for connection upgrade and bi-directional streaming. (#101) --- lib/webrick/httpresponse.rb | 36 +++++++++++++++++++++++++------ test/webrick/test_httpresponse.rb | 33 ++++++++++++++++++++++++++++ test/webrick/test_httpserver.rb | 3 +-- 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/lib/webrick/httpresponse.rb b/lib/webrick/httpresponse.rb index c7238dc0..9b8837c5 100644 --- a/lib/webrick/httpresponse.rb +++ b/lib/webrick/httpresponse.rb @@ -105,6 +105,11 @@ class InvalidHeader < StandardError attr_reader :sent_size + ## + # Set the response body proc as an streaming/upgrade response. + + attr_accessor :upgrade + ## # Creates a new HTTP response object. WEBrick::Config::HTTP is the # default configuration. @@ -217,6 +222,16 @@ def keep_alive? @keep_alive end + ## + # Sets the response to be a streaming/upgrade response. + # This will disable keep-alive and chunked transfer encoding. + + def upgrade!(protocol) + @upgrade = protocol + @keep_alive = false + @chunked = false + end + ## # Sends the response on +socket+ @@ -242,6 +257,14 @@ def setup_header() # :nodoc: @header['server'] ||= @config[:ServerSoftware] @header['date'] ||= Time.now.httpdate + if @upgrade + @header['connection'] = 'upgrade' + @header['upgrade'] = @upgrade + @keep_alive = false + + return + end + # HTTP/0.9 features if @request_http_version < "1.0" @http_version = HTTPVersion.new("0.9") @@ -268,11 +291,10 @@ def setup_header() # :nodoc: elsif %r{^multipart/byteranges} =~ @header['content-type'] @header.delete('content-length') elsif @header['content-length'].nil? - if @body.respond_to? :readpartial - elsif @body.respond_to? :call - make_body_tempfile + if @body.respond_to?(:bytesize) + @header['content-length'] = @body.bytesize.to_s else - @header['content-length'] = (@body ? @body.bytesize : 0).to_s + @header['connection'] = 'close' end end @@ -517,14 +539,16 @@ def send_body_proc(socket) @body.call(ChunkedWrapper.new(socket, self)) socket.write("0#{CRLF}#{CRLF}") else - size = @header['content-length'].to_i if @bodytempfile @bodytempfile.rewind IO.copy_stream(@bodytempfile, socket) else @body.call(socket) end - @sent_size = size + + if content_length = @header['content-length'] + @sent_size = content_length.to_i + end end end diff --git a/test/webrick/test_httpresponse.rb b/test/webrick/test_httpresponse.rb index 07648600..1a2bc3e0 100644 --- a/test/webrick/test_httpresponse.rb +++ b/test/webrick/test_httpresponse.rb @@ -261,6 +261,39 @@ def test_send_body_proc_chunked assert_equal 0, logger.messages.length end + def test_send_body_proc_upgrade + @res.body = Proc.new { |out| out.write('hello'); out.close } + @res.upgrade!("text") + + IO.pipe do |r, w| + @res.send_response(w) + w.close + assert_match /Connection: upgrade\r\nUpgrade: text\r\n\r\nhello/, r.read + end + assert_empty logger.messages + end + + def test_send_body_proc_stream + @res.body = Proc.new do |socket| + chunk = socket.read + socket.write(chunk) + socket.close + end + + UNIXSocket.pair do |s1, s2| + thread = Thread.new do + @res.send_response(s1) + end + + s2.write("hello") + s2.close_write + chunk = s2.read + assert_match /Connection: close\r\n\r\nhello/, chunk + s2.close + end + assert_empty logger.messages + end + def test_set_error status = 400 message = 'missing attribute' diff --git a/test/webrick/test_httpserver.rb b/test/webrick/test_httpserver.rb index 56ac6774..93838649 100644 --- a/test/webrick/test_httpserver.rb +++ b/test/webrick/test_httpserver.rb @@ -377,8 +377,7 @@ def test_response_io_without_chunked_set :ServerName => "localhost" } log_tester = lambda {|log, access_log| - assert_equal(1, log.length) - assert_match(/WARN Could not determine content-length of response body./, log[0]) + assert_empty log } TestWEBrick.start_httpserver(config, log_tester){|server, addr, port, log| server.mount_proc("/", lambda { |req, res| From 841b7dadd5d2dc7cd5f5cceca2258ad53023f8dd Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 27 Jan 2023 11:21:18 +0900 Subject: [PATCH 31/36] Bump up 1.8.0 --- lib/webrick/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/webrick/version.rb b/lib/webrick/version.rb index a4cfd504..66c20a39 100644 --- a/lib/webrick/version.rb +++ b/lib/webrick/version.rb @@ -14,5 +14,5 @@ module WEBrick ## # The WEBrick version - VERSION = "1.7.0" + VERSION = "1.8.0" end From 950173565113d03d38f1d92996d061950a402241 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 27 Jan 2023 23:56:57 +1300 Subject: [PATCH 32/36] Fix warning about missing content type. --- test/webrick/test_httpserver.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/webrick/test_httpserver.rb b/test/webrick/test_httpserver.rb index 93838649..0c5a6147 100644 --- a/test/webrick/test_httpserver.rb +++ b/test/webrick/test_httpserver.rb @@ -541,21 +541,25 @@ def test_big_chunks end def test_accept_put_requests - TestWEBrick.start_httpserver {|server, addr, port, log| + TestWEBrick.start_httpserver do |server, addr, port, log| server.mount_proc("/", lambda {|req, res| res.status = 200 assert_equal("abcde", req.body) }) + Thread.pass while server.status != :Running Net::HTTP.start(addr, port) do |http| req = Net::HTTP::Put.new("/") req.body = "abcde" - http.request(req){|res| + req['content-type'] = "text/plain" + + http.request(req) do |res| assert_equal("200", res.code) - } + end + server.shutdown end - } + end end end From af5d2f57baa4ea6ee070343e0d28616f9d44e746 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 27 Jan 2023 02:57:26 -0800 Subject: [PATCH 33/36] Body should be non-frozen by default. (#103) --- lib/webrick/httpresponse.rb | 4 ++-- test/webrick/test_httpresponse.rb | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/webrick/httpresponse.rb b/lib/webrick/httpresponse.rb index 9b8837c5..dde0261d 100644 --- a/lib/webrick/httpresponse.rb +++ b/lib/webrick/httpresponse.rb @@ -122,7 +122,7 @@ def initialize(config) @status = HTTPStatus::RC_OK @reason_phrase = nil @http_version = HTTPVersion::convert(@config[:HTTPVersion]) - @body = '' + @body = +"" @keep_alive = true @cookies = [] @request_method = nil @@ -441,7 +441,7 @@ def check_header(header_value) # :stopdoc: def error_body(backtrace, ex, host, port) - @body = +'' + @body = +"" @body << <<-_end_of_html_ diff --git a/test/webrick/test_httpresponse.rb b/test/webrick/test_httpresponse.rb index 1a2bc3e0..9909ded3 100644 --- a/test/webrick/test_httpresponse.rb +++ b/test/webrick/test_httpresponse.rb @@ -28,6 +28,10 @@ def setup @res.keep_alive = true end + def test_response_body_not_frozen + refute @res.body.frozen? + end + def test_prevent_response_splitting_headers_crlf res['X-header'] = "malicious\r\nCookie: cracked_indicator_for_test" io = StringIO.new From c566eb7d0b9145eb8c6588aa0de4da68cace5451 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 27 Jan 2023 02:57:44 -0800 Subject: [PATCH 34/36] Join test thread. (#104) --- test/webrick/test_httpresponse.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/webrick/test_httpresponse.rb b/test/webrick/test_httpresponse.rb index 9909ded3..48f8ace9 100644 --- a/test/webrick/test_httpresponse.rb +++ b/test/webrick/test_httpresponse.rb @@ -294,6 +294,8 @@ def test_send_body_proc_stream chunk = s2.read assert_match /Connection: close\r\n\r\nhello/, chunk s2.close + + thread.join end assert_empty logger.messages end From 8fd3ef1df9c44c6331692ba5381aecc9d5c813f4 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 27 Jan 2023 02:57:59 -0800 Subject: [PATCH 35/36] Fix several regexp warnings. (#105) --- test/webrick/test_httpresponse.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/webrick/test_httpresponse.rb b/test/webrick/test_httpresponse.rb index 48f8ace9..83734533 100644 --- a/test/webrick/test_httpresponse.rb +++ b/test/webrick/test_httpresponse.rb @@ -272,7 +272,7 @@ def test_send_body_proc_upgrade IO.pipe do |r, w| @res.send_response(w) w.close - assert_match /Connection: upgrade\r\nUpgrade: text\r\n\r\nhello/, r.read + assert_match(/Connection: upgrade\r\nUpgrade: text\r\n\r\nhello/, r.read) end assert_empty logger.messages end @@ -292,7 +292,7 @@ def test_send_body_proc_stream s2.write("hello") s2.close_write chunk = s2.read - assert_match /Connection: close\r\n\r\nhello/, chunk + assert_match(/Connection: close\r\n\r\nhello/, chunk) s2.close thread.join From 158a7ef85337e0e5cdce0c29cb5cb89c7beb9c53 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 27 Jan 2023 20:16:02 +0900 Subject: [PATCH 36/36] Bump up 1.8.1 --- lib/webrick/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/webrick/version.rb b/lib/webrick/version.rb index 66c20a39..ceeefc33 100644 --- a/lib/webrick/version.rb +++ b/lib/webrick/version.rb @@ -14,5 +14,5 @@ module WEBrick ## # The WEBrick version - VERSION = "1.8.0" + VERSION = "1.8.1" end