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'
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e1dee1d7..0021fa89 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -7,18 +7,15 @@ 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, head ]
os: [ ubuntu-latest, macos-latest ]
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- - name: Install dependencies
- run: |
- gem install bundler --no-document
- bundle install
+ bundler-cache: true # 'bundle install' and cache
- name: Run test
- run: rake test
+ run: bundle exec rake test
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/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
diff --git a/Rakefile b/Rakefile
index 43a5a3f5..5a7afabd 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/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/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..680ac65a 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 = {}
@@ -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']
@@ -522,7 +526,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/lib/webrick/httpresponse.rb b/lib/webrick/httpresponse.rb
index ba4494ab..dde0261d 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
#
@@ -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.
@@ -117,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
@@ -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
@@ -332,7 +354,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 +441,7 @@ def check_header(header_value)
# :stopdoc:
def error_body(backtrace, ex, host, port)
- @body = ''
+ @body = +""
@body << <<-_end_of_html_
@@ -453,11 +475,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
@@ -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
@@ -539,7 +563,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..92e4f80d 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
#
@@ -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/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..48aa1371 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
#
@@ -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,15 @@ 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",
+ "webmanifest" => "application/manifest+json",
+ "webp" => "image/webp",
+ "woff" => "font/woff",
+ "woff2" => "font/woff2",
"xbm" => "image/x-xbitmap",
"xhtml" => "text/html",
"xls" => "application/vnd.ms-excel",
@@ -112,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
@@ -231,7 +242,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 +506,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..f085d5d2 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
#
@@ -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]
diff --git a/lib/webrick/ssl.rb b/lib/webrick/ssl.rb
index e448095a..6937f93b 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
#
@@ -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
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..ceeefc33 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
#
@@ -14,5 +14,5 @@ module WEBrick
##
# The WEBrick version
- VERSION = "1.7.0"
+ VERSION = "1.8.1"
end
diff --git a/test/lib/test/unit/assertions.rb b/test/lib/core_assertions.rb
similarity index 61%
rename from test/lib/test/unit/assertions.rb
rename to test/lib/core_assertions.rb
index ee6a758f..bac3856a 100644
--- a/test/lib/test/unit/assertions.rb
+++ b/test/lib/core_assertions.rb
@@ -1,153 +1,146 @@
# frozen_string_literal: true
-require 'minitest/unit'
-require 'pp'
module Test
module Unit
module Assertions
- include MiniTest::Assertions
+ def _assertions= n # :nodoc:
+ @_assertions = n
+ end
- def mu_pp(obj) #:nodoc:
- obj.pretty_inspect.chomp
+ def _assertions # :nodoc:
+ @_assertions ||= 0
end
- MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc:
+ ##
+ # Returns a proc that will output +msg+ along with the default message.
- # :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
+ 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
- # :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
+ module CoreAssertions
+ require_relative 'envutil'
+ require 'pp'
+
+ def mu_pp(obj) #:nodoc:
+ obj.pretty_inspect.chomp
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
+ 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
- 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
+ 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
- assert expected, proc {
- exception_details(e, message(msg) {"#{mu_pp(exp)} exception expected, not"}.call)
+ 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"
}
-
- return e
+ code = code.force_encoding(Encoding::UTF_8)
+ catch {|tag| eval(code, binding, fname, line - 1)}
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
+ 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::MJIT) && RubyVM::MJIT.enabled?
- # :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
+ require_relative 'memory_status'
+ raise Test::Unit::PendedError, "unsupported platform" unless defined?(Memory::Status)
- 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
+ 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
- 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($~)
+ 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
- ex
+ rescue LoadError
+ pend
end
# :call-seq:
@@ -175,330 +168,32 @@ 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}
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
+ msg = message(msg) {
+ "Exception raised:\n<#{mu_pp(e)}>\n" +
+ "Backtrace:\n" +
+ e.backtrace.map{|frame| " #{frame}"}.join("\n")
+ }
+ raise Test::Unit::AssertionFailedError, 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)
+ 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
@@ -508,13 +203,19 @@ def prepare_syntax_check(code, fname = caller_locations(2, 1)[0], mesg = fname.t
else
line = 1
end
- yield(code, fname, line, mesg)
+ 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)
- prepare_syntax_check(code, *args) do |src, fname, line, mesg|
+ 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)
@@ -522,17 +223,6 @@ def assert_valid_syntax(code, *args)
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
@@ -544,78 +234,6 @@ def assert_normal_exit(testsrc, message = '', child_env: nil, **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
+ res = nil
end
- if res
+ if res and !(SystemExit === res)
if bt = res.backtrace
bt.each do |l|
l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"}
@@ -663,7 +304,7 @@ class Test::Unit::Runner
else
res.set_backtrace(caller)
end
- raise res unless SystemExit === res
+ raise res
end
# really is it succeed?
@@ -671,93 +312,227 @@ class Test::Unit::Runner
# 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
+ 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
- def assert_warning(pat, msg = nil)
- stderr = EnvUtil.verbose_warning {
- EnvUtil.with_default_internal(pat.encoding) {
- yield
+ begin
+ yield
+ rescue Test::Unit::PendedError => e
+ return e if exp.include? Test::Unit::PendedError
+ 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
}
- }
- msg = message(msg) {diff pat, stderr}
- assert(pat === stderr, msg)
- end
- def assert_warn(*args)
- assert_warning(*args) {$VERBOSE = false; yield}
- end
+ assert expected, proc {
+ flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"})
+ }
- 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)
+ return e
+ ensure
+ unless e
+ exp = exp.first if exp.size == 1
- 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}"})
+ flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"})
+ end
end
- rescue LoadError
- skip
end
- def assert_cpu_usage_low(msg = nil, pct: 0.01)
- require 'benchmark'
+ # :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
- 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"
+ 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"}
- # 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
+ 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
- assert_operator tms.total, :<=, max, msg
+ MINI_DIR = File.join(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
- def assert_is_minus_zero(f)
- assert(1.0/f == -Float::INFINITY, "#{f} is not -0.0")
+ # :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
- def assert_file
- AssertFile
+ # :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 :*.
@@ -781,13 +556,13 @@ def assert_pattern_list(pattern_list, actual, message=nil)
msg = message(msg) {
expect_msg = "Expected #{mu_pp pattern}\n"
if /\n[^\n]/ =~ rest
- actual_mesg = "to match\n"
+ 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}"
+ actual_mesg = "to match " + mu_pp(rest)
end
actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters"
expect_msg + actual_mesg
@@ -803,40 +578,39 @@ def assert_pattern_list(pattern_list, actual, message=nil)
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
+ 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
- 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
+
+ def assert_deprecated_warn(mesg = /deprecated/)
+ assert_warn(mesg) do
+ Warning[:deprecated] = true
+ yield
end
- values
end
class << (AssertFile = Struct.new(:failure_message).new)
include Assertions
+ include CoreAssertions
def assert_file_predicate(predicate, *args)
if /\Anot_/ =~ predicate
predicate = $'
@@ -888,7 +662,8 @@ def message
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(/^/, ' | ')}"
+ v = v.message
+ "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.b.gsub(/^/, ' | ').force_encoding(v.encoding)}"
}.join("\n")
end
@@ -897,6 +672,48 @@ def pass?
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 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
@@ -913,11 +730,6 @@ def assert_all_assertions_foreach(msg = nil, *keys, &block)
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
@@ -935,6 +747,22 @@ def message(msg = nil, *args, &default) # :nodoc:
super
end
end
+
+ def diff(exp, act)
+ require 'pp'
+ q = PP.new(+"")
+ q.guard_inspect_key do
+ q.group(2, "expected: ") do
+ q.pp exp
+ end
+ q.text q.newline
+ q.group(2, "actual: ") do
+ q.pp act
+ end
+ q.flush
+ end
+ q.output
+ end
end
end
end
diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb
index d76ed660..0391b90c 100644
--- a/test/lib/envutil.rb
+++ b/test/lib/envutil.rb
@@ -42,12 +42,23 @@ def rubybin
DEFAULT_SIGNALS = Signal.list
DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM
+ RUBYLIB = ENV["RUBYLIB"]
+
class << self
- attr_accessor :subprocess_timeout_scale
+ attr_accessor :timeout_scale
+ attr_reader :original_internal_encoding, :original_external_encoding,
+ :original_verbose, :original_warning
+
+ def capture_global_values
+ @original_internal_encoding = Encoding.default_internal
+ @original_external_encoding = Encoding.default_external
+ @original_verbose = $VERBOSE
+ @original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil
+ end
end
def apply_timeout_scale(t)
- if scale = EnvUtil.subprocess_timeout_scale
+ if scale = EnvUtil.timeout_scale
t * scale
else
t
@@ -62,14 +73,63 @@ def timeout(sec, klass = nil, message = nil, &blk)
end
module_function :timeout
+ def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1)
+ reprieve = apply_timeout_scale(reprieve) if reprieve
+
+ 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
+ when 0, true
+ pgroup = -pid
+ when nil, false
+ pgroup = pid
+ end
+
+ lldb = true if /darwin/ =~ RUBY_PLATFORM
+
+ while signal = signals.shift
+
+ if lldb and [:ABRT, :KILL].include?(signal)
+ lldb = false
+ # sudo -n: --non-interactive
+ # lldb -p: attach
+ # -o: run command
+ system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit])
+ true
+ end
+
+ 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
+ else
+ break
+ end
+ end
+ end
+ $?
+ end
+ module_function :terminate
+
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,
+ rubybin: EnvUtil.rubybin, precommand: nil,
**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
@@ -81,17 +141,25 @@ 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}
if Array === args and Hash === args.first
child_env.update(args.shift)
end
+ if RUBYLIB and lib = child_env["RUBYLIB"]
+ child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR)
+ end
+ child_env['ASAN_OPTIONS'] = ENV['ASAN_OPTIONS'] if ENV['ASAN_OPTIONS']
args = [args] if args.kind_of?(String)
- pid = spawn(child_env, rubybin, *args, **opt)
+ pid = spawn(child_env, *precommand, rubybin, *args, opt)
in_c.close
- out_c.close if capture_stdout
- err_c.close if capture_stderr && capture_stderr != :merge_to_stdout
+ out_c&.close
+ out_c = nil
+ err_c&.close
+ err_c = nil
if block_given?
return yield in_p, out_p, err_p, pid
else
@@ -102,35 +170,8 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr =
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 = $?
+ status = terminate(pid, signal, opt[:pgroup], reprieve)
+ terminated = Time.now
end
stdout = th_stdout.value if capture_stdout
stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout
@@ -141,8 +182,8 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr =
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")].()
+ msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)"
+ msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n"))
raise timeout_error, msg, bt.map(&:to_s)
end
return stdout, stderr, status
@@ -160,36 +201,35 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr =
end
module_function :invoke_ruby
- alias rubyexec invoke_ruby
- class << self
- alias rubyexec invoke_ruby
- end
-
def verbose_warning
class << (stderr = "".dup)
- alias write <<
+ alias write concat
+ def flush; end
end
- stderr, $stderr, verbose, $VERBOSE = $stderr, stderr, $VERBOSE, true
+ stderr, $stderr = $stderr, stderr
+ $VERBOSE = true
yield stderr
return $stderr
ensure
- stderr, $stderr, $VERBOSE = $stderr, stderr, verbose
+ stderr, $stderr = $stderr, stderr
+ $VERBOSE = EnvUtil.original_verbose
+ EnvUtil.original_warning&.each {|i, v| Warning[i] = v}
end
module_function :verbose_warning
def default_warning
- verbose, $VERBOSE = $VERBOSE, false
+ $VERBOSE = false
yield
ensure
- $VERBOSE = verbose
+ $VERBOSE = EnvUtil.original_verbose
end
module_function :default_warning
def suppress_warning
- verbose, $VERBOSE = $VERBOSE, nil
+ $VERBOSE = nil
yield
ensure
- $VERBOSE = verbose
+ $VERBOSE = EnvUtil.original_verbose
end
module_function :suppress_warning
@@ -202,32 +242,28 @@ def under_gc_stress(stress = true)
module_function :under_gc_stress
def with_default_external(enc)
- verbose, $VERBOSE = $VERBOSE, nil
- origenc, Encoding.default_external = Encoding.default_external, enc
- $VERBOSE = verbose
+ suppress_warning { Encoding.default_external = enc }
yield
ensure
- verbose, $VERBOSE = $VERBOSE, nil
- Encoding.default_external = origenc
- $VERBOSE = verbose
+ suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding }
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
+ suppress_warning { Encoding.default_internal = enc }
yield
ensure
- verbose, $VERBOSE = $VERBOSE, nil
- Encoding.default_internal = origenc
- $VERBOSE = verbose
+ suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding }
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}
+ singleton_class.class_eval {
+ define_method(:to_s) {name}
+ alias inspect to_s
+ alias name to_s
+ }
class_eval(&block) if block
end
end
@@ -235,7 +271,11 @@ def labeled_module(name, &block)
def labeled_class(name, superclass = Object, &block)
Class.new(superclass) do
- singleton_class.class_eval {define_method(:to_s) {name}; alias inspect to_s}
+ singleton_class.class_eval {
+ define_method(:to_s) {name}
+ alias inspect to_s
+ alias name to_s
+ }
class_eval(&block) if block
end
end
@@ -272,6 +312,37 @@ def self.diagnostic_reports(signame, pid, now)
end
end
+ def self.failure_description(status, now, message = "", out = "")
+ pid = status.pid
+ if signo = status.termsig
+ signame = Signal.signame(signo)
+ sigdesc = "signal #{signo}"
+ end
+ log = 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!(/(? #{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