1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899
|
"exec" "${RUBY-ruby}" "-x" "$0" "$@" || true # -*- Ruby -*-
#!./ruby
# $Id$
# NOTE:
# Never use optparse in this file.
# Never use test/unit in this file.
# Never use Ruby extensions in this file.
$start_time = Time.now
begin
require 'fileutils'
require 'tmpdir'
rescue LoadError
$:.unshift File.join(File.dirname(__FILE__), '../lib')
retry
end
if !Dir.respond_to?(:mktmpdir)
# copied from lib/tmpdir.rb
def Dir.mktmpdir(prefix_suffix=nil, tmpdir=nil)
case prefix_suffix
when nil
prefix = "d"
suffix = ""
when String
prefix = prefix_suffix
suffix = ""
when Array
prefix = prefix_suffix[0]
suffix = prefix_suffix[1]
else
raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
end
tmpdir ||= Dir.tmpdir
t = Time.now.strftime("%Y%m%d")
n = nil
begin
path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
path << "-#{n}" if n
path << suffix
Dir.mkdir(path, 0700)
rescue Errno::EEXIST
n ||= 0
n += 1
retry
end
if block_given?
begin
yield path
ensure
FileUtils.remove_entry_secure path
end
else
path
end
end
end
# Configuration
bt = Struct.new(:ruby,
:verbose,
:color,
:tty,
:quiet,
:wn,
:progress,
:progress_bs,
:passed,
:failed,
:reset,
:columns,
:window_width,
:width,
:indent,
:platform,
:timeout,
:timeout_scale,
:launchable_test_reports
)
BT = Class.new(bt) do
def indent=(n)
super
if (self.columns ||= 0) < n
$stderr.print(' ' * (n - self.columns))
end
self.columns = indent
end
def putc(c)
unless self.quiet
if self.window_width == nil
unless w = ENV["COLUMNS"] and (w = w.to_i) > 0
w = 80
end
w -= 1
self.window_width = w
end
if self.window_width and self.columns >= self.window_width
$stderr.print "\n", " " * (self.indent ||= 0)
self.columns = indent
end
$stderr.print c
$stderr.flush
self.columns += 1
end
end
def wn=(wn)
unless wn == 1
if /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ ENV.delete("MAKEFLAGS")
begin
if fifo = $3
fifo.gsub!(/\\(?=.)/, '')
r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY)
w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY)
else
r = IO.for_fd($1.to_i(10), "rb", autoclose: false)
w = IO.for_fd($2.to_i(10), "wb", autoclose: false)
end
rescue
r.close if r
else
r.close_on_exec = true
w.close_on_exec = true
tokens = r.read_nonblock(wn > 0 ? wn : 1024, exception: false)
r.close
if String === tokens
tokens.freeze
auth = w
w = nil
at_exit {auth << tokens; auth.close}
wn = tokens.size + 1
else
w.close
wn = 1
end
end
end
if wn <= 0
require 'etc'
wn = [Etc.nprocessors / 2, 1].max
end
end
super wn
end
def apply_timeout_scale(timeout)
timeout&.*(timeout_scale)
end
end.new
BT_STATE = Struct.new(:count, :error).new
def main
BT.ruby = File.expand_path('miniruby')
BT.verbose = false
$VERBOSE = false
$stress = false
BT.color = nil
BT.tty = nil
BT.quiet = false
BT.timeout = 180
BT.timeout_scale = (defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? ? 3 : 1) # for --jit-wait
if (ts = (ENV["RUBY_TEST_TIMEOUT_SCALE"] || ENV["RUBY_TEST_SUBPROCESS_TIMEOUT_SCALE"]).to_i) > 1
BT.timeout_scale *= ts
end
# BT.wn = 1
dir = nil
quiet = false
tests = nil
ARGV.delete_if {|arg|
case arg
when /\A--ruby=(.*)/
ruby = $1
ruby.gsub!(/^([^ ]*)/){File.expand_path($1)}
ruby.gsub!(/(\s+-I\s*)((?!(?:\.\/)*-(?:\s|\z))\S+)/){$1+File.expand_path($2)}
ruby.gsub!(/(\s+-r\s*)(\.\.?\/\S+)/){$1+File.expand_path($2)}
BT.ruby = ruby
true
when /\A--sets=(.*)/
tests = Dir.glob("#{File.dirname($0)}/test_{#{$1}}*.rb").sort
puts tests.map {|path| File.basename(path) }.inspect
true
when /\A--dir=(.*)/
dir = $1
true
when /\A(--stress|-s)/
$stress = true
when /\A--color(?:=(?:always|(auto)|(never)|(.*)))?\z/
warn "unknown --color argument: #$3" if $3
BT.color = color = $1 ? nil : !$2
true
when /\A--tty(=(?:yes|(no)|(.*)))?\z/
warn "unknown --tty argument: #$3" if $3
BT.tty = !$1 || !$2
true
when /\A(-q|--q(uiet)?)\z/
quiet = true
BT.quiet = true
true
when /\A-j(\d+)?/
BT.wn = $1.to_i
true
when /\A--timeout=(\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?)(?::(\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?))?/
BT.timeout = $1.to_f
BT.timeout_scale = $2.to_f if defined?($2)
true
when /\A(-v|--v(erbose)?)\z/
BT.verbose = true
BT.quiet = false
true
when /\A(-h|--h(elp)?)\z/
puts(<<-End)
Usage: #{File.basename($0, '.*')} --ruby=PATH [--sets=NAME,NAME,...]
--sets=NAME,NAME,... Name of test sets.
--dir=DIRECTORY Working directory.
default: /tmp/bootstraptestXXXXX.tmpwd
--color[=WHEN] Colorize the output. WHEN defaults to 'always'
or can be 'never' or 'auto'.
--timeout=TIMEOUT Default timeout in seconds.
-s, --stress stress test.
-v, --verbose Output test name before exec.
-q, --quiet Don\'t print header message.
-h, --help Print this message and quit.
End
exit true
when /\A-j/
true
when /--launchable-test-reports=(.*)/
if File.exist?($1)
# To protect files from overwritten, do nothing when the file exists.
return true
end
begin
require_relative '../tool/lib/launchable'
rescue LoadError
# The following error sometimes happens, so we're going to skip writing Launchable report files in this case.
#
# ```
# /tmp/tmp.bISss9CtXZ/.ext/common/json/ext.rb:15:in 'Kernel#require':
# /tmp/tmp.bISss9CtXZ/.ext/x86_64-linux/json/ext/parser.so:
# undefined symbol: ruby_abi_version - ruby_abi_version (LoadError)
# ```
#
return true
end
BT.launchable_test_reports = writer = Launchable::JsonStreamWriter.new($1)
writer.write_array('testCases')
at_exit {
writer.close
}
true
else
false
end
}
if tests and not ARGV.empty?
abort "--sets and arguments are exclusive"
end
tests ||= ARGV
tests = Dir.glob("#{File.dirname($0)}/test_*.rb").sort if tests.empty?
paths = tests.map {|path| File.expand_path(path) }
BT.progress = %w[- \\ | /]
BT.progress_bs = "\b" * BT.progress[0].size
BT.tty = $stderr.tty? if BT.tty.nil?
BT.wn ||= /-j(\d+)?/ =~ (ENV["MAKEFLAGS"] || ENV["MFLAGS"]) ? $1.to_i : 1
case BT.color
when nil
BT.color = BT.tty && /dumb/ !~ ENV["TERM"]
end
BT.tty &&= !BT.verbose
if BT.color
# dircolors-like style
colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:\n]*)/)] : {}
begin
File.read(File.join(__dir__, "../tool/colors")).scan(/(\w+)=([^:\n]*)/) do |n, c|
colors[n] ||= c
end
rescue
end
BT.passed = "\e[;#{colors["pass"] || "32"}m"
BT.failed = "\e[;#{colors["fail"] || "31"}m"
BT.reset = "\e[m"
else
BT.passed = BT.failed = BT.reset = ""
end
target_version = `#{BT.ruby} -v`.chomp
BT.platform = target_version[/\[(.*)\]\z/, 1]
unless quiet
puts $start_time
if defined?(RUBY_DESCRIPTION)
puts "Driver is #{RUBY_DESCRIPTION}"
elsif defined?(RUBY_PATCHLEVEL)
puts "Driver is ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}#{RUBY_PLATFORM}) [#{RUBY_PLATFORM}]"
else
puts "Driver is ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
end
puts "Target is #{target_version}"
puts
$stdout.flush
end
in_temporary_working_directory(dir) do
exec_test paths
end
end
def erase(e = true)
if e and BT.columns > 0 and BT.tty and !BT.verbose
"\e[1K\r"
else
""
end
end
def load_test paths
paths.each do |path|
load File.expand_path(path)
end
end
def concurrent_exec_test
aq = Queue.new
rq = Queue.new
ts = BT.wn.times.map do
Thread.new do
while as = aq.pop
as.call
rq << as
end
ensure
rq << nil
end
end
Assertion.all.to_a.shuffle.each do |path, assertions|
assertions.each do |as|
aq << as
end
end
BT.indent = 1
aq.close
i = 1
term_wn = 0
begin
while BT.wn != term_wn
if r = rq.pop
BT_STATE.count += 1
case
when BT.quiet
when BT.tty
$stderr.print "#{BT.progress_bs}#{BT.progress[(i+=1) % BT.progress.size]}"
else
BT.putc '.'
end
else
term_wn += 1
end
end
ensure
ts.each(&:kill)
ts.each(&:join)
end
end
##
# Module for writing a test file for uploading test results into Launchable.
# In bootstraptest, we aggregate the test results based on file level.
module Launchable
@@last_test_name = nil
@@failure_log = ''
@@duration = 0
def show_progress(message = '')
faildesc, t = super
if writer = BT.launchable_test_reports
if faildesc
@@failure_log += faildesc
end
repo_path = File.expand_path("#{__dir__}/../")
relative_path = File.join(__dir__, self.path).delete_prefix("#{repo_path}/")
if @@last_test_name != nil && @@last_test_name != relative_path
# The test path is a URL-encoded representation.
# https://github.com/launchableinc/cli/blob/v1.81.0/launchable/testpath.py#L18
test_path = "#{encode_test_path_component("file")}=#{encode_test_path_component(@@last_test_name)}"
if @@failure_log.size > 0
status = 'TEST_FAILED'
else
status = 'TEST_PASSED'
end
writer.write_object(
{
testPath: test_path,
status: status,
duration: t,
createdAt: Time.now.to_s,
stderr: @@failure_log,
stdout: nil,
data: {
lineNumber: self.lineno
}
}
)
@@duration = 0
@@failure_log.clear
end
@@last_test_name = relative_path
@@duration += t
end
end
private
def encode_test_path_component component
component.to_s.gsub('%', '%25').gsub('=', '%3D').gsub('#', '%23').gsub('&', '%26')
end
end
def exec_test(paths)
# setup
load_test paths
BT_STATE.count = 0
BT_STATE.error = 0
BT.columns = 0
BT.width = paths.map {|path| File.basename(path).size}.max + 2
# execute tests
if BT.wn > 1
concurrent_exec_test
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
def target_platform
BT.platform or RUBY_PLATFORM
end
class Assertion < Struct.new(:src, :path, :lineno, :proc)
prepend Launchable
@count = 0
@all = Hash.new{|h, k| h[k] = []}
@errbuf = []
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[/\Atest_(.+)\.rb\z/, 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 || BT.launchable_test_reports
faildesc, errout = with_stderr {yield}
t = Time.now - t if BT.verbose || BT.launchable_test_reports
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
BT.putc '.'
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
[faildesc, t]
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 = '', timeout: BT.timeout, **argh)
if BT.ruby
timeout = BT.apply_timeout_scale(timeout)
filename = make_srcfile(**argh)
begin
kw = self.err ? {err: self.err} : {}
out = IO.popen("#{BT.ruby} -W0 #{opt} #{filename}", **kw)
pid = out.pid
th = Thread.new {out.read.tap {Process.waitpid(pid); out.close}}
th.value if th.join(timeout)
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:#{frozen_string_literal}" unless frozen_string_literal.nil?
if $stress
f.puts "GC.stress = true" if $stress
else
f.puts ""
end
f.puts "class BT_Skip < Exception; end; def skip(msg) = raise(BT_Skip, msg.to_s)"
f.puts "print(begin; #{self.src}; rescue BT_Skip; $!.message; end)"
}
filename
end
end
def add_assertion src, pr
loc = caller_locations(2, 1).first
lineno = loc.lineno
path = File.basename(loc.path)
Assertion.new(src, path, lineno, pr)
end
def assert_equal(expected, testsrc, message = '', opt = '', **kwargs)
add_assertion testsrc, -> as do
as.assert_check(message, opt, **kwargs) {|result|
if expected == result
nil
else
desc = "#{result.inspect} (expected #{expected.inspect})"
pretty(testsrc, desc, result)
end
}
end
end
def assert_match(expected_pattern, testsrc, message = '', **argh)
add_assertion testsrc, -> as do
as.assert_check(message, **argh) {|result|
if expected_pattern =~ result
nil
else
desc = "#{expected_pattern.inspect} expected to be =~\n#{result.inspect}"
pretty(testsrc, desc, result)
end
}
end
end
def assert_not_match(unexpected_pattern, testsrc, message = '')
add_assertion testsrc, -> as do
as.assert_check(message) {|result|
if unexpected_pattern !~ result
nil
else
desc = "#{unexpected_pattern.inspect} expected to be !~\n#{result.inspect}"
pretty(testsrc, desc, result)
end
}
end
end
def assert_valid_syntax(testsrc, message = '')
add_assertion testsrc, -> as do
as.assert_check(message, '-c') {|result|
result if /Syntax OK/ !~ result
}
end
end
def assert_normal_exit(testsrc, *rest, timeout: BT.timeout, **opt)
add_assertion testsrc, -> as do
timeout = BT.apply_timeout_scale(timeout)
message, ignore_signals = rest
message ||= ''
as.show_progress(message) {
faildesc = nil
filename = as.make_srcfile
timeout_signaled = false
logfile = "assert_normal_exit.#{as.path}.#{as.lineno}.log"
io = IO.popen("#{BT.ruby} -W0 #{filename}", err: logfile)
pid = io.pid
th = Thread.new {
io.read
io.close
$?
}
if !th.join(timeout)
Process.kill :KILL, pid
timeout_signaled = true
end
status = th.value
if status && status.signaled?
signo = status.termsig
signame = Signal.list.invert[signo]
unless ignore_signals and ignore_signals.include?(signame)
sigdesc = "signal #{signo}"
if signame
sigdesc = "SIG#{signame} (#{sigdesc})"
end
if timeout_signaled
sigdesc << " (timeout)"
end
faildesc = pretty(testsrc, "killed by #{sigdesc}", nil)
stderr_log = File.read(logfile)
if !stderr_log.empty?
faildesc << "\n" if /\n\z/ !~ faildesc
stderr_log << "\n" if /\n\z/ !~ stderr_log
stderr_log.gsub!(/^.*\n/) { '| ' + $& }
faildesc << stderr_log
end
end
end
faildesc
}
end
end
def assert_finish(timeout_seconds, testsrc, message = '')
add_assertion testsrc, -> as do
timeout_seconds = BT.apply_timeout_scale(timeout_seconds)
as.show_progress(message) {
faildesc = nil
filename = as.make_srcfile
io = IO.popen("#{BT.ruby} -W0 #{filename}", err: as.err)
pid = io.pid
waited = false
tlimit = Time.now + timeout_seconds
diff = timeout_seconds
while diff > 0
if Process.waitpid pid, Process::WNOHANG
waited = true
break
end
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
else
sleep 0.1
end
diff = tlimit - Time.now
end
if !waited
Process.kill(:KILL, pid)
Process.waitpid pid
faildesc = pretty(testsrc, "not finished in #{timeout_seconds} seconds", nil)
end
io.close
faildesc
}
end
end
def flunk(message = '')
add_assertion '', -> as do
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
def pretty(src, desc, result)
src = src.sub(/\A\s*\n/, '')
lines = src.lines
src = lines[0..20].join + "(...snip)\n" if lines.size > 20
(/\n/ =~ src ? "\n#{adjust_indent(src)}" : src) + " #=> #{desc}"
end
INDENT = 27
def adjust_indent(src)
untabify(src).gsub(/^ {#{INDENT}}/o, '').gsub(/^/, ' ').sub(/\s*\z/, "\n")
end
def untabify(str)
str.gsub(/^\t+/) {' ' * (8 * $&.size) }
end
def in_temporary_working_directory(dir)
if dir
Dir.mkdir dir
Dir.chdir(dir) {
yield
}
else
Dir.mktmpdir(["bootstraptest", ".tmpwd"]) {|d|
Dir.chdir(d) {
yield
}
}
end
end
def cleanup_coredump
if File.file?('core')
require 'time'
Dir.glob('/tmp/bootstraptest-core.*').each do |f|
if Time.now - File.mtime(f) > 7 * 24 * 60 * 60 # 7 days
warn "Deleting an old core file: #{f}"
FileUtils.rm(f)
end
end
core_path = "/tmp/bootstraptest-core.#{Time.now.utc.iso8601}"
warn "A core file is found. Saving it at: #{core_path.dump}"
FileUtils.mv('core', core_path)
cmd = ['gdb', BT.ruby, '-c', core_path, '-ex', 'bt', '-batch']
p cmd # debugging why it's not working
system(*cmd)
end
FileUtils.rm_f Dir.glob('core.*')
FileUtils.rm_f BT.ruby+'.stackdump' if BT.ruby
end
class CoreDumpError < StandardError; end
def check_coredump
if File.file?('core') or not Dir.glob('core.*').empty? or
(BT.ruby and File.exist?(BT.ruby+'.stackdump'))
raise CoreDumpError, "core dumped"
end
end
def yjit_enabled?
ENV.key?('RUBY_YJIT_ENABLE') || ENV.fetch('RUN_OPTS', '').include?('yjit') || BT.ruby.include?('yjit')
end
def rjit_enabled?
# Don't check `RubyVM::RJIT.enabled?`. On btest-bruby, target Ruby != runner Ruby.
ENV.fetch('RUN_OPTS', '').include?('rjit')
end
exit main
|