1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

support concurrent btest execution

* `-j` option for concurrent test with threads
  * `-jN` uses N threads
  * `-j` uses nproc/2 threads
* Introduce `BT` struct to manage configurations
* Introduce `Assertion` to manage all assertions
* Remove all toplevel instance variables
* Show elapsed seconds at last

```
$ time make btest
...
real    0m37.319s
user    0m26.221s
sys     0m16.534s

$ time make btest TESTOPTS=-j
...
real    0m11.812s
user    0m36.667s
sys     0m21.872s
```
This commit is contained in:
Koichi Sasada 2022-02-05 03:10:15 +09:00
parent 2a76440fac
commit 603ab70961
Notes: git 2022-02-06 03:06:23 +09:00
5 changed files with 488 additions and 333 deletions

View file

@ -8,6 +8,8 @@
# Never use Ruby extensions in this file. # Never use Ruby extensions in this file.
# Maintain Ruby 1.8 compatibility for now # Maintain Ruby 1.8 compatibility for now
$start_time = Time.now
begin begin
require 'fileutils' require 'fileutils'
require 'tmpdir' require 'tmpdir'
@ -58,24 +60,44 @@ if !Dir.respond_to?(:mktmpdir)
end end
end end
# Configuration
BT = Struct.new(:ruby,
:verbose,
:color,
:tty,
:quiet,
:wn,
:progress,
:progress_bs,
:passed,
:failed,
:reset,
:columns,
:width,
).new
BT_STATE = Struct.new(:count, :error).new
def main def main
@ruby = File.expand_path('miniruby') BT.ruby = File.expand_path('miniruby')
@verbose = false BT.verbose = false
$VERBOSE = false $VERBOSE = false
$stress = false $stress = false
@color = nil BT.color = nil
@tty = nil BT.tty = nil
@quiet = false BT.quiet = false
BT.wn = 1
dir = nil dir = nil
quiet = false quiet = false
tests = nil tests = nil
ARGV.delete_if {|arg| ARGV.delete_if {|arg|
case arg case arg
when /\A--ruby=(.*)/ when /\A--ruby=(.*)/
@ruby = $1 ruby = $1
@ruby.gsub!(/^([^ ]*)/){File.expand_path($1)} ruby.gsub!(/^([^ ]*)/){File.expand_path($1)}
@ruby.gsub!(/(\s+-I\s*)((?!(?:\.\/)*-(?:\s|\z))\S+)/){$1+File.expand_path($2)} ruby.gsub!(/(\s+-I\s*)((?!(?:\.\/)*-(?:\s|\z))\S+)/){$1+File.expand_path($2)}
@ruby.gsub!(/(\s+-r\s*)(\.\.?\/\S+)/){$1+File.expand_path($2)} ruby.gsub!(/(\s+-r\s*)(\.\.?\/\S+)/){$1+File.expand_path($2)}
BT.ruby = ruby
true true
when /\A--sets=(.*)/ when /\A--sets=(.*)/
tests = Dir.glob("#{File.dirname($0)}/test_{#{$1}}*.rb").sort tests = Dir.glob("#{File.dirname($0)}/test_{#{$1}}*.rb").sort
@ -88,18 +110,26 @@ def main
$stress = true $stress = true
when /\A--color(?:=(?:always|(auto)|(never)|(.*)))?\z/ when /\A--color(?:=(?:always|(auto)|(never)|(.*)))?\z/
warn "unknown --color argument: #$3" if $3 warn "unknown --color argument: #$3" if $3
@color = $1 ? nil : !$2 BT.color = color = $1 ? nil : !$2
true true
when /\A--tty(=(?:yes|(no)|(.*)))?\z/ when /\A--tty(=(?:yes|(no)|(.*)))?\z/
warn "unknown --tty argument: #$3" if $3 warn "unknown --tty argument: #$3" if $3
@tty = !$1 || !$2 BT.tty = !$1 || !$2
true true
when /\A(-q|--q(uiet))\z/ when /\A(-q|--q(uiet))\z/
quiet = true quiet = true
@quiet = true BT.quiet = true
true
when /\A-j(\d+)?/
wn = $1.to_i
if wn <= 0
require 'etc'
wn = [Etc.nprocessors / 2, 1].max
end
BT.wn = wn
true true
when /\A(-v|--v(erbose))\z/ when /\A(-v|--v(erbose))\z/
@verbose = true BT.verbose = true
when /\A(-h|--h(elp)?)\z/ when /\A(-h|--h(elp)?)\z/
puts(<<-End) puts(<<-End)
Usage: #{File.basename($0, '.*')} --ruby=PATH [--sets=NAME,NAME,...] Usage: #{File.basename($0, '.*')} --ruby=PATH [--sets=NAME,NAME,...]
@ -128,15 +158,16 @@ End
tests = Dir.glob("#{File.dirname($0)}/test_*.rb").sort if tests.empty? tests = Dir.glob("#{File.dirname($0)}/test_*.rb").sort if tests.empty?
pathes = tests.map {|path| File.expand_path(path) } pathes = tests.map {|path| File.expand_path(path) }
@progress = %w[- \\ | /] BT.progress = %w[- \\ | /]
@progress_bs = "\b" * @progress[0].size BT.progress_bs = "\b" * BT.progress[0].size
@tty = $stderr.tty? if @tty.nil? BT.tty = $stderr.tty? if BT.tty.nil?
case @color
case BT.color
when nil when nil
@color = @tty && /dumb/ !~ ENV["TERM"] BT.color = BT.tty && /dumb/ !~ ENV["TERM"]
end end
@tty &&= !@verbose BT.tty &&= !BT.verbose
if @color if BT.color
# dircolors-like style # dircolors-like style
colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:\n]*)/)] : {} colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:\n]*)/)] : {}
begin begin
@ -145,14 +176,14 @@ End
end end
rescue rescue
end end
@passed = "\e[;#{colors["pass"] || "32"}m" BT.passed = "\e[;#{colors["pass"] || "32"}m"
@failed = "\e[;#{colors["fail"] || "31"}m" BT.failed = "\e[;#{colors["fail"] || "31"}m"
@reset = "\e[m" BT.reset = "\e[m"
else else
@passed = @failed = @reset = "" BT.passed = BT.failed = BT.reset = ""
end end
unless quiet unless quiet
puts Time.now puts $start_time
if defined?(RUBY_DESCRIPTION) if defined?(RUBY_DESCRIPTION)
puts "Driver is #{RUBY_DESCRIPTION}" puts "Driver is #{RUBY_DESCRIPTION}"
elsif defined?(RUBY_PATCHLEVEL) elsif defined?(RUBY_PATCHLEVEL)
@ -160,289 +191,473 @@ End
else else
puts "Driver is ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" puts "Driver is ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
end end
puts "Target is #{`#{@ruby} -v`.chomp}" puts "Target is #{`#{BT.ruby} -v`.chomp}"
puts puts
$stdout.flush $stdout.flush
end end
in_temporary_working_directory(dir) { in_temporary_working_directory(dir) do
exec_test pathes exec_test pathes
} end
end end
def erase(e = true) def erase(e = true)
if e and @columns > 0 and @tty and !@verbose if e and BT.columns > 0 and BT.tty and !BT.verbose
"\e[1K\r" "\e[1K\r"
else else
"" ""
end end
end end
def exec_test(pathes) def load_test pathes
@count = 0
@error = 0
@errbuf = []
@location = nil
@columns = 0
@width = pathes.map {|path| File.basename(path).size}.max + 2
pathes.each do |path| pathes.each do |path|
@basename = File.basename(path)
unless @quiet
$stderr.printf("%s%-*s ", erase(@quiet), @width, @basename)
$stderr.flush
end
@columns = @width + 1
$stderr.puts if @verbose
count = @count
error = @error
load File.expand_path(path) load File.expand_path(path)
if @tty
if @error == error
msg = "PASS #{@count-count}"
@columns += msg.size - 1
$stderr.print "#{@progress_bs}#{@passed}#{msg}#{@reset}" unless @quiet
else
msg = "FAIL #{@error-error}/#{@count-count}"
$stderr.print "#{@progress_bs}#{@failed}#{msg}#{@reset}"
@columns = 0
end
end
$stderr.puts if !@quiet and (@tty or @error == error)
end
$stderr.print(erase) if @quiet
@errbuf.each do |msg|
$stderr.puts msg
end
out = @quiet ? $stdout : $stderr
if @error == 0
if @count == 0
out.puts "No tests, no problem" unless @quiet
else
out.puts "#{@passed}PASS#{@reset} all #{@count} tests"
end
exit true
else
$stderr.puts "#{@failed}FAIL#{@reset} #{@error}/#{@count} tests failed"
exit false
end end
end end
def show_progress(message = '') def concurrent_exec_test
if @quiet aq = Queue.new
# do nothing rq = Queue.new
elsif @verbose
$stderr.print "\##{@count} #{@location} " ts = BT.wn.times.map do
elsif @tty Thread.new do
$stderr.print "#{@progress_bs}#{@progress[@count % @progress.size]}" while as = aq.pop
end as.call
t = Time.now if @verbose rq << as
faildesc, errout = with_stderr {yield} end
t = Time.now - t if @verbose ensure
if !faildesc rq << nil
if @quiet
# do nothing
elsif @tty
$stderr.print "#{@progress_bs}#{@progress[@count % @progress.size]}"
elsif @verbose
$stderr.printf(". %.3f\n", t)
else
$stderr.print '.'
end
else
$stderr.print "#{@failed}F"
$stderr.printf(" %.3f", t) if @verbose
$stderr.print @reset
$stderr.puts if @verbose
error faildesc, message
unless errout.empty?
$stderr.print "#{@failed}stderr output is not empty#{@reset}\n", adjust_indent(errout)
end
if @tty and !@verbose
$stderr.printf("%-*s%s", @width, @basename, @progress[@count % @progress.size])
end end
end end
rescue Interrupt
$stderr.puts "\##{@count} #{@location}" Assertion.all.to_a.shuffle.each do |path, assertions|
raise assertions.each do |as|
rescue Exception => err aq << as
$stderr.print 'E' end
$stderr.puts if @verbose end
error err.message, message
ensure $stderr.print ' ' unless BT.quiet
aq.close
i = 1
term_wn = 0
begin begin
check_coredump while BT.wn != term_wn
rescue CoreDumpError => err if r = rq.pop
$stderr.print 'E' case
$stderr.puts if @verbose when BT.quiet
error err.message, message when BT.tty
$stderr.print "#{BT.progress_bs}#{BT.progress[(i+=1) % BT.progress.size]}"
else
$stderr.print '.'
end
else
term_wn += 1
end
end
ensure
ts.each(&:kill)
ts.each(&:join)
end
end
def exec_test(pathes)
# setup
load_test pathes
BT_STATE.count = 0
BT_STATE.error = 0
BT.columns = 0
BT.width = pathes.map {|path| File.basename(path).size}.max + 2
# execute tests
if BT.wn > 1
concurrent_exec_test if BT.wn > 1
else
prev_basename = nil
Assertion.all.each do |basename, assertions|
if !BT.quiet && basename != prev_basename
prev_basename = basename
$stderr.printf("%s%-*s ", erase(BT.quiet), BT.width, basename)
$stderr.flush
end
BT.columns = BT.width + 1
$stderr.puts if BT.verbose
count = BT_STATE.count
error = BT_STATE.error
assertions.each do |assertion|
BT_STATE.count += 1
assertion.call
end
if BT.tty
if BT_STATE.error == error
msg = "PASS #{BT_STATE.count-count}"
BT.columns += msg.size - 1
$stderr.print "#{BT.progress_bs}#{BT.passed}#{msg}#{BT.reset}" unless BT.quiet
else
msg = "FAIL #{BT_STATE.error-error}/#{BT_STATE.count-count}"
$stderr.print "#{BT.progress_bs}#{BT.failed}#{msg}#{BT.reset}"
BT.columns = 0
end
end
$stderr.puts if !BT.quiet and (BT.tty or BT_STATE.error == error)
end
end
# show results
unless BT.quiet
$stderr.puts(erase)
sec = Time.now - $start_time
$stderr.puts "Finished in #{'%.2f' % sec} sec\n\n" if Assertion.count > 0
end
Assertion.errbuf.each do |msg|
$stderr.puts msg
end
out = BT.quiet ? $stdout : $stderr
if BT_STATE.error == 0
if Assertion.count == 0
out.puts "No tests, no problem" unless BT.quiet
else
out.puts "#{BT.passed}PASS#{BT.reset} all #{Assertion.count} tests"
end
true
else
$stderr.puts "#{BT.failed}FAIL#{BT.reset} #{BT_STATE.error}/#{BT_STATE.count} tests failed"
false
end end
end end
def target_platform def target_platform
if @ruby if BT.ruby
`#{@ruby} --disable-gems -e 'print RUBY_PLATFORM'` `#{BT.ruby} --disable-gems -e 'print RUBY_PLATFORM'`
else else
RUBY_PLATFORM RUBY_PLATFORM
end end
end end
def show_limit(testsrc, opt = '', **argh) class Assertion < Struct.new(:src, :path, :lineno, :proc)
result = get_result_string(testsrc, opt, **argh) @count = 0
if @tty and @verbose @all = Hash.new{|h, k| h[k] = []}
$stderr.puts ".{#@reset}\n#{erase}#{result}" @errbuf = []
else
@errbuf.push result class << self
attr_reader :count, :errbuf
def all
@all
end
def add as
@all[as.path] << as
as.id = (@count += 1)
end
end
attr_accessor :id
attr_reader :err, :category
def initialize(*args)
super
self.class.add self
@category = self.path.match(/test_(.+)\.rb/)[1]
end
def call
self.proc.call self
end
def assert_check(message = '', opt = '', **argh)
show_progress(message) {
result = get_result_string(opt, **argh)
yield(result)
}
end
def with_stderr
out = err = nil
r, w = IO.pipe
@err = w
err_reader = Thread.new{ r.read }
begin
out = yield
ensure
w.close
err = err_reader.value
r.close rescue nil
end
return out, err
end
def show_error(msg, additional_message)
msg = "#{BT.failed}\##{self.id} #{self.path}:#{self.lineno}#{BT.reset}: #{msg} #{additional_message}"
if BT.tty
$stderr.puts "#{erase}#{msg}"
else
Assertion.errbuf << msg
end
BT_STATE.error += 1
end
def show_progress(message = '')
if BT.quiet || BT.wn > 1
# do nothing
elsif BT.verbose
$stderr.print "\##{@id} #{self.path}:#{self.lineno} "
elsif BT.tty
$stderr.print "#{BT.progress_bs}#{BT.progress[BT_STATE.count % BT.progress.size]}"
end
t = Time.now if BT.verbose
faildesc, errout = with_stderr {yield}
t = Time.now - t if BT.verbose
if !faildesc
# success
if BT.quiet || BT.wn > 1
# do nothing
elsif BT.tty
$stderr.print "#{BT.progress_bs}#{BT.progress[BT_STATE.count % BT.progress.size]}"
elsif BT.verbose
$stderr.printf(". %.3f\n", t)
else
$stderr.print '.'
end
else
$stderr.print "#{BT.failed}F"
$stderr.printf(" %.3f", t) if BT.verbose
$stderr.print BT.reset
$stderr.puts if BT.verbose
show_error faildesc, message
unless errout.empty?
$stderr.print "#{BT.failed}stderr output is not empty#{BT.reset}\n", adjust_indent(errout)
end
if BT.tty and !BT.verbose and BT.wn == 1
$stderr.printf("%-*s%s", BT.width, path, BT.progress[BT_STATE.count % BT.progress.size])
end
end
rescue Interrupt
$stderr.puts "\##{@id} #{path}:#{lineno}"
raise
rescue Exception => err
$stderr.print 'E'
$stderr.puts if BT.verbose
show_error err.message, message
ensure
begin
check_coredump
rescue CoreDumpError => err
$stderr.print 'E'
$stderr.puts if BT.verbose
show_error err.message, message
cleanup_coredump
end
end
def get_result_string(opt = '', **argh)
if BT.ruby
filename = make_srcfile(**argh)
begin
kw = self.err ? {err: self.err} : {}
out = IO.popen("#{BT.ruby} -W0 #{opt} #{filename}", **kw)
pid = out.pid
out.read.tap{ Process.waitpid(pid); out.close }
ensure
raise Interrupt if $? and $?.signaled? && $?.termsig == Signal.list["INT"]
begin
Process.kill :KILL, pid
rescue Errno::ESRCH
# OK
end
end
else
eval(src).to_s
end
end
def make_srcfile(frozen_string_literal: nil)
filename = "bootstraptest.#{self.path}_#{self.lineno}_#{self.id}.rb"
File.open(filename, 'w') {|f|
f.puts "#frozen_string_literal:true" if frozen_string_literal
f.puts "GC.stress = true" if $stress
f.puts "print(begin; #{self.src}; end)"
}
filename
end end
end end
def assert_check(testsrc, message = '', opt = '', **argh) def add_assertion src, pr
show_progress(message) { loc = caller_locations(2, 1).first
result = get_result_string(testsrc, opt, **argh) lineno = loc.lineno
yield(result) path = File.basename(loc.path)
}
Assertion.new(src, path, lineno, pr)
end end
def assert_equal(expected, testsrc, message = '', opt = '', **argh) def assert_equal(expected, testsrc, message = '', opt = '', **argh)
newtest add_assertion testsrc, -> as do
assert_check(testsrc, message, opt, **argh) {|result| as.assert_check(message, opt, **argh) {|result|
if expected == result if expected == result
nil nil
else else
desc = "#{result.inspect} (expected #{expected.inspect})" desc = "#{result.inspect} (expected #{expected.inspect})"
pretty(testsrc, desc, result) pretty(testsrc, desc, result)
end end
} }
end
end end
def assert_match(expected_pattern, testsrc, message = '') def assert_match(expected_pattern, testsrc, message = '')
newtest add_assertion testsrc, -> as do
assert_check(testsrc, message) {|result| as.assert_check(message) {|result|
if expected_pattern =~ result if expected_pattern =~ result
nil nil
else else
desc = "#{expected_pattern.inspect} expected to be =~\n#{result.inspect}" desc = "#{expected_pattern.inspect} expected to be =~\n#{result.inspect}"
pretty(testsrc, desc, result) pretty(testsrc, desc, result)
end end
} }
end
end end
def assert_not_match(unexpected_pattern, testsrc, message = '') def assert_not_match(unexpected_pattern, testsrc, message = '')
newtest add_assertion testsrc, -> as do
assert_check(testsrc, message) {|result| as.assert_check(message) {|result|
if unexpected_pattern !~ result if unexpected_pattern !~ result
nil nil
else else
desc = "#{unexpected_pattern.inspect} expected to be !~\n#{result.inspect}" desc = "#{unexpected_pattern.inspect} expected to be !~\n#{result.inspect}"
pretty(testsrc, desc, result) pretty(testsrc, desc, result)
end end
} }
end
end end
def assert_valid_syntax(testsrc, message = '') def assert_valid_syntax(testsrc, message = '')
newtest add_assertion testsrc, -> as do
assert_check(testsrc, message, '-c') {|result| as.assert_check(message, '-c') {|result|
result if /Syntax OK/ !~ result result if /Syntax OK/ !~ result
} }
end
end end
def assert_normal_exit(testsrc, *rest, timeout: nil, **opt) def assert_normal_exit(testsrc, *rest, timeout: nil, **opt)
newtest add_assertion testsrc, -> as do
message, ignore_signals = rest message, ignore_signals = rest
message ||= '' message ||= ''
show_progress(message) { as.show_progress(message) {
faildesc = nil faildesc = nil
filename = make_srcfile(testsrc) filename = as.make_srcfile
old_stderr = $stderr.dup timeout_signaled = false
timeout_signaled = false logfile = "assert_normal_exit.#{as.path}.#{as.lineno}.log"
begin
$stderr.reopen("assert_normal_exit.log", "w") begin
io = IO.popen("#{@ruby} -W0 #{filename}") err = open(logfile, "w")
pid = io.pid io = IO.popen("#{BT.ruby} -W0 #{filename}", err: err)
th = Thread.new { pid = io.pid
io.read th = Thread.new {
io.close io.read
$? io.close
} $?
if !th.join(timeout) }
Process.kill :KILL, pid if !th.join(timeout)
timeout_signaled = true Process.kill :KILL, pid
timeout_signaled = true
end
status = th.value
ensure
err.close
end end
status = th.value if status && status.signaled?
ensure signo = status.termsig
$stderr.reopen(old_stderr) signame = Signal.list.invert[signo]
old_stderr.close unless ignore_signals and ignore_signals.include?(signame)
end sigdesc = "signal #{signo}"
if status && status.signaled? if signame
signo = status.termsig sigdesc = "SIG#{signame} (#{sigdesc})"
signame = Signal.list.invert[signo] end
unless ignore_signals and ignore_signals.include?(signame) if timeout_signaled
sigdesc = "signal #{signo}" sigdesc << " (timeout)"
if signame end
sigdesc = "SIG#{signame} (#{sigdesc})" faildesc = pretty(testsrc, "killed by #{sigdesc}", nil)
end stderr_log = File.read(logfile)
if timeout_signaled if !stderr_log.empty?
sigdesc << " (timeout)" faildesc << "\n" if /\n\z/ !~ faildesc
end stderr_log << "\n" if /\n\z/ !~ stderr_log
faildesc = pretty(testsrc, "killed by #{sigdesc}", nil) stderr_log.gsub!(/^.*\n/) { '| ' + $& }
stderr_log = File.read("assert_normal_exit.log") faildesc << stderr_log
if !stderr_log.empty? end
faildesc << "\n" if /\n\z/ !~ faildesc
stderr_log << "\n" if /\n\z/ !~ stderr_log
stderr_log.gsub!(/^.*\n/) { '| ' + $& }
faildesc << stderr_log
end end
end end
end faildesc
faildesc }
} end
end end
def assert_finish(timeout_seconds, testsrc, message = '') def assert_finish(timeout_seconds, testsrc, message = '')
if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # for --jit-wait add_assertion testsrc, -> as do
timeout_seconds *= 3 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # for --jit-wait
end timeout_seconds *= 3
newtest end
show_progress(message) {
faildesc = nil as.show_progress(message) {
filename = make_srcfile(testsrc) faildesc = nil
io = IO.popen("#{@ruby} -W0 #{filename}") filename = as.make_srcfile
pid = io.pid io = IO.popen("#{BT.ruby} -W0 #{filename}", err: as.err)
waited = false pid = io.pid
tlimit = Time.now + timeout_seconds waited = false
diff = timeout_seconds tlimit = Time.now + timeout_seconds
while diff > 0 diff = timeout_seconds
if Process.waitpid pid, Process::WNOHANG while diff > 0
waited = true if Process.waitpid pid, Process::WNOHANG
break waited = true
end break
if io.respond_to?(:read_nonblock)
if IO.select([io], nil, nil, diff)
begin
io.read_nonblock(1024)
rescue Errno::EAGAIN, IO::WaitReadable, EOFError
break
end while true
end end
else if io.respond_to?(:read_nonblock)
sleep 0.1 if IO.select([io], nil, nil, diff)
begin
io.read_nonblock(1024)
rescue Errno::EAGAIN, IO::WaitReadable, EOFError
break
end while true
end
else
sleep 0.1
end
diff = tlimit - Time.now
end end
diff = tlimit - Time.now if !waited
end Process.kill(:KILL, pid)
if !waited Process.waitpid pid
Process.kill(:KILL, pid) faildesc = pretty(testsrc, "not finished in #{timeout_seconds} seconds", nil)
Process.waitpid pid end
faildesc = pretty(testsrc, "not finished in #{timeout_seconds} seconds", nil) io.close
end faildesc
io.close }
faildesc end
}
end end
def flunk(message = '') def flunk(message = '')
newtest add_assertion '', -> as do
show_progress('') { message } as.show_progress('') { message }
end
end
def show_limit(testsrc, opt = '', **argh)
return if BT.quiet
add_assertion testsrc, -> as do
result = as.get_result_string(opt, **argh)
Assertion.errbuf << result
end
end end
def pretty(src, desc, result) def pretty(src, desc, result)
@ -460,66 +675,6 @@ def untabify(str)
str.gsub(/^\t+/) {' ' * (8 * $&.size) } str.gsub(/^\t+/) {' ' * (8 * $&.size) }
end end
def make_srcfile(src, frozen_string_literal: nil)
filename = 'bootstraptest.tmp.rb'
File.open(filename, 'w') {|f|
f.puts "#frozen_string_literal:true" if frozen_string_literal
f.puts "GC.stress = true" if $stress
f.puts "print(begin; #{src}; end)"
}
filename
end
def get_result_string(src, opt = '', **argh)
if @ruby
filename = make_srcfile(src, **argh)
begin
`#{@ruby} -W0 #{opt} #{filename}`
ensure
raise Interrupt if $? and $?.signaled? && $?.termsig == Signal.list["INT"]
end
else
eval(src).to_s
end
end
def with_stderr
out = err = nil
begin
r, w = IO.pipe
stderr = $stderr.dup
$stderr.reopen(w)
w.close
reader = Thread.start {r.read}
begin
out = yield
ensure
$stderr.reopen(stderr)
err = reader.value
end
ensure
w.close rescue nil
r.close rescue nil
end
return out, err
end
def newtest
@location = File.basename(caller(2).first)
@count += 1
cleanup_coredump
end
def error(msg, additional_message)
msg = "#{@failed}\##{@count} #{@location}#{@reset}: #{msg} #{additional_message}"
if @tty
$stderr.puts "#{erase}#{msg}"
else
@errbuf.push msg
end
@error += 1
end
def in_temporary_working_directory(dir) def in_temporary_working_directory(dir)
if dir if dir
Dir.mkdir dir Dir.mkdir dir
@ -547,21 +702,21 @@ def cleanup_coredump
core_path = "/tmp/bootstraptest-core.#{Time.now.utc.iso8601}" core_path = "/tmp/bootstraptest-core.#{Time.now.utc.iso8601}"
warn "A core file is found. Saving it at: #{core_path.dump}" warn "A core file is found. Saving it at: #{core_path.dump}"
FileUtils.mv('core', core_path) FileUtils.mv('core', core_path)
cmd = ['gdb', @ruby, '-c', core_path, '-ex', 'bt', '-batch'] cmd = ['gdb', BT.ruby, '-c', core_path, '-ex', 'bt', '-batch']
p cmd # debugging why it's not working p cmd # debugging why it's not working
system(*cmd) system(*cmd)
end end
FileUtils.rm_f Dir.glob('core.*') FileUtils.rm_f Dir.glob('core.*')
FileUtils.rm_f @ruby+'.stackdump' if @ruby FileUtils.rm_f BT.ruby+'.stackdump' if BT.ruby
end end
class CoreDumpError < StandardError; end class CoreDumpError < StandardError; end
def check_coredump def check_coredump
if File.file?('core') or not Dir.glob('core.*').empty? or if File.file?('core') or not Dir.glob('core.*').empty? or
(@ruby and File.exist?(@ruby+'.stackdump')) (BT.ruby and File.exist?(BT.ruby+'.stackdump'))
raise CoreDumpError, "core dumped" raise CoreDumpError, "core dumped"
end end
end end
main exit main

View file

@ -1,7 +1,7 @@
assert_equal 'ok', %q{ assert_equal 'ok', %q{
File.unlink('zzz.rb') if File.file?('zzz.rb') File.unlink('zzz1.rb') if File.file?('zzz1.rb')
instance_eval do instance_eval do
autoload :ZZZ, './zzz.rb' autoload :ZZZ, './zzz1.rb'
begin begin
ZZZ ZZZ
rescue LoadError rescue LoadError
@ -11,9 +11,9 @@ assert_equal 'ok', %q{
}, '[ruby-dev:43816]' }, '[ruby-dev:43816]'
assert_equal 'ok', %q{ assert_equal 'ok', %q{
open('zzz.rb', 'w') {|f| f.puts '' } open('zzz2.rb', 'w') {|f| f.puts '' }
instance_eval do instance_eval do
autoload :ZZZ, './zzz.rb' autoload :ZZZ, './zzz2.rb'
begin begin
ZZZ ZZZ
rescue NameError rescue NameError
@ -23,29 +23,29 @@ assert_equal 'ok', %q{
}, '[ruby-dev:43816]' }, '[ruby-dev:43816]'
assert_equal 'ok', %q{ assert_equal 'ok', %q{
open('zzz.rb', 'w') {|f| f.puts 'class ZZZ; def self.ok;:ok;end;end'} open('zzz3.rb', 'w') {|f| f.puts 'class ZZZ; def self.ok;:ok;end;end'}
instance_eval do instance_eval do
autoload :ZZZ, './zzz.rb' autoload :ZZZ, './zzz3.rb'
ZZZ.ok ZZZ.ok
end end
}, '[ruby-dev:43816]' }, '[ruby-dev:43816]'
assert_equal 'ok', %q{ assert_equal 'ok', %q{
open("zzz.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"} open("zzz4.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"}
autoload :ZZZ, "./zzz.rb" autoload :ZZZ, "./zzz4.rb"
ZZZ.ok ZZZ.ok
} }
assert_equal 'ok', %q{ assert_equal 'ok', %q{
open("zzz.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"} open("zzz5.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"}
autoload :ZZZ, "./zzz.rb" autoload :ZZZ, "./zzz5.rb"
require "./zzz.rb" require "./zzz5.rb"
ZZZ.ok ZZZ.ok
} }
assert_equal 'okok', %q{ assert_equal 'okok', %q{
open("zzz.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"} open("zzz6.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"}
autoload :ZZZ, "./zzz.rb" autoload :ZZZ, "./zzz6.rb"
t1 = Thread.new {ZZZ.ok} t1 = Thread.new {ZZZ.ok}
t2 = Thread.new {ZZZ.ok} t2 = Thread.new {ZZZ.ok}
[t1.value, t2.value].join [t1.value, t2.value].join
@ -60,9 +60,9 @@ assert_finish 5, %q{
}, '[ruby-core:21696]' }, '[ruby-core:21696]'
assert_equal 'A::C', %q{ assert_equal 'A::C', %q{
open("zzz.rb", "w") {} open("zzz7.rb", "w") {}
class A class A
autoload :C, "./zzz" autoload :C, "./zzz7"
class C class C
end end
C C

View file

@ -10,7 +10,7 @@ show_limit %q{
puts "Fiber count: #{fibers.count} (#{error})" puts "Fiber count: #{fibers.count} (#{error})"
break break
end while true end while true
} unless @quiet }
assert_equal %q{ok}, %q{ assert_equal %q{ok}, %q{
Fiber.new{ Fiber.new{

View file

@ -22,7 +22,7 @@ assert_match /\Awrong number of arguments \(.*\b0\b.* 1\)\z/, %q{
} }
# default argument # default argument
assert_equal '1', 'def m(x=1) x end; m()' assert_equal '1', 'def m(x=1) x end; m();'
assert_equal '1', 'def m(x=7) x end; m(1)' assert_equal '1', 'def m(x=7) x end; m(1)'
assert_equal '1', 'def m(a,x=1) x end; m(7)' assert_equal '1', 'def m(a,x=1) x end; m(7)'
assert_equal '1', 'def m(a,x=7) x end; m(7,1)' assert_equal '1', 'def m(a,x=7) x end; m(7,1)'

View file

@ -243,7 +243,7 @@ assert_equal 'true', %{
} }
assert_equal 'ok', %{ assert_equal 'ok', %{
open("zzz.rb", "w") do |f| open("zzz_t1.rb", "w") do |f|
f.puts <<-END f.puts <<-END
begin begin
Thread.new { fork { GC.start } }.join Thread.new { fork { GC.start } }.join
@ -254,7 +254,7 @@ assert_equal 'ok', %{
end end
END END
end end
require "./zzz.rb" require "./zzz_t1.rb"
$result $result
} }
@ -408,7 +408,7 @@ assert_equal 'ok', %q{
} }
assert_equal 'ok', %{ assert_equal 'ok', %{
open("zzz.rb", "w") do |f| open("zzz_t2.rb", "w") do |f|
f.puts <<-'end;' # do f.puts <<-'end;' # do
begin begin
m = Thread::Mutex.new m = Thread::Mutex.new
@ -432,7 +432,7 @@ assert_equal 'ok', %{
end end
end; end;
end end
require "./zzz.rb" require "./zzz_t2.rb"
$result $result
} }