summaryrefslogtreecommitdiff
path: root/bootstraptest
diff options
context:
space:
mode:
Diffstat (limited to 'bootstraptest')
-rwxr-xr-xbootstraptest/runner.rb842
-rw-r--r--bootstraptest/test_attr.rb16
-rw-r--r--bootstraptest/test_autoload.rb30
-rw-r--r--bootstraptest/test_constant_cache.rb187
-rw-r--r--bootstraptest/test_eval.rb72
-rw-r--r--bootstraptest/test_exception.rb2
-rw-r--r--bootstraptest/test_finalizer.rb8
-rw-r--r--bootstraptest/test_flow.rb2
-rw-r--r--bootstraptest/test_gc.rb2
-rw-r--r--bootstraptest/test_insns.rb21
-rw-r--r--bootstraptest/test_io.rb5
-rw-r--r--bootstraptest/test_jump.rb6
-rw-r--r--bootstraptest/test_literal.rb8
-rw-r--r--bootstraptest/test_literal_suffix.rb12
-rw-r--r--bootstraptest/test_load.rb2
-rw-r--r--bootstraptest/test_method.rb34
-rw-r--r--bootstraptest/test_ractor.rb418
-rw-r--r--bootstraptest/test_rjit.rb58
-rw-r--r--bootstraptest/test_syntax.rb52
-rw-r--r--bootstraptest/test_thread.rb26
-rw-r--r--bootstraptest/test_yjit.rb2581
-rw-r--r--bootstraptest/test_yjit_rust_port.rb422
22 files changed, 4364 insertions, 442 deletions
diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb
index 11d5a6c17e..20f121cdf4 100755
--- a/bootstraptest/runner.rb
+++ b/bootstraptest/runner.rb
@@ -6,7 +6,8 @@
# Never use optparse in this file.
# Never use test/unit in this file.
# Never use Ruby extensions in this file.
-# Maintain Ruby 1.8 compatibility for now
+
+$start_time = Time.now
begin
require 'fileutils'
@@ -58,24 +59,114 @@ if !Dir.respond_to?(:mktmpdir)
end
end
+# Configuration
+bt = Struct.new(:ruby,
+ :verbose,
+ :color,
+ :tty,
+ :quiet,
+ :wn,
+ :progress,
+ :progress_bs,
+ :passed,
+ :failed,
+ :reset,
+ :columns,
+ :window_width,
+ :width,
+ :indent,
+ :platform,
+ )
+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
+end.new
+
+BT_STATE = Struct.new(:count, :error).new
+
def main
- @ruby = File.expand_path('miniruby')
- @verbose = false
+ BT.ruby = File.expand_path('miniruby')
+ BT.verbose = false
$VERBOSE = false
$stress = false
- @color = nil
- @tty = nil
- @quiet = false
+ BT.color = nil
+ BT.tty = nil
+ BT.quiet = false
+ # 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)}
+ 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
@@ -88,18 +179,23 @@ def main
$stress = true
when /\A--color(?:=(?:always|(auto)|(never)|(.*)))?\z/
warn "unknown --color argument: #$3" if $3
- @color = $1 ? nil : !$2
+ BT.color = color = $1 ? nil : !$2
true
when /\A--tty(=(?:yes|(no)|(.*)))?\z/
warn "unknown --tty argument: #$3" if $3
- @tty = !$1 || !$2
+ BT.tty = !$1 || !$2
true
when /\A(-q|--q(uiet))\z/
quiet = true
- @quiet = true
+ BT.quiet = true
+ true
+ when /\A-j(\d+)?/
+ BT.wn = $1.to_i
true
when /\A(-v|--v(erbose))\z/
- @verbose = true
+ BT.verbose = true
+ BT.quiet = false
+ true
when /\A(-h|--h(elp)?)\z/
puts(<<-End)
Usage: #{File.basename($0, '.*')} --ruby=PATH [--sets=NAME,NAME,...]
@@ -121,22 +217,23 @@ End
end
}
if tests and not ARGV.empty?
- $stderr.puts "--tests and arguments are exclusive"
- exit false
+ abort "--sets and arguments are exclusive"
end
tests ||= ARGV
tests = Dir.glob("#{File.dirname($0)}/test_*.rb").sort if tests.empty?
pathes = tests.map {|path| File.expand_path(path) }
- @progress = %w[- \\ | /]
- @progress_bs = "\b" * @progress[0].size
- @tty = $stderr.tty? if @tty.nil?
- case @color
+ 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
- @color = @tty && /dumb/ !~ ENV["TERM"]
+ BT.color = BT.tty && /dumb/ !~ ENV["TERM"]
end
- @tty &&= !@verbose
- if @color
+ BT.tty &&= !BT.verbose
+ if BT.color
# dircolors-like style
colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:\n]*)/)] : {}
begin
@@ -145,14 +242,16 @@ End
end
rescue
end
- @passed = "\e[;#{colors["pass"] || "32"}m"
- @failed = "\e[;#{colors["fail"] || "31"}m"
- @reset = "\e[m"
+ BT.passed = "\e[;#{colors["pass"] || "32"}m"
+ BT.failed = "\e[;#{colors["fail"] || "31"}m"
+ BT.reset = "\e[m"
else
- @passed = @failed = @reset = ""
+ BT.passed = BT.failed = BT.reset = ""
end
+ target_version = `#{BT.ruby} -v`.chomp
+ BT.platform = target_version[/\[(.*)\]\z/, 1]
unless quiet
- puts Time.now
+ puts $start_time
if defined?(RUBY_DESCRIPTION)
puts "Driver is #{RUBY_DESCRIPTION}"
elsif defined?(RUBY_PATCHLEVEL)
@@ -160,272 +259,474 @@ End
else
puts "Driver is ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
end
- puts "Target is #{`#{@ruby} -v`.chomp}"
+ puts "Target is #{target_version}"
puts
$stdout.flush
end
- in_temporary_working_directory(dir) {
+ in_temporary_working_directory(dir) do
exec_test pathes
- }
+ end
end
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"
else
""
end
end
-def exec_test(pathes)
- @count = 0
- @error = 0
- @errbuf = []
- @location = nil
- @columns = 0
- @width = pathes.map {|path| File.basename(path).size}.max + 2
+def load_test pathes
pathes.each do |path|
- @basename = File.basename(path)
- $stderr.printf("%s%-*s ", erase(@quiet), @width, @basename)
- $stderr.flush
- @columns = @width + 1
- $stderr.puts if @verbose
- count = @count
- error = @error
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}"
+ 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
+ case
+ when BT.quiet
+ when BT.tty
+ $stderr.print "#{BT.progress_bs}#{BT.progress[(i+=1) % BT.progress.size]}"
+ else
+ BT.putc '.'
+ end
else
- msg = "FAIL #{@error-error}/#{@count-count}"
- $stderr.print "#{@progress_bs}#{@failed}#{msg}#{@reset}"
- @columns = 0
+ term_wn += 1
end
end
- $stderr.puts unless @quiet and @tty and @error == error
+ ensure
+ ts.each(&:kill)
+ ts.each(&:join)
end
- $stderr.print(erase) if @quiet
- @errbuf.each do |msg|
+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
+ 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
- if @error == 0
- if @count == 0
- $stderr.puts "No tests, no problem"
+
+ out = BT.quiet ? $stdout : $stderr
+
+ if BT_STATE.error == 0
+ if Assertion.count == 0
+ out.puts "No tests, no problem" unless BT.quiet
else
- $stderr.puts "#{@passed}PASS#{@reset} all #{@count} tests"
+ out.puts "#{BT.passed}PASS#{BT.reset} all #{Assertion.count} tests"
end
- exit true
+ true
else
- $stderr.puts "#{@failed}FAIL#{@reset} #{@error}/#{@count} tests failed"
- exit false
+ $stderr.puts "#{BT.failed}FAIL#{BT.reset} #{BT_STATE.error}/#{BT_STATE.count} tests failed"
+ false
end
end
-def show_progress(message = '')
- if @verbose
- $stderr.print "\##{@count} #{@location} "
- elsif @tty
- $stderr.print "#{@progress_bs}#{@progress[@count % @progress.size]}"
- end
- t = Time.now if @verbose
- faildesc, errout = with_stderr {yield}
- t = Time.now - t if @verbose
- if !faildesc
- if @tty
- $stderr.print "#{@progress_bs}#{@progress[@count % @progress.size]}"
- elsif @verbose
- $stderr.printf(". %.3f\n", t)
- else
- $stderr.print '.'
+def target_platform
+ BT.platform or RUBY_PLATFORM
+end
+
+class Assertion < Struct.new(:src, :path, :lineno, :proc)
+ @count = 0
+ @all = Hash.new{|h, k| h[k] = []}
+ @errbuf = []
+
+ class << self
+ attr_reader :count, :errbuf
+
+ def all
+ @all
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)
+
+ def add as
+ @all[as.path] << as
+ as.id = (@count += 1)
end
- if @tty and !@verbose
- $stderr.printf("%-*s%s", @width, @basename, @progress[@count % @progress.size])
+ 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
-rescue Interrupt
- $stderr.puts "\##{@count} #{@location}"
- raise
-rescue Exception => err
- $stderr.print 'E'
- $stderr.puts if @verbose
- error err.message, message
-ensure
- begin
- check_coredump
- rescue CoreDumpError => err
+
+ 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
+ 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
+ rescue Interrupt
+ $stderr.puts "\##{@id} #{path}:#{lineno}"
+ raise
+ rescue Exception => err
$stderr.print 'E'
- $stderr.puts if @verbose
- error err.message, message
+ $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
-end
-def show_limit(testsrc, opt = '', **argh)
- result = get_result_string(testsrc, opt, **argh)
- if @tty and @verbose
- $stderr.puts ".{#@reset}\n#{erase}#{result}"
- else
- @errbuf.push result
+ 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:#{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 assert_check(testsrc, message = '', opt = '', **argh)
- show_progress(message) {
- result = get_result_string(testsrc, opt, **argh)
- yield(result)
- }
+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 = '', **argh)
- newtest
- assert_check(testsrc, message, opt, **argh) {|result|
- if expected == result
- nil
- else
- desc = "#{result.inspect} (expected #{expected.inspect})"
- pretty(testsrc, desc, result)
- 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 = '')
- newtest
- assert_check(testsrc, message) {|result|
- if expected_pattern =~ result
- nil
- else
- desc = "#{expected_pattern.inspect} expected to be =~\n#{result.inspect}"
- pretty(testsrc, desc, result)
- 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 = '')
- newtest
- assert_check(testsrc, message) {|result|
- if unexpected_pattern !~ result
- nil
- else
- desc = "#{unexpected_pattern.inspect} expected to be !~\n#{result.inspect}"
- pretty(testsrc, desc, result)
- end
- }
+ 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 = '')
- newtest
- assert_check(testsrc, message, '-c') {|result|
- result if /Syntax OK/ !~ result
- }
+ 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: nil, **opt)
- newtest
- message, ignore_signals = rest
- message ||= ''
- show_progress(message) {
- faildesc = nil
- filename = make_srcfile(testsrc)
- old_stderr = $stderr.dup
- timeout_signaled = false
- begin
- $stderr.reopen("assert_normal_exit.log", "w")
- io = IO.popen("#{@ruby} -W0 #{filename}")
- 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
- ensure
- $stderr.reopen(old_stderr)
- old_stderr.close
- end
- 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)"
+ add_assertion testsrc, -> as do
+ 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"
+
+ begin
+ err = open(logfile, "w")
+ io = IO.popen("#{BT.ruby} -W0 #{filename}", err: err)
+ pid = io.pid
+ th = Thread.new {
+ io.read
+ io.close
+ $?
+ }
+ if !th.join(timeout)
+ Process.kill :KILL, pid
+ timeout_signaled = true
end
- faildesc = pretty(testsrc, "killed by #{sigdesc}", nil)
- stderr_log = File.read("assert_normal_exit.log")
- 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
+ status = th.value
+ ensure
+ err.close
+ end
+ 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
- end
- faildesc
- }
+ faildesc
+ }
+ end
end
def assert_finish(timeout_seconds, testsrc, message = '')
- if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? # for --jit-wait
- timeout_seconds *= 3
- end
- newtest
- show_progress(message) {
- faildesc = nil
- filename = make_srcfile(testsrc)
- io = IO.popen("#{@ruby} -W0 #{filename}")
- 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
+ add_assertion testsrc, -> as do
+ if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # for --jit-wait
+ timeout_seconds *= 3
+ end
+
+ 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
- else
- sleep 0.1
+ 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
- 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
- }
+ 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 = '')
- newtest
- show_progress('') { 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)
@@ -443,66 +744,6 @@ def untabify(str)
str.gsub(/^\t+/) {' ' * (8 * $&.size) }
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)
if dir
Dir.mkdir dir
@@ -530,21 +771,30 @@ def cleanup_coredump
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', @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
system(*cmd)
end
FileUtils.rm_f Dir.glob('core.*')
- FileUtils.rm_f @ruby+'.stackdump' if @ruby
+ 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
- (@ruby and File.exist?(@ruby+'.stackdump'))
+ (BT.ruby and File.exist?(BT.ruby+'.stackdump'))
raise CoreDumpError, "core dumped"
end
end
-main
+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
diff --git a/bootstraptest/test_attr.rb b/bootstraptest/test_attr.rb
index 721a847145..3cb9d3eb39 100644
--- a/bootstraptest/test_attr.rb
+++ b/bootstraptest/test_attr.rb
@@ -34,3 +34,19 @@ assert_equal %{ok}, %{
print "ok"
end
}, '[ruby-core:15120]'
+
+assert_equal %{ok}, %{
+ class Big
+ attr_reader :foo
+ def initialize
+ @foo = "ok"
+ end
+ end
+
+ obj = Big.new
+ 100.times do |i|
+ obj.instance_variable_set(:"@ivar_\#{i}", i)
+ end
+
+ Big.new.foo
+}
diff --git a/bootstraptest/test_autoload.rb b/bootstraptest/test_autoload.rb
index a9f8e6dacd..9e0850bc52 100644
--- a/bootstraptest/test_autoload.rb
+++ b/bootstraptest/test_autoload.rb
@@ -1,7 +1,7 @@
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
- autoload :ZZZ, './zzz.rb'
+ autoload :ZZZ, './zzz1.rb'
begin
ZZZ
rescue LoadError
@@ -11,9 +11,9 @@ assert_equal 'ok', %q{
}, '[ruby-dev:43816]'
assert_equal 'ok', %q{
- open('zzz.rb', 'w') {|f| f.puts '' }
+ open('zzz2.rb', 'w') {|f| f.puts '' }
instance_eval do
- autoload :ZZZ, './zzz.rb'
+ autoload :ZZZ, './zzz2.rb'
begin
ZZZ
rescue NameError
@@ -23,29 +23,29 @@ assert_equal 'ok', %q{
}, '[ruby-dev:43816]'
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
- autoload :ZZZ, './zzz.rb'
+ autoload :ZZZ, './zzz3.rb'
ZZZ.ok
end
}, '[ruby-dev:43816]'
assert_equal 'ok', %q{
- open("zzz.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"}
- autoload :ZZZ, "./zzz.rb"
+ open("zzz4.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"}
+ autoload :ZZZ, "./zzz4.rb"
ZZZ.ok
}
assert_equal 'ok', %q{
- open("zzz.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"}
- autoload :ZZZ, "./zzz.rb"
- require "./zzz.rb"
+ open("zzz5.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"}
+ autoload :ZZZ, "./zzz5.rb"
+ require "./zzz5.rb"
ZZZ.ok
}
assert_equal 'okok', %q{
- open("zzz.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"}
- autoload :ZZZ, "./zzz.rb"
+ open("zzz6.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"}
+ autoload :ZZZ, "./zzz6.rb"
t1 = Thread.new {ZZZ.ok}
t2 = Thread.new {ZZZ.ok}
[t1.value, t2.value].join
@@ -60,9 +60,9 @@ assert_finish 5, %q{
}, '[ruby-core:21696]'
assert_equal 'A::C', %q{
- open("zzz.rb", "w") {}
+ open("zzz7.rb", "w") {}
class A
- autoload :C, "./zzz"
+ autoload :C, "./zzz7"
class C
end
C
diff --git a/bootstraptest/test_constant_cache.rb b/bootstraptest/test_constant_cache.rb
new file mode 100644
index 0000000000..1fa83256ed
--- /dev/null
+++ b/bootstraptest/test_constant_cache.rb
@@ -0,0 +1,187 @@
+# Constant lookup is cached.
+assert_equal '1', %q{
+ CONST = 1
+
+ def const
+ CONST
+ end
+
+ const
+ const
+}
+
+# Invalidate when a constant is set.
+assert_equal '2', %q{
+ CONST = 1
+
+ def const
+ CONST
+ end
+
+ const
+
+ CONST = 2
+
+ const
+}
+
+# Invalidate when a constant of the same name is set.
+assert_equal '1', %q{
+ CONST = 1
+
+ def const
+ CONST
+ end
+
+ const
+
+ class Container
+ CONST = 2
+ end
+
+ const
+}
+
+# Invalidate when a constant is removed.
+assert_equal 'missing', %q{
+ class Container
+ CONST = 1
+
+ def const
+ CONST
+ end
+
+ def self.const_missing(name)
+ 'missing'
+ end
+
+ new.const
+ remove_const :CONST
+ end
+
+ Container.new.const
+}
+
+# Invalidate when a constant's visibility changes.
+assert_equal 'missing', %q{
+ class Container
+ CONST = 1
+
+ def self.const_missing(name)
+ 'missing'
+ end
+ end
+
+ def const
+ Container::CONST
+ end
+
+ const
+
+ Container.private_constant :CONST
+
+ const
+}
+
+# Invalidate when a constant's visibility changes even if the call to the
+# visibility change method fails.
+assert_equal 'missing', %q{
+ class Container
+ CONST1 = 1
+
+ def self.const_missing(name)
+ 'missing'
+ end
+ end
+
+ def const1
+ Container::CONST1
+ end
+
+ const1
+
+ begin
+ Container.private_constant :CONST1, :CONST2
+ rescue NameError
+ end
+
+ const1
+}
+
+# Invalidate when a module is included.
+assert_equal 'INCLUDE', %q{
+ module Include
+ CONST = :INCLUDE
+ end
+
+ class Parent
+ CONST = :PARENT
+ end
+
+ class Child < Parent
+ def const
+ CONST
+ end
+
+ new.const
+
+ include Include
+ end
+
+ Child.new.const
+}
+
+# Invalidate when const_missing is hit.
+assert_equal '2', %q{
+ module Container
+ Foo = 1
+ Bar = 2
+
+ class << self
+ attr_accessor :count
+
+ def const_missing(name)
+ @count += 1
+ @count == 1 ? Foo : Bar
+ end
+ end
+
+ @count = 0
+ end
+
+ def const
+ Container::Baz
+ end
+
+ const
+ const
+}
+
+# Invalidate when the iseq gets cleaned up.
+assert_equal '2', %q{
+ CONSTANT = 1
+
+ iseq = RubyVM::InstructionSequence.compile(<<~RUBY)
+ CONSTANT
+ RUBY
+
+ iseq.eval
+ iseq = nil
+
+ GC.start
+ CONSTANT = 2
+}
+
+# Invalidate when the iseq gets cleaned up even if it was never in the cache.
+assert_equal '2', %q{
+ CONSTANT = 1
+
+ iseq = RubyVM::InstructionSequence.compile(<<~RUBY)
+ CONSTANT
+ RUBY
+
+ iseq = nil
+
+ GC.start
+ CONSTANT = 2
+}
diff --git a/bootstraptest/test_eval.rb b/bootstraptest/test_eval.rb
index 5d2593c306..d923a957bc 100644
--- a/bootstraptest/test_eval.rb
+++ b/bootstraptest/test_eval.rb
@@ -116,6 +116,33 @@ assert_equal %q{1}, %q{
Const
}
}
+assert_equal %q{1}, %q{
+ class TrueClass
+ Const = 1
+ end
+ true.instance_eval %{
+ Const
+ }
+}
+assert_equal %q{[:Const]}, %q{
+ mod = Module.new
+ mod.instance_eval %{
+ Const = 1
+ }
+ raise if defined?(Module::Const)
+ mod.singleton_class.constants
+}
+assert_equal %q{can't define singleton}, %q{
+ begin
+ 123.instance_eval %{
+ Const = 1
+ }
+ "bad"
+ rescue TypeError => e
+ raise "bad" if defined?(Integer::Const)
+ e.message
+ end
+}
assert_equal %q{top}, %q{
Const = :top
class C
@@ -191,7 +218,7 @@ assert_equal %q{[10, main]}, %q{
%w[break next redo].each do |keyword|
assert_match %r"Can't escape from eval with #{keyword}\b", %{
- STDERR.reopen(STDOUT)
+ $stderr = STDOUT
begin
eval "0 rescue #{keyword}"
rescue SyntaxError => e
@@ -200,8 +227,18 @@ assert_equal %q{[10, main]}, %q{
}, '[ruby-dev:31372]'
end
+assert_normal_exit %{
+ $stderr = STDOUT
+ 5000.times do
+ begin
+ eval "0 rescue break"
+ rescue SyntaxError
+ end
+ end
+}
+
assert_normal_exit %q{
- STDERR.reopen(STDOUT)
+ $stderr = STDOUT
class Foo
def self.add_method
class_eval("def some-bad-name; puts 'hello' unless @some_variable.some_function(''); end")
@@ -327,3 +364,34 @@ assert_normal_exit %q{
end
}, 'check escaping the internal value th->base_block'
+assert_equal "false", <<~RUBY, "literal strings are mutable", "--disable-frozen-string-literal"
+ eval("'test'").frozen?
+RUBY
+
+assert_equal "false", <<~RUBY, "literal strings are mutable", "--disable-frozen-string-literal", frozen_string_literal: true
+ eval("'test'").frozen?
+RUBY
+
+assert_equal "true", <<~RUBY, "literal strings are frozen", "--enable-frozen-string-literal"
+ eval("'test'").frozen?
+RUBY
+
+assert_equal "true", <<~RUBY, "literal strings are frozen", "--enable-frozen-string-literal", frozen_string_literal: false
+ eval("'test'").frozen?
+RUBY
+
+assert_equal "false", <<~RUBY, "__FILE__ is mutable", "--disable-frozen-string-literal"
+ eval("__FILE__").frozen?
+RUBY
+
+assert_equal "false", <<~RUBY, "__FILE__ is mutable", "--disable-frozen-string-literal", frozen_string_literal: true
+ eval("__FILE__").frozen?
+RUBY
+
+assert_equal "true", <<~RUBY, "__FILE__ is frozen", "--enable-frozen-string-literal"
+ eval("__FILE__").frozen?
+RUBY
+
+assert_equal "true", <<~RUBY, "__FILE__ is frozen", "--enable-frozen-string-literal", frozen_string_literal: false
+ eval("__FILE__").frozen?
+RUBY
diff --git a/bootstraptest/test_exception.rb b/bootstraptest/test_exception.rb
index 0fb6f552b8..decfdc08a3 100644
--- a/bootstraptest/test_exception.rb
+++ b/bootstraptest/test_exception.rb
@@ -370,7 +370,7 @@ assert_equal %q{}, %q{
}
##
-assert_match /undefined method `foo\'/, %q{#`
+assert_match /undefined method 'foo\'/, %q{#`
STDERR.reopen(STDOUT)
class C
def inspect
diff --git a/bootstraptest/test_finalizer.rb b/bootstraptest/test_finalizer.rb
index 22a16b1220..ccfa0b55d6 100644
--- a/bootstraptest/test_finalizer.rb
+++ b/bootstraptest/test_finalizer.rb
@@ -6,3 +6,11 @@ ObjectSpace.define_finalizer(b1,proc{b1.inspect})
ObjectSpace.define_finalizer(a2,proc{a1.inspect})
ObjectSpace.define_finalizer(a1,proc{})
}, '[ruby-dev:35778]'
+
+assert_equal 'true', %q{
+ obj = Object.new
+ id = obj.object_id
+
+ ObjectSpace.define_finalizer(obj, proc { |i| print(id == i) })
+ nil
+}
diff --git a/bootstraptest/test_flow.rb b/bootstraptest/test_flow.rb
index 35f19db588..15528a4213 100644
--- a/bootstraptest/test_flow.rb
+++ b/bootstraptest/test_flow.rb
@@ -363,7 +363,7 @@ assert_equal %q{[1, 2, 3, 5, 2, 3, 5, 7, 8]}, %q{$a = []; begin; ; $a << 1
; $a << 8
; rescue Exception; $a << 99; end; $a}
assert_equal %q{[1, 2, 6, 3, 5, 7, 8]}, %q{$a = []; begin; ; $a << 1
- o = "test"; $a << 2
+ o = "test".dup; $a << 2
def o.test(a); $a << 3
return a; $a << 4
ensure; $a << 5
diff --git a/bootstraptest/test_gc.rb b/bootstraptest/test_gc.rb
index eb68c9845e..17bc497822 100644
--- a/bootstraptest/test_gc.rb
+++ b/bootstraptest/test_gc.rb
@@ -14,7 +14,7 @@ ms = "a".."k"
o.send(meth)
end
end
-}, '[ruby-dev:39453]'
+}, '[ruby-dev:39453]' unless rjit_enabled? # speed up RJIT CI
assert_normal_exit %q{
a = []
diff --git a/bootstraptest/test_insns.rb b/bootstraptest/test_insns.rb
index 31fdc29d02..06828a7f7a 100644
--- a/bootstraptest/test_insns.rb
+++ b/bootstraptest/test_insns.rb
@@ -92,7 +92,7 @@ tests = [
[ 'intern', %q{ :"#{true}" }, ],
[ 'newarray', %q{ ["true"][0] }, ],
- [ 'newarraykwsplat', %q{ [**{x:'true'}][0][:x] }, ],
+ [ 'pushtoarraykwsplat', %q{ [**{x:'true'}][0][:x] }, ],
[ 'duparray', %q{ [ true ][0] }, ],
[ 'expandarray', %q{ y = [ true, false, nil ]; x, = y; x }, ],
[ 'expandarray', %q{ y = [ true, false, nil ]; x, *z = y; x }, ],
@@ -120,6 +120,7 @@ tests = [
[ 'dup', %q{ x = y = true; x }, ],
[ 'dupn', %q{ Object::X ||= true }, ],
[ 'reverse', %q{ q, (w, e), r = 1, [2, 3], 4; e == 3 }, ],
+ [ 'swap', %q{ !!defined?([[]]) }, ],
[ 'swap', <<-'},', ], # {
x = [[false, true]]
for i, j in x # here
@@ -213,9 +214,11 @@ tests = [
'true'.freeze
},
- [ 'opt_newarray_max', %q{ [ ].max.nil? }, ],
- [ 'opt_newarray_max', %q{ [1, x = 2, 3].max == 3 }, ],
- [ 'opt_newarray_max', <<-'},', ], # {
+ [ 'opt_newarray_send', %q{ ![ ].hash.nil? }, ],
+
+ [ 'opt_newarray_send', %q{ [ ].max.nil? }, ],
+ [ 'opt_newarray_send', %q{ [1, x = 2, 3].max == 3 }, ],
+ [ 'opt_newarray_send', <<-'},', ], # {
class Array
def max
true
@@ -223,9 +226,9 @@ tests = [
end
[1, x = 2, 3].max
},
- [ 'opt_newarray_min', %q{ [ ].min.nil? }, ],
- [ 'opt_newarray_min', %q{ [3, x = 2, 1].min == 1 }, ],
- [ 'opt_newarray_min', <<-'},', ], # {
+ [ 'opt_newarray_send', %q{ [ ].min.nil? }, ],
+ [ 'opt_newarray_send', %q{ [3, x = 2, 1].min == 1 }, ],
+ [ 'opt_newarray_send', <<-'},', ], # {
class Array
def min
true
@@ -351,7 +354,7 @@ tests = [
[ 'opt_ge', %q{ +0.0.next_float >= 0.0 }, ],
[ 'opt_ge', %q{ ?z >= ?a }, ],
- [ 'opt_ltlt', %q{ '' << 'true' }, ],
+ [ 'opt_ltlt', %q{ +'' << 'true' }, ],
[ 'opt_ltlt', %q{ ([] << 'true').join }, ],
[ 'opt_ltlt', %q{ (1 << 31) == 2147483648 }, ],
@@ -360,7 +363,7 @@ tests = [
[ 'opt_aref', %q{ 'true'[0] == ?t }, ],
[ 'opt_aset', %q{ [][0] = true }, ],
[ 'opt_aset', %q{ {}[0] = true }, ],
- [ 'opt_aset', %q{ x = 'frue'; x[0] = 't'; x }, ],
+ [ 'opt_aset', %q{ x = +'frue'; x[0] = 't'; x }, ],
[ 'opt_aset', <<-'},', ], # {
# opt_aref / opt_aset mixup situation
class X; def x; {}; end; end
diff --git a/bootstraptest/test_io.rb b/bootstraptest/test_io.rb
index 89c00d0b88..666e5a011b 100644
--- a/bootstraptest/test_io.rb
+++ b/bootstraptest/test_io.rb
@@ -1,3 +1,4 @@
+/freebsd/ =~ RUBY_PLATFORM or
assert_finish 5, %q{
r, w = IO.pipe
t1 = Thread.new { r.sysread(1) }
@@ -30,7 +31,8 @@ assert_finish 10, %q{
end
}, '[ruby-dev:32566]'
-assert_finish 1, %q{
+/freebsd/ =~ RUBY_PLATFORM or
+assert_finish 5, %q{
r, w = IO.pipe
Thread.new {
w << "ab"
@@ -83,6 +85,7 @@ assert_normal_exit %q{
ARGF.set_encoding "foo"
}
+/freebsd/ =~ RUBY_PLATFORM or
10.times do
assert_normal_exit %q{
at_exit { p :foo }
diff --git a/bootstraptest/test_jump.rb b/bootstraptest/test_jump.rb
index 18a2737ea3..8751343b1f 100644
--- a/bootstraptest/test_jump.rb
+++ b/bootstraptest/test_jump.rb
@@ -147,7 +147,7 @@ assert_equal %q{131}, %q{
}
}
assert_match %r{Invalid retry}, %q{
-STDERR.reopen(STDOUT)
+$stderr = STDOUT
begin
eval %q{
1.times{
@@ -292,12 +292,12 @@ assert_equal "true", %q{
end
end
end
- s = "foo"
+ s = +"foo"
s.return_eigenclass == class << s; self; end
}, '[ruby-core:21379]'
assert_match %r{Invalid yield}, %q{
-STDERR.reopen(STDOUT)
+$stderr = STDOUT
begin
eval %q{
class Object
diff --git a/bootstraptest/test_literal.rb b/bootstraptest/test_literal.rb
index 9b3c10d519..a30661a796 100644
--- a/bootstraptest/test_literal.rb
+++ b/bootstraptest/test_literal.rb
@@ -65,8 +65,12 @@ assert_equal ':a3c', ':"a#{1+2}c".inspect'
assert_equal 'Symbol', ':"a#{1+2}c".class'
# xstring
-assert_equal "foo\n", %q(`echo foo`)
-assert_equal "foo\n", %q(s = "foo"; `echo #{s}`)
+# WASI doesn't support spawning a new process for now.
+if /wasi/ !~ target_platform
+ assert_equal "foo\n", %q(`echo foo`)
+ assert_equal "foo\n", %q(s = "foo"; `echo #{s}`)
+end
+assert_equal "ECHO FOO", %q(def `(s) s.upcase; end; `echo foo`)
# regexp
assert_equal '', '//.source'
diff --git a/bootstraptest/test_literal_suffix.rb b/bootstraptest/test_literal_suffix.rb
index c36fa7078f..7a4d67d0fa 100644
--- a/bootstraptest/test_literal_suffix.rb
+++ b/bootstraptest/test_literal_suffix.rb
@@ -46,9 +46,9 @@ assert_equal '1', '1rescue nil'
assert_equal '10000000000000000001/10000000000000000000',
'1.0000000000000000001r'
-assert_equal 'syntax error, unexpected local variable or method, expecting end-of-input',
- %q{begin eval('1ir', nil, '', 0); rescue SyntaxError => e; e.message[/\A:(?:\d+:)? (.*)/, 1] end}
-assert_equal 'syntax error, unexpected local variable or method, expecting end-of-input',
- %q{begin eval('1.2ir', nil, '', 0); rescue SyntaxError => e; e.message[/\A:(?:\d+:)? (.*)/, 1] end}
-assert_equal 'syntax error, unexpected local variable or method, expecting end-of-input',
- %q{begin eval('1e1r', nil, '', 0); rescue SyntaxError => e; e.message[/\A:(?:\d+:)? (.*)/, 1] end}
+assert_equal 'unexpected local variable or method, expecting end-of-input',
+ %q{begin eval('1ir', nil, '', 0); rescue SyntaxError => e; e.message[/(?:\^~*|\A:(?:\d+:)? syntax error,) (.*)/, 1]; end}
+assert_equal 'unexpected local variable or method, expecting end-of-input',
+ %q{begin eval('1.2ir', nil, '', 0); rescue SyntaxError => e; e.message[/(?:\^~*|\A:(?:\d+:)? syntax error,) (.*)/, 1]; end}
+assert_equal 'unexpected local variable or method, expecting end-of-input',
+ %q{begin eval('1e1r', nil, '', 0); rescue SyntaxError => e; e.message[/(?:\^~*|\A:(?:\d+:)? syntax error,) (.*)/, 1]; end}
diff --git a/bootstraptest/test_load.rb b/bootstraptest/test_load.rb
index e63c93a8f4..3253582a32 100644
--- a/bootstraptest/test_load.rb
+++ b/bootstraptest/test_load.rb
@@ -12,7 +12,7 @@ assert_equal 'ok', %q{
}
}.map {|t| t.value }
vs[0] == M && vs[1] == M ? :ok : :ng
-}, '[ruby-dev:32048]'
+}, '[ruby-dev:32048]' unless ENV.fetch('RUN_OPTS', '').include?('rjit') # Thread seems to be switching during JIT. To be fixed later.
assert_equal 'ok', %q{
%w[a a/foo b].each {|d| Dir.mkdir(d)}
diff --git a/bootstraptest/test_method.rb b/bootstraptest/test_method.rb
index 3462aa9434..d1d1f57d55 100644
--- a/bootstraptest/test_method.rb
+++ b/bootstraptest/test_method.rb
@@ -22,7 +22,7 @@ assert_match /\Awrong number of arguments \(.*\b0\b.* 1\)\z/, %q{
}
# 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(a,x=1) x end; m(7)'
assert_equal '1', 'def m(a,x=7) x end; m(7,1)'
@@ -340,24 +340,6 @@ assert_equal '1', %q( class C; def m() 7 end; private :m end
assert_equal '1', %q( class C; def m() 1 end; private :m end
C.new.send(:m) )
-# with block
-assert_equal '[[:ok1, :foo], [:ok2, :foo, :bar]]',
-%q{
- class C
- def [](a)
- $ary << [yield, a]
- end
- def []=(a, b)
- $ary << [yield, a, b]
- end
- end
-
- $ary = []
- C.new[:foo, &lambda{:ok1}]
- C.new[:foo, &lambda{:ok2}] = :bar
- $ary
-}
-
# with
assert_equal '[:ok1, [:ok2, 11]]', %q{
class C
@@ -404,7 +386,6 @@ $result
# aset and splat
assert_equal '4', %q{class Foo;def []=(a,b,c,d);end;end;Foo.new[1,*a=[2,3]]=4}
-assert_equal '4', %q{class Foo;def []=(a,b,c,d);end;end;def m(&blk)Foo.new[1,*a=[2,3],&blk]=4;end;m{}}
# post test
assert_equal %q{[1, 2, :o1, :o2, [], 3, 4, NilClass, nil, nil]}, %q{
@@ -1107,10 +1088,6 @@ assert_equal 'ok', %q{
'ok'
end
}
-assert_equal 'ok', %q{
- [0][0, &proc{}] += 21
- 'ok'
-}, '[ruby-core:30534]'
# should not cache when splat
assert_equal 'ok', %q{
@@ -1190,3 +1167,12 @@ assert_equal 'DC', %q{
test2 o1, [], block
$result.join
}
+
+assert_equal 'ok', %q{
+ def foo
+ binding
+ ["ok"].first
+ end
+ foo
+ foo
+}, '[Bug #20178]'
diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb
index bf80b94cc7..451b58e793 100644
--- a/bootstraptest/test_ractor.rb
+++ b/bootstraptest/test_ractor.rb
@@ -190,7 +190,7 @@ assert_equal '[:ok, :ok, :ok]', %q{
# Ractor.make_shareable issue for locals in proc [Bug #18023]
assert_equal '[:a, :b, :c, :d, :e]', %q{
v1, v2, v3, v4, v5 = :a, :b, :c, :d, :e
- closure = Proc.new { [v1, v2, v3, v4, v5] }
+ closure = Ractor.current.instance_eval{ Proc.new { [v1, v2, v3, v4, v5] } }
Ractor.make_shareable(closure).call
}
@@ -198,17 +198,30 @@ assert_equal '[:a, :b, :c, :d, :e]', %q{
# Ractor.make_shareable issue for locals in proc [Bug #18023]
assert_equal '[:a, :b, :c, :d, :e, :f, :g]', %q{
a = :a
- closure = -> {
- b, c, d = :b, :c, :d
+ closure = Ractor.current.instance_eval do
-> {
- e, f, g = :e, :f, :g
- -> { [a, b, c, d, e, f, g] }
+ b, c, d = :b, :c, :d
+ -> {
+ e, f, g = :e, :f, :g
+ -> { [a, b, c, d, e, f, g] }
+ }.call
}.call
- }.call
+ end
Ractor.make_shareable(closure).call
}
+# Now autoload in non-main Ractor is not supported
+assert_equal 'ok', %q{
+ autoload :Foo, 'foo.rb'
+ r = Ractor.new do
+ p Foo
+ rescue Ractor::UnsafeError
+ :ok
+ end
+ r.take
+}
+
###
###
# Ractor still has several memory corruption so skip huge number of tests
@@ -270,8 +283,7 @@ assert_equal 30.times.map { 'ok' }.to_s, %q{
30.times.map{|i|
test i
}
-} unless ENV['RUN_OPTS'] =~ /--jit-min-calls=5/ || # This always fails with --jit-wait --jit-min-calls=5
- (ENV.key?('TRAVIS') && ENV['TRAVIS_CPU_ARCH'] == 'arm64') # https://bugs.ruby-lang.org/issues/17878
+} unless (ENV.key?('TRAVIS') && ENV['TRAVIS_CPU_ARCH'] == 'arm64') # https://bugs.ruby-lang.org/issues/17878
# Exception for empty select
assert_match /specify at least one ractor/, %q{
@@ -488,7 +500,7 @@ assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{
rs.delete r
n
}.sort
-}
+} unless /mswin/ =~ RUBY_PLATFORM # randomly hangs on mswin https://github.com/ruby/ruby/actions/runs/3753871445/jobs/6377551069#step:20:131
# Ractor.select also support multiple take, receive and yield
assert_equal '[true, true, true]', %q{
@@ -502,9 +514,9 @@ assert_equal '[true, true, true]', %q{
end
}
received = []
- take = []
+ taken = []
yielded = []
- until rs.empty?
+ until received.size == RN && taken.size == RN && yielded.size == RN
r, v = Ractor.select(CR, *rs, yield_value: 'yield')
case r
when :receive
@@ -512,11 +524,17 @@ assert_equal '[true, true, true]', %q{
when :yield
yielded << v
else
- take << v
+ taken << v
rs.delete r
end
end
- [received.all?('sendyield'), yielded.all?(nil), take.all?('take')]
+ r = [received == ['sendyield'] * RN,
+ yielded == [nil] * RN,
+ taken == ['take'] * RN,
+ ]
+
+ STDERR.puts [received, yielded, taken].inspect
+ r
}
# multiple Ractors can send to one Ractor
@@ -583,7 +601,7 @@ assert_equal '{:ok=>3}', %q{
end
3.times.map{Ractor.receive}.tally
-}
+} unless yjit_enabled? # `[BUG] Bus Error at 0x000000010b7002d0` in jit_exec()
# unshareable object are copied
assert_equal 'false', %q{
@@ -610,7 +628,7 @@ assert_equal "allocator undefined for Thread", %q{
}
# send shareable and unshareable objects
-assert_equal "ok", %q{
+assert_equal "ok", <<~'RUBY', frozen_string_literal: false
echo_ractor = Ractor.new do
loop do
v = Ractor.receive
@@ -677,10 +695,10 @@ assert_equal "ok", %q{
else
results.inspect
end
-}
+RUBY
# frozen Objects are shareable
-assert_equal [false, true, false].inspect, %q{
+assert_equal [false, true, false].inspect, <<~'RUBY', frozen_string_literal: false
class C
def initialize freeze
@a = 1
@@ -703,11 +721,11 @@ assert_equal [false, true, false].inspect, %q{
results << check(C.new(true)) # false
results << check(C.new(true).freeze) # true
results << check(C.new(false).freeze) # false
-}
+RUBY
# move example2: String
# touching moved object causes an error
-assert_equal 'hello world', %q{
+assert_equal 'hello world', <<~'RUBY', frozen_string_literal: false
# move
r = Ractor.new do
obj = Ractor.receive
@@ -725,7 +743,7 @@ assert_equal 'hello world', %q{
else
raise 'unreachable'
end
-}
+RUBY
# move example2: Array
assert_equal '[0, 1]', %q{
@@ -928,7 +946,7 @@ assert_equal 'ArgumentError', %q{
}
# ivar in shareable-objects are not allowed to access from non-main Ractor
-assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors", %q{
+assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors", <<~'RUBY', frozen_string_literal: false
class C
@iv = 'str'
end
@@ -939,13 +957,12 @@ assert_equal "can not get unshareable values from instance variables of classes/
end
end
-
begin
r.take
rescue Ractor::RemoteError => e
e.cause.message
end
-}
+RUBY
# ivar in shareable-objects are not allowed to access from non-main Ractor
assert_equal 'can not access instance variables of shareable objects from non-main Ractors', %q{
@@ -1069,6 +1086,27 @@ assert_equal '333', %q{
a + b + c + d + e + f
}
+# moved objects have their shape properly set to original object's shape
+assert_equal '1234', %q{
+class Obj
+ attr_accessor :a, :b, :c, :d
+ def initialize
+ @a = 1
+ @b = 2
+ @c = 3
+ end
+end
+r = Ractor.new do
+ obj = receive
+ obj.d = 4
+ [obj.a, obj.b, obj.c, obj.d]
+end
+obj = Obj.new
+r.send(obj, move: true)
+values = r.take
+values.join
+}
+
# cvar in shareable-objects are not allowed to access from non-main Ractor
assert_equal 'can not access class variables from non-main Ractors', %q{
class C
@@ -1088,8 +1126,30 @@ assert_equal 'can not access class variables from non-main Ractors', %q{
end
}
+# also cached cvar in shareable-objects are not allowed to access from non-main Ractor
+assert_equal 'can not access class variables from non-main Ractors', %q{
+ class C
+ @@cv = 'str'
+ def self.cv
+ @@cv
+ end
+ end
+
+ C.cv # cache
+
+ r = Ractor.new do
+ C.cv
+ end
+
+ begin
+ r.take
+ rescue Ractor::RemoteError => e
+ e.cause.message
+ end
+}
+
# Getting non-shareable objects via constants by other Ractors is not allowed
-assert_equal 'can not access non-shareable objects in constant C::CONST by non-main Ractor.', %q{
+assert_equal 'can not access non-shareable objects in constant C::CONST by non-main Ractor.', <<~'RUBY', frozen_string_literal: false
class C
CONST = 'str'
end
@@ -1101,10 +1161,10 @@ assert_equal 'can not access non-shareable objects in constant C::CONST by non-m
rescue Ractor::RemoteError => e
e.cause.message
end
-}
+ RUBY
# Constant cache should care about non-sharable constants
-assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", %q{
+assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", <<~'RUBY', frozen_string_literal: false
STR = "hello"
def str; STR; end
s = str() # fill const cache
@@ -1113,10 +1173,10 @@ assert_equal "can not access non-shareable objects in constant Object::STR by no
rescue Ractor::RemoteError => e
e.cause.message
end
-}
+RUBY
# Setting non-shareable objects into constants by other Ractors is not allowed
-assert_equal 'can not set constants with non-shareable objects by non-main Ractors', %q{
+assert_equal 'can not set constants with non-shareable objects by non-main Ractors', <<~'RUBY', frozen_string_literal: false
class C
end
r = Ractor.new do
@@ -1127,7 +1187,7 @@ assert_equal 'can not set constants with non-shareable objects by non-main Racto
rescue Ractor::RemoteError => e
e.cause.message
end
-}
+RUBY
# define_method is not allowed
assert_equal "defined with an un-shareable Proc in a different Ractor", %q{
@@ -1180,7 +1240,7 @@ assert_equal '0', %q{
}
# ObjectSpace._id2ref can not handle unshareable objects with Ractors
-assert_equal 'ok', %q{
+assert_equal 'ok', <<~'RUBY', frozen_string_literal: false
s = 'hello'
Ractor.new s.object_id do |id ;s|
@@ -1190,10 +1250,10 @@ assert_equal 'ok', %q{
:ok
end
end.take
-}
+RUBY
# Ractor.make_shareable(obj)
-assert_equal 'true', %q{
+assert_equal 'true', <<~'RUBY', frozen_string_literal: false
class C
def initialize
@a = 'foo'
@@ -1264,7 +1324,7 @@ assert_equal 'true', %q{
}
Ractor.shareable?(a)
-}
+RUBY
# Ractor.make_shareable(obj) doesn't freeze shareable objects
assert_equal 'true', %q{
@@ -1276,9 +1336,13 @@ assert_equal 'true', %q{
# Ractor.make_shareable(a_proc) makes a proc shareable.
assert_equal 'true', %q{
a = [1, [2, 3], {a: "4"}]
- pr = Proc.new do
- a
+
+ pr = Ractor.current.instance_eval do
+ Proc.new do
+ a
+ end
end
+
Ractor.make_shareable(a) # referred value should be shareable
Ractor.make_shareable(pr)
Ractor.shareable?(pr)
@@ -1326,10 +1390,12 @@ assert_equal '1', %q{
# Ractor.make_shareable(a_proc) makes a proc shareable.
assert_equal 'can not make a Proc shareable because it accesses outer variables (a).', %q{
a = b = nil
- pr = Proc.new do
- c = b # assign to a is okay because c is block local variable
- # reading b is okay
- a = b # assign to a is not allowed #=> Ractor::Error
+ pr = Ractor.current.instance_eval do
+ Proc.new do
+ c = b # assign to a is okay because c is block local variable
+ # reading b is okay
+ a = b # assign to a is not allowed #=> Ractor::Error
+ end
end
begin
@@ -1355,14 +1421,14 @@ assert_equal '[false, false, true, true]', %q{
}
# TracePoint with normal Proc should be Ractor local
-assert_equal '[4, 8]', %q{
+assert_equal '[6, 10]', %q{
rs = []
TracePoint.new(:line){|tp| rs << tp.lineno if tp.path == __FILE__}.enable do
- Ractor.new{ # line 4
+ Ractor.new{ # line 5
a = 1
b = 2
}.take
- c = 3 # line 8
+ c = 3 # line 9
end
rs
}
@@ -1430,28 +1496,13 @@ assert_equal "#{N}#{N}", %Q{
}.map{|r| r.take}.join
}
-# enc_table
-assert_equal "#{N/10}", %Q{
- Ractor.new do
- loop do
- Encoding.find("test-enc-#{rand(5_000)}").inspect
- rescue ArgumentError => e
- end
- end
-
- src = Encoding.find("UTF-8")
- #{N/10}.times{|i|
- src.replicate("test-enc-\#{i}")
- }
-}
-
# Generic ivtbl
n = N/2
assert_equal "#{n}#{n}", %Q{
2.times.map{
Ractor.new do
#{n}.times do
- obj = ''
+ obj = +''
obj.instance_variable_set("@a", 1)
obj.instance_variable_set("@b", 1)
obj.instance_variable_set("@c", 1)
@@ -1463,8 +1514,9 @@ assert_equal "#{n}#{n}", %Q{
# NameError
assert_equal "ok", %q{
+ obj = "".freeze # NameError refers the receiver indirectly
begin
- bar
+ obj.bar
rescue => err
end
begin
@@ -1500,6 +1552,254 @@ assert_equal "ok", %q{
1_000.times { idle_worker, tmp_reporter = Ractor.select(*workers) }
"ok"
+} unless yjit_enabled? || rjit_enabled? # flaky
+
+assert_equal "ok", %q{
+ def foo(*); ->{ super }; end
+ begin
+ Ractor.make_shareable(foo)
+ rescue Ractor::IsolationError
+ "ok"
+ end
+}
+
+assert_equal "ok", %q{
+ def foo(**); ->{ super }; end
+ begin
+ Ractor.make_shareable(foo)
+ rescue Ractor::IsolationError
+ "ok"
+ end
+}
+
+assert_equal "ok", %q{
+ def foo(...); ->{ super }; end
+ begin
+ Ractor.make_shareable(foo)
+ rescue Ractor::IsolationError
+ "ok"
+ end
+}
+
+assert_equal "ok", %q{
+ def foo((x), (y)); ->{ super }; end
+ begin
+ Ractor.make_shareable(foo([], []))
+ rescue Ractor::IsolationError
+ "ok"
+ end
+}
+
+# check method cache invalidation
+assert_equal "ok", %q{
+ module M
+ def foo
+ @foo
+ end
+ end
+
+ class A
+ include M
+
+ def initialize
+ 100.times { |i| instance_variable_set(:"@var_#{i}", "bad: #{i}") }
+ @foo = 2
+ end
+ end
+
+ class B
+ include M
+
+ def initialize
+ @foo = 1
+ end
+ end
+
+ Ractor.new do
+ b = B.new
+ 100_000.times do
+ raise unless b.foo == 1
+ end
+ end
+
+ a = A.new
+ 100_000.times do
+ raise unless a.foo == 2
+ end
+
+ "ok"
+}
+
+# check method cache invalidation
+assert_equal 'true', %q{
+ class C1; def self.foo = 1; end
+ class C2; def self.foo = 2; end
+ class C3; def self.foo = 3; end
+ class C4; def self.foo = 5; end
+ class C5; def self.foo = 7; end
+ class C6; def self.foo = 11; end
+ class C7; def self.foo = 13; end
+ class C8; def self.foo = 17; end
+
+ LN = 10_000
+ RN = 10
+ CS = [C1, C2, C3, C4, C5, C6, C7, C8]
+ rs = RN.times.map{|i|
+ Ractor.new(CS.shuffle){|cs|
+ LN.times.sum{
+ cs.inject(1){|r, c| r * c.foo} # c.foo invalidates method cache entry
+ }
+ }
+ }
+
+ n = CS.inject(1){|r, c| r * c.foo} * LN
+ rs.map{|r| r.take} == Array.new(RN){n}
+}
+
+# check experimental warning
+assert_match /\Atest_ractor\.rb:1:\s+warning:\s+Ractor is experimental/, %q{
+ Warning[:experimental] = $VERBOSE = true
+ STDERR.reopen(STDOUT)
+ eval("Ractor.new{}.take", nil, "test_ractor.rb", 1)
+}, frozen_string_literal: false
+
+# check moved object
+assert_equal 'ok', %q{
+ r = Ractor.new do
+ Ractor.receive
+ GC.start
+ :ok
+ end
+
+ obj = begin
+ raise
+ rescue => e
+ e = Marshal.load(Marshal.dump(e))
+ end
+
+ r.send obj, move: true
+ r.take
+}
+
+## Ractor::Selector
+
+# Selector#empty? returns true
+assert_equal 'true', %q{
+ skip true unless defined? Ractor::Selector
+
+ s = Ractor::Selector.new
+ s.empty?
+}
+
+# Selector#empty? returns false if there is target ractors
+assert_equal 'false', %q{
+ skip false unless defined? Ractor::Selector
+
+ s = Ractor::Selector.new
+ s.add Ractor.new{}
+ s.empty?
+}
+
+# Selector#clear removes all ractors from the waiting list
+assert_equal 'true', %q{
+ skip true unless defined? Ractor::Selector
+
+ s = Ractor::Selector.new
+ s.add Ractor.new{10}
+ s.add Ractor.new{20}
+ s.clear
+ s.empty?
+}
+
+# Selector#wait can wait multiple ractors
+assert_equal '[10, 20, true]', %q{
+ skip [10, 20, true] unless defined? Ractor::Selector
+
+ s = Ractor::Selector.new
+ s.add Ractor.new{10}
+ s.add Ractor.new{20}
+ r, v = s.wait
+ vs = []
+ vs << v
+ r, v = s.wait
+ vs << v
+ [*vs.sort, s.empty?]
+} if defined? Ractor::Selector
+
+# Selector#wait can wait multiple ractors with receiving.
+assert_equal '30', %q{
+ skip 30 unless defined? Ractor::Selector
+
+ RN = 30
+ rs = RN.times.map{
+ Ractor.new{ :v }
+ }
+ s = Ractor::Selector.new(*rs)
+
+ results = []
+ until s.empty?
+ results << s.wait
+
+ # Note that s.wait can raise an exception because other Ractors/Threads
+ # can take from the same ractors in the waiting set.
+ # In this case there is no other takers so `s.wait` doesn't raise an error.
+ end
+
+ results.size
+} if defined? Ractor::Selector
+
+# Selector#wait can support dynamic addition
+assert_equal '600', %q{
+ skip 600 unless defined? Ractor::Selector
+
+ RN = 100
+ s = Ractor::Selector.new
+ rs = RN.times.map{
+ Ractor.new{
+ Ractor.main << Ractor.new{ Ractor.yield :v3; :v4 }
+ Ractor.main << Ractor.new{ Ractor.yield :v5; :v6 }
+ Ractor.yield :v1
+ :v2
+ }
+ }
+
+ rs.each{|r| s.add(r)}
+ h = {v1: 0, v2: 0, v3: 0, v4: 0, v5: 0, v6: 0}
+
+ loop do
+ case s.wait receive: true
+ in :receive, r
+ s.add r
+ in r, v
+ h[v] += 1
+ break if h.all?{|k, v| v == RN}
+ end
+ end
+
+ h.sum{|k, v| v}
+} unless yjit_enabled? # http://ci.rvm.jp/results/trunk-yjit@ruby-sp2-docker/4466770
+
+# Selector should be GCed (free'ed) without trouble
+assert_equal 'ok', %q{
+ skip :ok unless defined? Ractor::Selector
+
+ RN = 30
+ rs = RN.times.map{
+ Ractor.new{ :v }
+ }
+ s = Ractor::Selector.new(*rs)
+ :ok
}
end # if !ENV['GITHUB_WORKFLOW']
+
+# Chilled strings are not shareable
+assert_equal 'false', %q{
+ Ractor.shareable?("chilled")
+}
+
+# Chilled strings can be made shareable
+assert_equal 'true', %q{
+ shareable = Ractor.make_shareable("chilled")
+ shareable == "chilled" && Ractor.shareable?(shareable)
+}
diff --git a/bootstraptest/test_rjit.rb b/bootstraptest/test_rjit.rb
new file mode 100644
index 0000000000..e123f35160
--- /dev/null
+++ b/bootstraptest/test_rjit.rb
@@ -0,0 +1,58 @@
+# VM_CALL_OPT_SEND + VM_METHOD_TYPE_ATTRSET
+assert_equal '1', %q{
+ class Foo
+ attr_writer :foo
+
+ def bar
+ send(:foo=, 1)
+ end
+ end
+
+ Foo.new.bar
+}
+
+# VM_CALL_OPT_SEND + OPTIMIZED_METHOD_TYPE_CALL
+assert_equal 'foo', %q{
+ def bar(&foo)
+ foo.send(:call)
+ end
+
+ bar { :foo }
+}
+
+# VM_CALL_OPT_SEND + OPTIMIZED_METHOD_TYPE_STRUCT_AREF
+assert_equal 'bar', %q{
+ def bar(foo)
+ foo.send(:bar)
+ end
+
+ bar(Struct.new(:bar).new(:bar))
+}
+
+# kwargs default w/ checkkeyword + locals (which shouldn't overwrite unspecified_bits)
+assert_equal '1', %q{
+ def foo(bar: 1.to_s)
+ _ = 1
+ bar
+ end
+
+ def entry
+ foo
+ end
+
+ entry
+}
+
+# Updating local type in Context
+assert_normal_exit %q{
+ def foo(flag, object)
+ klass = if flag
+ object
+ end
+ klass ||= object
+ return klass.new
+ end
+
+ foo(false, Object)
+ foo(true, Object)
+}
diff --git a/bootstraptest/test_syntax.rb b/bootstraptest/test_syntax.rb
index fa27bf2aeb..fbc9c6f62e 100644
--- a/bootstraptest/test_syntax.rb
+++ b/bootstraptest/test_syntax.rb
@@ -528,24 +528,24 @@ assert_equal %q{1}, %q{
i
}
def assert_syntax_error expected, code, message = ''
- assert_equal "#{expected}",
- "begin eval(%q{#{code}}, nil, '', 0)"'; rescue SyntaxError => e; e.message[/\A:(?:\d+:)? (.*)/, 1] end', message
+ assert_match /^#{Regexp.escape(expected)}/,
+ "begin eval(%q{#{code}}, nil, '', 0)"'; rescue SyntaxError => e; e.message[/(?:\^~*|\A:(?:\d+:)?(?! syntax errors? found)(?: syntax error,)?) (.*)/, 1] end', message
end
assert_syntax_error "unterminated string meets end of file", '().."', '[ruby-dev:29732]'
assert_equal %q{[]}, %q{$&;[]}, '[ruby-dev:31068]'
-assert_syntax_error "syntax error, unexpected *, expecting '}'", %q{{*0}}, '[ruby-dev:31072]'
-assert_syntax_error "`@0' is not allowed as an instance variable name", %q{@0..0}, '[ruby-dev:31095]'
-assert_syntax_error "identifier $00 is not valid to get", %q{$00..0}, '[ruby-dev:31100]'
-assert_syntax_error "identifier $00 is not valid to set", %q{0..$00=1}
+assert_syntax_error "unexpected *, expecting '}'", %q{{*0}}, '[ruby-dev:31072]'
+assert_syntax_error "'@0' is not allowed as an instance variable name", %q{@0..0}, '[ruby-dev:31095]'
+assert_syntax_error "'$00' is not allowed as a global variable name", %q{$00..0}, '[ruby-dev:31100]'
+assert_syntax_error "'$00' is not allowed as a global variable name", %q{0..$00=1}
assert_equal %q{0}, %q{[*0];0}, '[ruby-dev:31102]'
-assert_syntax_error "syntax error, unexpected ')'", %q{v0,(*,v1,) = 0}, '[ruby-dev:31104]'
+assert_syntax_error "unexpected ')'", %q{v0,(*,v1,) = 0}, '[ruby-dev:31104]'
assert_equal %q{1}, %q{
class << (ary=[]); def []; 0; end; def []=(x); super(0,x);end;end; ary[]+=1
}, '[ruby-dev:31110]'
assert_syntax_error "Can't set variable $1", %q{0..$1=1}, '[ruby-dev:31118]'
assert_valid_syntax %q{1.times{1+(1&&next)}}, '[ruby-dev:31119]'
assert_valid_syntax %q{x=-1;loop{x+=1&&redo if (x+=1).zero?}}, '[ruby-dev:31119]'
-assert_syntax_error %q{syntax error, unexpected end-of-input}, %q{!}, '[ruby-dev:31243]'
+assert_syntax_error %q{unexpected end-of-input}, %q{!}, '[ruby-dev:31243]'
assert_equal %q{[nil]}, %q{[()]}, '[ruby-dev:31252]'
assert_equal %q{true}, %q{!_=()}, '[ruby-dev:31263]'
assert_equal 'ok', %q{while true; redo; end if 1 == 2; :ok}, '[ruby-dev:31360]'
@@ -628,8 +628,8 @@ assert_equal '2', %q{
}
assert_match /invalid multibyte char/, %q{
- STDERR.reopen(STDOUT)
- eval("\"\xf0".force_encoding("utf-8"))
+ $stderr = STDOUT
+ eval("\"\xf0".dup.force_encoding("utf-8"))
}, '[ruby-dev:32429]'
# method ! and !=
@@ -904,3 +904,35 @@ assert_normal_exit %q{
Class
end
}, '[ruby-core:30293]'
+
+assert_equal "false", <<~RUBY, "literal strings are mutable", "--disable-frozen-string-literal"
+ 'test'.frozen?
+RUBY
+
+assert_equal "true", <<~RUBY, "literal strings are frozen", "--disable-frozen-string-literal", frozen_string_literal: true
+ 'test'.frozen?
+RUBY
+
+assert_equal "true", <<~RUBY, "literal strings are frozen", "--enable-frozen-string-literal"
+ 'test'.frozen?
+RUBY
+
+assert_equal "false", <<~RUBY, "literal strings are mutable", "--enable-frozen-string-literal", frozen_string_literal: false
+ 'test'.frozen?
+RUBY
+
+assert_equal "false", <<~RUBY, "__FILE__ is mutable", "--disable-frozen-string-literal"
+ __FILE__.frozen?
+RUBY
+
+assert_equal "true", <<~RUBY, "__FILE__ is frozen", "--disable-frozen-string-literal", frozen_string_literal: true
+ __FILE__.frozen?
+RUBY
+
+assert_equal "true", <<~RUBY, "__FILE__ is frozen", "--enable-frozen-string-literal"
+ __FILE__.frozen?
+RUBY
+
+assert_equal "false", <<~RUBY, "__FILE__ is mutable", "--enable-frozen-string-literal", frozen_string_literal: false
+ __FILE__.frozen?
+RUBY
diff --git a/bootstraptest/test_thread.rb b/bootstraptest/test_thread.rb
index 38a55ff229..a4d46e2f10 100644
--- a/bootstraptest/test_thread.rb
+++ b/bootstraptest/test_thread.rb
@@ -242,8 +242,22 @@ assert_equal 'true', %{
end
}
+assert_equal 'true', %{
+ Thread.new{}.join
+ begin
+ Process.waitpid2 fork{
+ Thread.new{
+ sleep 0.1
+ }.join
+ }
+ true
+ rescue NotImplementedError
+ true
+ end
+}
+
assert_equal 'ok', %{
- open("zzz.rb", "w") do |f|
+ open("zzz_t1.rb", "w") do |f|
f.puts <<-END
begin
Thread.new { fork { GC.start } }.join
@@ -254,7 +268,7 @@ assert_equal 'ok', %{
end
END
end
- require "./zzz.rb"
+ require "./zzz_t1.rb"
$result
}
@@ -289,7 +303,7 @@ assert_normal_exit %q{
}.each {|t|
t.join
}
-}
+} unless rjit_enabled? # flaky
assert_equal 'ok', %q{
def m
@@ -408,7 +422,7 @@ assert_equal 'ok', %q{
}
assert_equal 'ok', %{
- open("zzz.rb", "w") do |f|
+ open("zzz_t2.rb", "w") do |f|
f.puts <<-'end;' # do
begin
m = Thread::Mutex.new
@@ -432,7 +446,7 @@ assert_equal 'ok', %{
end
end;
end
- require "./zzz.rb"
+ require "./zzz_t2.rb"
$result
}
@@ -483,7 +497,7 @@ assert_equal 'foo', %q{
[th1, th2].each {|t| t.join }
GC.start
f.call.source
-}
+} unless rjit_enabled? # flaky
assert_normal_exit %q{
class C
def inspect
diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb
index 0a3aa81860..31bb626690 100644
--- a/bootstraptest/test_yjit.rb
+++ b/bootstraptest/test_yjit.rb
@@ -1,3 +1,540 @@
+# To run the tests in this file only, with YJIT enabled:
+# make btest BTESTS=bootstraptest/test_yjit.rb RUN_OPTS="--yjit-call-threshold=1"
+
+# regression test for popping before side exit
+assert_equal "ok", %q{
+ def foo(a, *) = a
+
+ def call(args, &)
+ foo(1) # spill at where the block arg will be
+ foo(*args, &)
+ end
+
+ call([1, 2])
+
+ begin
+ call([])
+ rescue ArgumentError
+ :ok
+ end
+}
+
+# regression test for send processing before side exit
+assert_equal "ok", %q{
+ def foo(a, *) = :foo
+
+ def call(args)
+ send(:foo, *args)
+ end
+
+ call([1, 2])
+
+ begin
+ call([])
+ rescue ArgumentError
+ :ok
+ end
+}
+
+# test discarding extra yield arguments
+assert_equal "2210150001501015", %q{
+ def splat_kw(ary) = yield *ary, a: 1
+
+ def splat(ary) = yield *ary
+
+ def kw = yield 1, 2, a: 0
+
+ def simple = yield 0, 1
+
+ def calls
+ [
+ splat([1, 1, 2]) { |x, y| x + y },
+ splat([1, 1, 2]) { |y, opt = raise| opt + y},
+ splat_kw([0, 1]) { |a:| a },
+ kw { |a:| a },
+ kw { |a| a },
+ simple { 5.itself },
+ simple { |a| a },
+ simple { |opt = raise| opt },
+ simple { |*rest| rest },
+ simple { |opt_kw: 5| opt_kw },
+ # autosplat ineractions
+ [0, 1, 2].yield_self { |a, b| [a, b] },
+ [0, 1, 2].yield_self { |a, opt = raise| [a, opt] },
+ [1].yield_self { |a, opt = 4| a + opt },
+ ]
+ end
+
+ calls.join
+}
+
+# test autosplat with empty splat
+assert_equal "ok", %q{
+ def m(pos, splat) = yield pos, *splat
+
+ m([:ok], []) {|v0,| v0 }
+}
+
+# regression test for send stack shifting
+assert_normal_exit %q{
+ def foo(a, b)
+ a.singleton_methods(b)
+ end
+
+ def call_foo
+ [1, 1, 1, 1, 1, 1, send(:foo, 1, 1)]
+ end
+
+ call_foo
+}
+
+# regression test for keyword splat with yield
+assert_equal 'nil', %q{
+ def splat_kw(kwargs) = yield(**kwargs)
+
+ splat_kw({}) { _1 }.inspect
+}
+
+# regression test for arity check with splat
+assert_equal '[:ae, :ae]', %q{
+ def req_one(a_, b_ = 1) = raise
+
+ def test(args)
+ req_one *args
+ rescue ArgumentError
+ :ae
+ end
+
+ [test(Array.new 5), test([])]
+} unless rjit_enabled? # Not yet working on RJIT
+
+# regression test for arity check with splat and send
+assert_equal '[:ae, :ae]', %q{
+ def two_reqs(a, b_, _ = 1) = a.gsub(a, a)
+
+ def test(name, args)
+ send(name, *args)
+ rescue ArgumentError
+ :ae
+ end
+
+ [test(:two_reqs, ["g", nil, nil, nil]), test(:two_reqs, ["g"])]
+}
+
+# regression test for GC marking stubs in invalidated code
+assert_normal_exit %q{
+ garbage = Array.new(10_000) { [] } # create garbage to cause iseq movement
+ eval(<<~RUBY)
+ def foo(n, garbage)
+ if n == 2
+ # 1.times.each to create a cfunc frame to preserve the JIT frame
+ # which will return to a stub housed in an invalidated block
+ return 1.times.each do
+ Object.define_method(:foo) {}
+ garbage.clear
+ GC.verify_compaction_references(toward: :empty, expand_heap: true)
+ end
+ end
+
+ foo(n + 1, garbage)
+ end
+ RUBY
+
+ foo(1, garbage)
+}
+
+# regression test for callee block handler overlapping with arguments
+assert_equal '3', %q{
+ def foo(_req, *args) = args.last
+
+ def call_foo = foo(0, 1, 2, 3, &->{})
+
+ call_foo
+}
+
+# call leaf builtin with a block argument
+assert_equal '0', "0.abs(&nil)"
+
+# regression test for invokeblock iseq guard
+assert_equal 'ok', %q{
+ return :ok unless defined?(GC.compact)
+ def foo = yield
+ 10.times do |i|
+ ret = eval("foo { #{i} }")
+ raise "failed at #{i}" unless ret == i
+ GC.compact
+ end
+ :ok
+} unless rjit_enabled? # Not yet working on RJIT
+
+# regression test for overly generous guard elision
+assert_equal '[0, :sum, 0, :sum]', %q{
+ # In faulty versions, the following happens:
+ # 1. YJIT puts object on the temp stack with type knowledge
+ # (CArray or CString) about RBASIC_CLASS(object).
+ # 2. In iter=0, due to the type knowledge, YJIT generates
+ # a call to sum() without any guard on RBASIC_CLASS(object).
+ # 3. In iter=1, a singleton class is added to the object,
+ # changing RBASIC_CLASS(object), falsifying the type knowledge.
+ # 4. Because the code from (1) has no class guard, it is incorrectly
+ # reused and the wrong method is invoked.
+ # Putting a literal is important for gaining type knowledge.
+ def carray(iter)
+ array = []
+ array.sum(iter.times { def array.sum(_) = :sum })
+ end
+
+ def cstring(iter)
+ string = "".dup
+ string.sum(iter.times { def string.sum(_) = :sum })
+ end
+
+ [carray(0), carray(1), cstring(0), cstring(1)]
+}
+
+# regression test for return type of Integer#/
+# It can return a T_BIGNUM when inputs are T_FIXNUM.
+assert_equal 0x3fffffffffffffff.to_s, %q{
+ def call(fixnum_min)
+ (fixnum_min / -1) - 1
+ end
+
+ call(-(2**62))
+}
+
+# regression test for return type of String#<<
+assert_equal 'Sub', %q{
+ def call(sub) = (sub << sub).itself
+
+ class Sub < String; end
+
+ call(Sub.new('o')).class
+}
+
+# test splat filling required and feeding rest
+assert_equal '[0, 1, 2, [3, 4]]', %q{
+ public def lead_rest(a, b, *rest)
+ [self, a, b, rest]
+ end
+
+ def call(args) = 0.lead_rest(*args)
+
+ call([1, 2, 3, 4])
+}
+
+# test missing opts are nil initialized
+assert_equal '[[0, 1, nil, 3], [0, 1, nil, 3], [0, 1, nil, 3, []], [0, 1, nil, 3, []]]', %q{
+ public def lead_opts(a, b=binding.local_variable_get(:c), c=3)
+ [self, a, b, c]
+ end
+
+ public def opts_rest(a=raise, b=binding.local_variable_get(:c), c=3, *rest)
+ [self, a, b, c, rest]
+ end
+
+ def call(args)
+ [
+ 0.lead_opts(1),
+ 0.lead_opts(*args),
+
+ 0.opts_rest(1),
+ 0.opts_rest(*args),
+ ]
+ end
+
+ call([1])
+}
+
+# test filled optionals with unspecified keyword param
+assert_equal 'ok', %q{
+ def opt_rest_opt_kw(_=1, *, k: :ok) = k
+
+ def call = opt_rest_opt_kw(0)
+
+ call
+}
+
+# test splat empty array with rest param
+assert_equal '[0, 1, 2, []]', %q{
+ public def foo(a=1, b=2, *rest)
+ [self, a, b, rest]
+ end
+
+ def call(args) = 0.foo(*args)
+
+ call([])
+}
+
+# Regression test for yielding with autosplat to block with
+# optional parameters. https://github.com/Shopify/yjit/issues/313
+assert_equal '[:a, :b, :a, :b]', %q{
+ def yielder(arg) = yield(arg) + yield(arg)
+
+ yielder([:a, :b]) do |c = :c, d = :d|
+ [c, d]
+ end
+}
+
+# Regression test for GC mishap while doing shape transition
+assert_equal '[:ok]', %q{
+ # [Bug #19601]
+ class RegressionTest
+ def initialize
+ @a = @b = @fourth_ivar_does_shape_transition = nil
+ end
+
+ def extender
+ @first_extended_ivar = [:ok]
+ end
+ end
+
+ GC.stress = true
+
+ # Used to crash due to GC run in rb_ensure_iv_list_size()
+ # not marking the newly allocated [:ok].
+ RegressionTest.new.extender.itself
+} unless rjit_enabled? # Skip on RJIT since this uncovers a crash
+
+assert_equal 'true', %q{
+ # regression test for tracking type of locals for too long
+ def local_setting_cmp(five)
+ victim = 5
+ five.define_singleton_method(:respond_to?) do |_, _|
+ victim = nil
+ end
+
+ # +1 makes YJIT track that victim is a number and
+ # defined? calls respond_to? from above indirectly
+ unless (victim + 1) && defined?(five.something)
+ # Would return wrong result if we still think `five` is a number
+ victim.nil?
+ end
+ end
+
+ local_setting_cmp(Object.new)
+ local_setting_cmp(Object.new)
+}
+
+assert_equal '18374962167983112447', %q{
+ # regression test for incorrectly discarding 32 bits of a pointer when it
+ # comes to default values.
+ def large_literal_default(n: 0xff00_fabcafe0_00ff)
+ n
+ end
+
+ def call_graph_root
+ large_literal_default
+ end
+
+ call_graph_root
+ call_graph_root
+}
+
+assert_normal_exit %q{
+ # regression test for a leak caught by an assert on --yjit-call-threshold=2
+ Foo = 1
+
+ eval("def foo = [#{(['Foo,']*256).join}]")
+
+ foo
+ foo
+
+ Object.send(:remove_const, :Foo)
+}
+
+assert_normal_exit %q{
+ # Test to ensure send on overridden c functions
+ # doesn't corrupt the stack
+ class Bar
+ def bar(x)
+ x
+ end
+ end
+
+ class Foo
+ def bar
+ Bar.new
+ end
+ end
+
+ foo = Foo.new
+ # before this change, this line would error
+ # because "s" would still be on the stack
+ # String.to_s is the overridden method here
+ p foo.bar.bar("s".__send__(:to_s))
+}
+
+
+assert_equal '[nil, nil, nil, nil, nil, nil]', %q{
+ [NilClass, TrueClass, FalseClass, Integer, Float, Symbol].each do |klass|
+ klass.class_eval("def foo = @foo")
+ end
+
+ [nil, true, false, 0xFABCAFE, 0.42, :cake].map do |instance|
+ instance.foo
+ instance.foo
+ end
+}
+
+assert_equal '[nil, nil, nil, nil, nil, nil]', %q{
+ # Tests defined? on non-heap objects
+ [NilClass, TrueClass, FalseClass, Integer, Float, Symbol].each do |klass|
+ klass.class_eval("def foo = defined?(@foo)")
+ end
+
+ [nil, true, false, 0xFABCAFE, 0.42, :cake].map do |instance|
+ instance.foo
+ instance.foo
+ end
+}
+
+assert_equal '[nil, "instance-variable", nil, "instance-variable"]', %q{
+ # defined? on object that changes shape between calls
+ class Foo
+ def foo
+ defined?(@foo)
+ end
+
+ def add
+ @foo = 1
+ end
+
+ def remove
+ self.remove_instance_variable(:@foo)
+ end
+ end
+
+ obj = Foo.new
+ [obj.foo, (obj.add; obj.foo), (obj.remove; obj.foo), (obj.add; obj.foo)]
+}
+
+assert_equal '["instance-variable", 5]', %q{
+ # defined? on object too complex for shape information
+ class Foo
+ def initialize
+ 100.times { |i| instance_variable_set("@foo#{i}", i) }
+ end
+
+ def foo
+ [defined?(@foo5), @foo5]
+ end
+ end
+
+ Foo.new.foo
+}
+
+# getinstancevariable with shape too complex
+assert_normal_exit %q{
+ class Foo
+ def initialize
+ @a = 1
+ end
+
+ def getter
+ @foobar
+ end
+ end
+
+ # Initialize ivars in changing order, making the Foo
+ # class have shape too complex
+ 100.times do |x|
+ foo = Foo.new
+ foo.instance_variable_set(:"@a#{x}", 1)
+ foo.instance_variable_set(:"@foobar", 777)
+
+ # The getter method eventually sees shape too complex
+ r = foo.getter
+ if r != 777
+ raise "error"
+ end
+ end
+}
+
+assert_equal '0', %q{
+ # This is a regression test for incomplete invalidation from
+ # opt_setinlinecache. This test might be brittle, so
+ # feel free to remove it in the future if it's too annoying.
+ # This test assumes --yjit-call-threshold=2.
+ module M
+ Foo = 1
+ def foo
+ Foo
+ end
+
+ def pin_self_type_then_foo
+ _ = @foo
+ foo
+ end
+
+ def only_ints
+ 1 + self
+ foo
+ end
+ end
+
+ class Integer
+ include M
+ end
+
+ class Sub
+ include M
+ end
+
+ foo_method = M.instance_method(:foo)
+
+ dbg = ->(message) do
+ return # comment this out to get printouts
+
+ $stderr.puts RubyVM::YJIT.disasm(foo_method)
+ $stderr.puts message
+ end
+
+ 2.times { 42.only_ints }
+
+ dbg["There should be two versions of getinlineache"]
+
+ module M
+ remove_const(:Foo)
+ end
+
+ dbg["There should be no getinlinecaches"]
+
+ 2.times do
+ 42.only_ints
+ rescue NameError => err
+ _ = "caught name error #{err}"
+ end
+
+ dbg["There should be one version of getinlineache"]
+
+ 2.times do
+ Sub.new.pin_self_type_then_foo
+ rescue NameError
+ _ = 'second specialization'
+ end
+
+ dbg["There should be two versions of getinlineache"]
+
+ module M
+ Foo = 1
+ end
+
+ dbg["There should still be two versions of getinlineache"]
+
+ 42.only_ints
+
+ dbg["There should be no getinlinecaches"]
+
+ # Find name of the first VM instruction in M#foo.
+ insns = RubyVM::InstructionSequence.of(foo_method).to_a
+ if defined?(RubyVM::YJIT.blocks_for) && (insns.last.find { Array === _1 }&.first == :opt_getinlinecache)
+ RubyVM::YJIT.blocks_for(RubyVM::InstructionSequence.of(foo_method))
+ .filter { _1.iseq_start_index == 0 }.count
+ else
+ 0 # skip the test
+ end
+}
+
# Check that frozen objects are respected
assert_equal 'great', %q{
class Foo
@@ -208,6 +745,45 @@ assert_equal 'false', %q{
less_than 2
}
+# BOP redefinition works on Integer#<=
+assert_equal 'false', %q{
+ def le(x, y) = x <= y
+
+ le(2, 2)
+
+ class Integer
+ def <=(_) = false
+ end
+
+ le(2, 2)
+}
+
+# BOP redefinition works on Integer#>
+assert_equal 'false', %q{
+ def gt(x, y) = x > y
+
+ gt(3, 2)
+
+ class Integer
+ def >(_) = false
+ end
+
+ gt(3, 2)
+}
+
+# BOP redefinition works on Integer#>=
+assert_equal 'false', %q{
+ def ge(x, y) = x >= y
+
+ ge(2, 2)
+
+ class Integer
+ def >=(_) = false
+ end
+
+ ge(2, 2)
+}
+
# Putobject, less-than operator, fixnums
assert_equal '2', %q{
def check_index(index)
@@ -663,7 +1239,7 @@ assert_equal "good", %q{
foo
begin
- GC.verify_compaction_references(double_heap: true, toward: :empty)
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
rescue NotImplementedError
# in case compaction isn't supported
end
@@ -674,7 +1250,7 @@ assert_equal "good", %q{
# Test polymorphic getinstancevariable. T_OBJECT -> T_STRING
assert_equal 'ok', %q{
@hello = @h1 = @h2 = @h3 = @h4 = 'ok'
- str = ""
+ str = +""
str.instance_variable_set(:@hello, 'ok')
public def get
@@ -818,6 +1394,18 @@ assert_equal '[42, :default]', %q{
]
}
+# Test default value block for Hash with opt_aref_with
+assert_equal "false", <<~RUBY, frozen_string_literal: false
+ def index_with_string(h)
+ h["foo"]
+ end
+
+ h = Hash.new { |h, k| k.frozen? }
+
+ index_with_string(h)
+ index_with_string(h)
+RUBY
+
# A regression test for making sure cfp->sp is proper when
# hitting stubs. See :stub-sp-flush:
assert_equal 'ok', %q{
@@ -975,6 +1563,38 @@ assert_equal '42', %q{
run
}
+# splatting an empty array on a specialized method
+assert_equal 'ok', %q{
+ def run
+ "ok".to_s(*[])
+ end
+
+ run
+ run
+}
+
+# splatting an single element array on a specialized method
+assert_equal '[1]', %q{
+ def run
+ [].<<(*[1])
+ end
+
+ run
+ run
+}
+
+# specialized method with wrong args
+assert_equal 'ok', %q{
+ def run(x)
+ "bad".to_s(123) if x
+ rescue
+ :ok
+ end
+
+ run(false)
+ run(true)
+}
+
# getinstancevariable on Symbol
assert_equal '[nil, nil]', %q{
# @foo to exercise the getinstancevariable instruction
@@ -1081,6 +1701,19 @@ assert_equal '[1, 2, 42]', %q{
[foo {1}, foo {2}, foo {42}]
}
+# test calling without block param
+assert_equal '[1, false, 2, false]', %q{
+ def bar
+ block_given? && yield
+ end
+
+ def foo(&block)
+ bar(&block)
+ end
+
+ [foo { 1 }, foo, foo { 2 }, foo]
+}
+
# test calling block param failing
assert_equal '42', %q{
def foo(&block)
@@ -1214,6 +1847,285 @@ assert_equal 'foo123', %q{
make_str("foo", 123)
}
+# test that invalidation of String#to_s doesn't crash
+assert_equal 'meh', %q{
+ def inval_method
+ "".to_s
+ end
+
+ inval_method
+
+ class String
+ def to_s
+ "meh"
+ end
+ end
+
+ inval_method
+}
+
+# test that overriding to_s on a String subclass works consistently
+assert_equal 'meh', %q{
+ class MyString < String
+ def to_s
+ "meh"
+ end
+ end
+
+ def test_to_s(obj)
+ obj.to_s
+ end
+
+ OBJ = MyString.new
+
+ # Should return '' both times
+ test_to_s("")
+ test_to_s("")
+
+ # Can return '' if YJIT optimises String#to_s too aggressively
+ test_to_s(OBJ)
+ test_to_s(OBJ)
+}
+
+# test string interpolation with overridden to_s
+assert_equal 'foo', %q{
+ class String
+ def to_s
+ "bad"
+ end
+ end
+
+ def make_str(foo)
+ "#{foo}"
+ end
+
+ make_str("foo")
+ make_str("foo")
+}
+
+# Test that String unary plus returns the same object ID for an unfrozen string.
+assert_equal 'true', <<~RUBY, frozen_string_literal: false
+ def jittable_method
+ str = "bar"
+
+ old_obj_id = str.object_id
+ uplus_str = +str
+
+ uplus_str.object_id == old_obj_id
+ end
+ jittable_method
+RUBY
+
+# Test that String unary plus returns a different unfrozen string when given a frozen string
+assert_equal 'false', %q{
+ # Logic needs to be inside an ISEQ, such as a method, for YJIT to compile it
+ def jittable_method
+ frozen_str = "foo".freeze
+
+ old_obj_id = frozen_str.object_id
+ uplus_str = +frozen_str
+
+ uplus_str.object_id == old_obj_id || uplus_str.frozen?
+ end
+
+ jittable_method
+}
+
+# String-subclass objects should behave as expected inside string-interpolation via concatstrings
+assert_equal 'monkeys / monkeys, yo!', %q{
+ class MyString < String
+ # This is a terrible idea in production code, but we'd like YJIT to match CRuby
+ def to_s
+ super + ", yo!"
+ end
+ end
+
+ def jittable_method
+ m = MyString.new('monkeys')
+ "#{m} / #{m.to_s}"
+ end
+
+ jittable_method
+}
+
+# String-subclass objects should behave as expected for string equality
+assert_equal 'false', %q{
+ class MyString < String
+ # This is a terrible idea in production code, but we'd like YJIT to match CRuby
+ def ==(b)
+ "#{self}_" == b
+ end
+ end
+
+ def jittable_method
+ ma = MyString.new("a")
+
+ # Check equality with string-subclass receiver
+ ma == "a" || ma != "a_" ||
+ # Check equality with string receiver
+ "a_" == ma || "a" != ma ||
+ # Check equality between string subclasses
+ ma != MyString.new("a_") ||
+ # Make sure "string always equals itself" check isn't used with overridden equality
+ ma == ma
+ end
+ jittable_method
+}
+
+# Test to_s duplicates a string subclass object but not a string
+assert_equal 'false', %q{
+ class MyString < String; end
+
+ def jittable_method
+ a = "a"
+ ma = MyString.new("a")
+
+ a.object_id != a.to_s.object_id ||
+ ma.object_id == ma.to_s.object_id
+ end
+ jittable_method
+}
+
+# Test freeze on string subclass
+assert_equal 'true', %q{
+ class MyString < String; end
+
+ def jittable_method
+ fma = MyString.new("a").freeze
+
+ # Freezing a string subclass should not duplicate it
+ fma.object_id == fma.freeze.object_id
+ end
+ jittable_method
+}
+
+# Test unary minus on string subclass
+assert_equal 'true', %q{
+ class MyString < String; end
+
+ def jittable_method
+ ma = MyString.new("a")
+ fma = MyString.new("a").freeze
+
+ # Unary minus on frozen string subclass should not duplicate it
+ fma.object_id == (-fma).object_id &&
+ # Unary minus on unfrozen string subclass should duplicate it
+ ma.object_id != (-ma).object_id
+ end
+ jittable_method
+}
+
+# Test unary plus on string subclass
+assert_equal 'true', %q{
+ class MyString < String; end
+
+ def jittable_method
+ fma = MyString.new("a").freeze
+
+ # Unary plus on frozen string subclass should not duplicate it
+ fma.object_id != (+fma).object_id
+ end
+ jittable_method
+}
+
+# test getbyte on string class
+assert_equal '[97, :nil, 97, :nil, :raised]', %q{
+ def getbyte(s, i)
+ byte = begin
+ s.getbyte(i)
+ rescue TypeError
+ :raised
+ end
+
+ byte || :nil
+ end
+
+ getbyte("a", 0)
+ getbyte("a", 0)
+
+ [getbyte("a", 0), getbyte("a", 1), getbyte("a", -1), getbyte("a", -2), getbyte("a", "a")]
+} unless rjit_enabled? # Not yet working on RJIT
+
+# Basic test for String#setbyte
+assert_equal 'AoZ', %q{
+ s = +"foo"
+ s.setbyte(0, 65)
+ s.setbyte(-1, 90)
+ s
+}
+
+# String#setbyte IndexError
+assert_equal 'String#setbyte', %q{
+ def ccall = "".setbyte(1, 0)
+ begin
+ ccall
+ rescue => e
+ e.backtrace.first.split("'").last
+ end
+}
+
+# String#setbyte TypeError
+assert_equal 'String#setbyte', %q{
+ def ccall = "".setbyte(nil, 0)
+ begin
+ ccall
+ rescue => e
+ e.backtrace.first.split("'").last
+ end
+}
+
+# String#setbyte FrozenError
+assert_equal 'String#setbyte', %q{
+ def ccall = "a".freeze.setbyte(0, 0)
+ begin
+ ccall
+ rescue => e
+ e.backtrace.first.split("'").last
+ end
+}
+
+# non-leaf String#setbyte
+assert_equal 'String#setbyte', %q{
+ def to_int
+ @caller = caller
+ 0
+ end
+
+ def ccall = "a".dup.setbyte(self, 98)
+ ccall
+
+ @caller.first.split("'").last
+}
+
+# non-leaf String#byteslice
+assert_equal 'TypeError', %q{
+ def ccall = "".byteslice(nil, nil)
+ begin
+ ccall
+ rescue => e
+ e.class
+ end
+}
+
+# Test << operator on string subclass
+assert_equal 'abab', %q{
+ class MyString < String; end
+
+ def jittable_method
+ a = -"a"
+ mb = MyString.new("b")
+
+ buf = String.new
+ mbuf = MyString.new
+
+ buf << a << mb
+ mbuf << a << mb
+
+ buf + mbuf
+ end
+ jittable_method
+}
+
# test invokebuiltin as used in struct assignment
assert_equal '123', %q{
def foo(obj)
@@ -1405,6 +2317,19 @@ assert_equal '123', %q{
foo(Foo)
}
+# Test EP == BP invalidation with moving ISEQs
+assert_equal 'ok', %q{
+ def entry
+ ok = proc { :ok } # set #entry as an EP-escaping ISEQ
+ [nil].reverse_each do # avoid exiting the JIT frame on the constant
+ GC.compact # move #entry ISEQ
+ end
+ ok # should be read off of escaped EP
+ end
+
+ entry.call
+}
+
# invokesuper edge case
assert_equal '[:A, [:A, :B]]', %q{
class B
@@ -1561,6 +2486,50 @@ assert_equal '[:A, :Btwo]', %q{
ins.foo
}
+# invokesuper with a block
+assert_equal 'true', %q{
+ class A
+ def foo = block_given?
+ end
+
+ class B < A
+ def foo = super()
+ end
+
+ B.new.foo { }
+ B.new.foo { }
+}
+
+# invokesuper in a block
+assert_equal '[0, 2]', %q{
+ class A
+ def foo(x) = x * 2
+ end
+
+ class B < A
+ def foo
+ 2.times.map do |x|
+ super(x)
+ end
+ end
+ end
+
+ B.new.foo
+ B.new.foo
+}
+
+# invokesuper zsuper in a bmethod
+assert_equal 'ok', %q{
+ class Foo
+ define_method(:itself) { super }
+ end
+ begin
+ Foo.new.itself
+ rescue RuntimeError
+ :ok
+ end
+}
+
# Call to fixnum
assert_equal '[true, false]', %q{
def is_odd(obj)
@@ -1601,6 +2570,16 @@ assert_equal '[true, false, true, false]', %q{
[is_odd(123), is_odd(456), is_odd(bignum), is_odd(bignum+1)]
}
+# Flonum and Flonum
+assert_equal '[2.0, 0.0, 1.0, 4.0]', %q{
+ [1.0 + 1.0, 1.0 - 1.0, 1.0 * 1.0, 8.0 / 2.0]
+}
+
+# Flonum and Fixnum
+assert_equal '[2.0, 0.0, 1.0, 4.0]', %q{
+ [1.0 + 1, 1.0 - 1, 1.0 * 1, 8.0 / 2]
+}
+
# Call to static and dynamic symbol
assert_equal 'bar', %q{
def to_string(obj)
@@ -1641,6 +2620,30 @@ assert_equal '[1, 2, 3, 4, 5]', %q{
splatarray
}
+# splatkw
+assert_equal '[1, 2]', %q{
+ def foo(a:) = [a, yield]
+
+ def entry(&block)
+ a = { a: 1 }
+ foo(**a, &block)
+ end
+
+ entry { 2 }
+}
+assert_equal '[1, 2]', %q{
+ def foo(a:) = [a, yield]
+
+ def entry(obj, &block)
+ foo(**obj, &block)
+ end
+
+ entry({ a: 3 }) { 2 }
+ obj = Object.new
+ def obj.to_hash = { a: 1 }
+ entry(obj) { 2 }
+}
+
assert_equal '[1, 1, 2, 1, 2, 3]', %q{
def expandarray
arr = [1, 2, 3]
@@ -1695,6 +2698,23 @@ assert_equal '[:not_array, nil, nil]', %q{
expandarray_not_array(obj)
}
+assert_equal '[1, 2]', %q{
+ class NilClass
+ private
+ def to_ary
+ [1, 2]
+ end
+ end
+
+ def expandarray_redefined_nilclass
+ a, b = nil
+ [a, b]
+ end
+
+ expandarray_redefined_nilclass
+ expandarray_redefined_nilclass
+} unless rjit_enabled?
+
assert_equal '[1, 2, nil]', %q{
def expandarray_rhs_too_small
a, b, c = [1, 2]
@@ -1705,6 +2725,17 @@ assert_equal '[1, 2, nil]', %q{
expandarray_rhs_too_small
}
+assert_equal '[nil, 2, nil]', %q{
+ def foo(arr)
+ a, b, c = arr
+ end
+
+ a, b, c1 = foo([0, 1])
+ a, b, c2 = foo([0, 1, 2])
+ a, b, c3 = foo([0, 1])
+ [c1, c2, c3]
+}
+
assert_equal '[1, [2]]', %q{
def expandarray_splat
a, *b = [1, 2]
@@ -1794,7 +2825,7 @@ assert_equal '[[:c_return, :String, :string_alias, "events_to_str"]]', %q{
events.compiled(events)
events
-}
+} unless rjit_enabled? # RJIT calls extra Ruby methods
# test enabling a TracePoint that targets a particular line in a C method call
assert_equal '[true]', %q{
@@ -1876,7 +2907,7 @@ assert_equal '[[:c_call, :itself]]', %q{
tp.enable { shouldnt_compile }
events
-}
+} unless rjit_enabled? # RJIT calls extra Ruby methods
# test enabling c_return tracing before compiling
assert_equal '[[:c_return, :itself, main]]', %q{
@@ -1891,6 +2922,26 @@ assert_equal '[[:c_return, :itself, main]]', %q{
tp.enable { shouldnt_compile }
events
+} unless rjit_enabled? # RJIT calls extra Ruby methods
+
+# test c_call invalidation
+assert_equal '[[:c_call, :itself]]', %q{
+ # enable the event once to make sure invalidation
+ # happens the second time we enable it
+ TracePoint.new(:c_call) {}.enable{}
+
+ def compiled
+ itself
+ end
+
+ # assume first call compiles
+ compiled
+
+ events = []
+ tp = TracePoint.new(:c_call) { |tp| events << [tp.event, tp.method_id] }
+ tp.enable { compiled }
+
+ events
}
# test enabling tracing for a suspended fiber
@@ -1917,7 +2968,6 @@ assert_equal '[:itself]', %q{
itself
end
-
tracing_ractor = Ractor.new do
# 1: start tracing
events = []
@@ -2086,6 +3136,22 @@ assert_equal '[1]', %q{
5.times.map { kwargs(value: 1) }.uniq
}
+assert_equal '[:ok]', %q{
+ def kwargs(value:)
+ value
+ end
+
+ 5.times.map { kwargs() rescue :ok }.uniq
+}
+
+assert_equal '[:ok]', %q{
+ def kwargs(a:, b: nil)
+ value
+ end
+
+ 5.times.map { kwargs(b: 123) rescue :ok }.uniq
+}
+
assert_equal '[[1, 2]]', %q{
def kwargs(left:, right:)
[left, right]
@@ -2107,6 +3173,40 @@ assert_equal '[[1, 2]]', %q{
5.times.map { kwargs(1, kwarg: 2) }.uniq
}
+# optional and keyword args
+assert_equal '[[1, 2, 3]]', %q{
+ def opt_and_kwargs(a, b=2, c: nil)
+ [a,b,c]
+ end
+
+ 5.times.map { opt_and_kwargs(1, c: 3) }.uniq
+}
+
+assert_equal '[[1, 2, 3]]', %q{
+ def opt_and_kwargs(a, b=nil, c: nil)
+ [a,b,c]
+ end
+
+ 5.times.map { opt_and_kwargs(1, 2, c: 3) }.uniq
+}
+
+# Bug #18453
+assert_equal '[[1, nil, 2]]', %q{
+ def opt_and_kwargs(a = {}, b: nil, c: nil)
+ [a, b, c]
+ end
+
+ 5.times.map { opt_and_kwargs(1, c: 2) }.uniq
+}
+
+assert_equal '[[{}, nil, 1]]', %q{
+ def opt_and_kwargs(a = {}, b: nil, c: nil)
+ [a, b, c]
+ end
+
+ 5.times.map { opt_and_kwargs(c: 1) }.uniq
+}
+
# leading and keyword arguments are swapped into the right order
assert_equal '[[1, 2, 3, 4, 5, 6]]', %q{
def kwargs(five, six, a:, b:, c:, d:)
@@ -2165,6 +3265,133 @@ assert_equal '[2]', %q{
5.times.map { default_expression(value: 2) }.uniq
}
+# constant default values on keywords
+assert_equal '[3]', %q{
+ def default_expression(value: 3)
+ value
+ end
+
+ 5.times.map { default_expression }.uniq
+}
+
+# non-constant default values on keywords
+assert_equal '[3]', %q{
+ def default_value
+ 3
+ end
+
+ def default_expression(value: default_value)
+ value
+ end
+
+ 5.times.map { default_expression }.uniq
+}
+
+# reordered optional kwargs
+assert_equal '[[100, 1]]', %q{
+ def foo(capacity: 100, max: nil)
+ [capacity, max]
+ end
+
+ 5.times.map { foo(max: 1) }.uniq
+}
+
+# invalid lead param
+assert_equal 'ok', %q{
+ def bar(baz: 2)
+ baz
+ end
+
+ def foo
+ bar(1, baz: 123)
+ end
+
+ begin
+ foo
+ foo
+ rescue ArgumentError => e
+ print "ok"
+ end
+}
+
+# reordered required kwargs
+assert_equal '[[1, 2, 3, 4]]', %q{
+ def foo(default1: 1, required1:, default2: 3, required2:)
+ [default1, required1, default2, required2]
+ end
+
+ 5.times.map { foo(required1: 2, required2: 4) }.uniq
+}
+
+# reordered default expression kwargs
+assert_equal '[[:one, :two, 3]]', %q{
+ def foo(arg1: (1+0), arg2: (2+0), arg3: (3+0))
+ [arg1, arg2, arg3]
+ end
+
+ 5.times.map { foo(arg2: :two, arg1: :one) }.uniq
+}
+
+# complex kwargs
+assert_equal '[[1, 2, 3, 4]]', %q{
+ def foo(required:, specified: 999, simple_default: 3, complex_default: "4".to_i)
+ [required, specified, simple_default, complex_default]
+ end
+
+ 5.times.map { foo(specified: 2, required: 1) }.uniq
+}
+
+# cfunc kwargs
+assert_equal '{:foo=>123}', %q{
+ def foo(bar)
+ bar.store(:value, foo: 123)
+ bar[:value]
+ end
+
+ foo({})
+ foo({})
+}
+
+# cfunc kwargs
+assert_equal '{:foo=>123}', %q{
+ def foo(bar)
+ bar.replace(foo: 123)
+ end
+
+ foo({})
+ foo({})
+}
+
+# cfunc kwargs
+assert_equal '{:foo=>123, :bar=>456}', %q{
+ def foo(bar)
+ bar.replace(foo: 123, bar: 456)
+ end
+
+ foo({})
+ foo({})
+}
+
+# variadic cfunc kwargs
+assert_equal '{:foo=>123}', %q{
+ def foo(bar)
+ bar.merge(foo: 123)
+ end
+
+ foo({})
+ foo({})
+}
+
+# optimized cfunc kwargs
+assert_equal 'false', %q{
+ def foo
+ :foo.eql?(foo: :foo)
+ end
+
+ foo
+ foo
+}
+
# attr_reader on frozen object
assert_equal 'false', %q{
class Foo
@@ -2288,3 +3515,1347 @@ assert_equal '{:foo=>2}', %q{
foo
foo
}
+
+# block invalidation edge case
+assert_equal 'undef', %q{
+ class A
+ def foo(arg)
+ arg.times { A.remove_method(:bar) }
+ self
+ end
+
+ def bar
+ 4
+ end
+
+ def use(arg)
+ # two consecutive sends. When bar is removed, the return address
+ # for calling it is already on foo's control frame
+ foo(arg).bar
+ rescue NoMethodError
+ :undef
+ end
+ end
+
+ A.new.use 0
+ A.new.use 0
+ A.new.use 1
+}
+
+# block invalidation edge case
+assert_equal 'ok', %q{
+ class A
+ Good = :ng
+ def foo(arg)
+ arg.times { A.const_set(:Good, :ok) }
+ self
+ end
+
+ def id(arg)
+ arg
+ end
+
+ def use(arg)
+ # send followed by an opt_getinlinecache.
+ # The return address remains on the control frame
+ # when opt_getinlinecache is invalidated.
+ foo(arg).id(Good)
+ end
+ end
+
+ A.new.use 0
+ A.new.use 0
+ A.new.use 1
+}
+
+assert_equal 'ok', %q{
+ # test hitting a branch stub when out of memory
+ def nimai(jita)
+ if jita
+ :ng
+ else
+ :ok
+ end
+ end
+
+ nimai(true)
+ nimai(true)
+
+ RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT)
+
+ nimai(false)
+}
+
+assert_equal 'new', %q{
+ # test block invalidation while out of memory
+ def foo
+ :old
+ end
+
+ def test
+ foo
+ end
+
+ def bar
+ :bar
+ end
+
+
+ test
+ test
+
+ RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT)
+
+ # Old simulat_omm! leaves one byte of space and this fills it up
+ bar
+ bar
+
+ def foo
+ :new
+ end
+
+ test
+}
+
+assert_equal 'ok', %q{
+ # Try to compile new method while OOM
+ def foo
+ :ok
+ end
+
+ RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT)
+
+ foo
+ foo
+}
+
+# struct aref embedded
+assert_equal '2', %q{
+ def foo(s)
+ s.foo
+ end
+
+ S = Struct.new(:foo)
+ foo(S.new(1))
+ foo(S.new(2))
+}
+
+# struct aref non-embedded
+assert_equal '4', %q{
+ def foo(s)
+ s.d
+ end
+
+ S = Struct.new(:a, :b, :c, :d, :e)
+ foo(S.new(1,2,3,4,5))
+ foo(S.new(1,2,3,4,5))
+}
+
+# struct aset embedded
+assert_equal '123', %q{
+ def foo(s)
+ s.foo = 123
+ end
+
+ s = Struct.new(:foo).new
+ foo(s)
+ s = Struct.new(:foo).new
+ foo(s)
+ s.foo
+}
+
+# struct aset non-embedded
+assert_equal '[1, 2, 3, 4, 5]', %q{
+ def foo(s)
+ s.a = 1
+ s.b = 2
+ s.c = 3
+ s.d = 4
+ s.e = 5
+ end
+
+ S = Struct.new(:a, :b, :c, :d, :e)
+ s = S.new
+ foo(s)
+ s = S.new
+ foo(s)
+ [s.a, s.b, s.c, s.d, s.e]
+}
+
+# struct aref too many args
+assert_equal 'ok', %q{
+ def foo(s)
+ s.foo(:bad)
+ end
+
+ s = Struct.new(:foo).new
+ foo(s) rescue :ok
+ foo(s) rescue :ok
+}
+
+# struct aset too many args
+assert_equal 'ok', %q{
+ def foo(s)
+ s.set_foo(123, :bad)
+ end
+
+ s = Struct.new(:foo) do
+ alias :set_foo :foo=
+ end
+ foo(s) rescue :ok
+ foo(s) rescue :ok
+}
+
+# File.join is a cfunc accepting variable arguments as a Ruby array (argc = -2)
+assert_equal 'foo/bar', %q{
+ def foo
+ File.join("foo", "bar")
+ end
+
+ foo
+ foo
+}
+
+# File.join is a cfunc accepting variable arguments as a Ruby array (argc = -2)
+assert_equal '', %q{
+ def foo
+ File.join()
+ end
+
+ foo
+ foo
+}
+
+# Make sure we're correctly reading RStruct's as.ary union for embedded RStructs
+assert_equal '3,12', %q{
+ pt_struct = Struct.new(:x, :y)
+ p = pt_struct.new(3, 12)
+ def pt_inspect(pt)
+ "#{pt.x},#{pt.y}"
+ end
+
+ # Make sure pt_inspect is JITted
+ 10.times { pt_inspect(p) }
+
+ # Make sure it's returning '3,12' instead of e.g. '3,false'
+ pt_inspect(p)
+}
+
+# Regression test for deadlock between branch_stub_hit and ractor_receive_if
+assert_equal '10', %q{
+ r = Ractor.new Ractor.current do |main|
+ main << 1
+ main << 2
+ main << 3
+ main << 4
+ main << 5
+ main << 6
+ main << 7
+ main << 8
+ main << 9
+ main << 10
+ end
+
+ a = []
+ a << Ractor.receive_if{|msg| msg == 10}
+ a << Ractor.receive_if{|msg| msg == 9}
+ a << Ractor.receive_if{|msg| msg == 8}
+ a << Ractor.receive_if{|msg| msg == 7}
+ a << Ractor.receive_if{|msg| msg == 6}
+ a << Ractor.receive_if{|msg| msg == 5}
+ a << Ractor.receive_if{|msg| msg == 4}
+ a << Ractor.receive_if{|msg| msg == 3}
+ a << Ractor.receive_if{|msg| msg == 2}
+ a << Ractor.receive_if{|msg| msg == 1}
+
+ a.length
+}
+
+# checktype
+assert_equal 'false', %q{
+ def function()
+ [1, 2] in [Integer, String]
+ end
+ function()
+}
+
+# opt_send_without_block (VM_METHOD_TYPE_ATTRSET)
+assert_equal 'foo', %q{
+ class Foo
+ attr_writer :foo
+
+ def foo()
+ self.foo = "foo"
+ end
+ end
+ foo = Foo.new
+ foo.foo
+}
+
+# anytostring, intern
+assert_equal 'true', %q{
+ def foo()
+ :"#{true}"
+ end
+ foo()
+}
+
+# toregexp, objtostring
+assert_equal '/true/', %q{
+ def foo()
+ /#{true}/
+ end
+ foo().inspect
+}
+
+# concatstrings, objtostring
+assert_equal '9001', %q{
+ def foo()
+ "#{9001}"
+ end
+ foo()
+}
+
+# opt_send_without_block (VM_METHOD_TYPE_CFUNC)
+assert_equal 'nil', %q{
+ def foo
+ nil.inspect # argc: 0
+ end
+ foo
+}
+assert_equal '4', %q{
+ def foo
+ 2.pow(2) # argc: 1
+ end
+ foo
+}
+assert_equal 'aba', %q{
+ def foo
+ "abc".tr("c", "a") # argc: 2
+ end
+ foo
+}
+assert_equal 'true', %q{
+ def foo
+ respond_to?(:inspect) # argc: -1
+ end
+ foo
+}
+assert_equal '["a", "b"]', %q{
+ def foo
+ "a\nb".lines(chomp: true) # kwargs
+ end
+ foo
+}
+
+# invokebuiltin
+assert_equal '123', %q{
+ def foo(obj)
+ obj.foo = 123
+ end
+
+ struct = Struct.new(:foo)
+ obj = struct.new
+ foo(obj)
+}
+
+# invokebuiltin_delegate
+assert_equal '.', %q{
+ def foo(path)
+ Dir.open(path).path
+ end
+ foo(".")
+}
+
+# opt_invokebuiltin_delegate_leave
+assert_equal '[0]', %q{"\x00".unpack("c")}
+
+# opt_send_without_block (VM_METHOD_TYPE_ISEQ)
+assert_equal '1', %q{
+ def foo = 1
+ def bar = foo
+ bar
+}
+assert_equal '[1, 2, 3]', %q{
+ def foo(a, b) = [1, a, b]
+ def bar = foo(2, 3)
+ bar
+}
+assert_equal '[1, 2, 3, 4, 5, 6]', %q{
+ def foo(a, b, c:, d:, e: 0, f: 6) = [a, b, c, d, e, f]
+ def bar = foo(1, 2, c: 3, d: 4, e: 5)
+ bar
+}
+assert_equal '[1, 2, 3, 4]', %q{
+ def foo(a, b = 2) = [a, b]
+ def bar = foo(1) + foo(3, 4)
+ bar
+}
+
+assert_equal '1', %q{
+ def foo(a) = a
+ def bar = foo(1) { 2 }
+ bar
+}
+assert_equal '[1, 2]', %q{
+ def foo(a, &block) = [a, block.call]
+ def bar = foo(1) { 2 }
+ bar
+}
+
+# opt_send_without_block (VM_METHOD_TYPE_IVAR)
+assert_equal 'foo', %q{
+ class Foo
+ attr_reader :foo
+
+ def initialize
+ @foo = "foo"
+ end
+ end
+ Foo.new.foo
+}
+
+# opt_send_without_block (VM_METHOD_TYPE_OPTIMIZED)
+assert_equal 'foo', %q{
+ Foo = Struct.new(:bar)
+ Foo.new("bar").bar = "foo"
+}
+assert_equal 'foo', %q{
+ Foo = Struct.new(:bar)
+ Foo.new("foo").bar
+}
+
+# getblockparamproxy
+assert_equal 'foo', %q{
+ def foo(&block)
+ block.call
+ end
+ foo { "foo" }
+}
+
+# getblockparam
+assert_equal 'foo', %q{
+ def foo(&block)
+ block
+ end
+ foo { "foo" }.call
+}
+
+assert_equal '[1, 2]', %q{
+ def foo
+ x = [2]
+ [1, *x]
+ end
+
+ foo
+ foo
+}
+
+# respond_to? with changing symbol
+assert_equal 'false', %q{
+ def foo(name)
+ :sym.respond_to?(name)
+ end
+ foo(:to_s)
+ foo(:to_s)
+ foo(:not_exist)
+}
+
+# respond_to? with method being defined
+assert_equal 'true', %q{
+ def foo
+ :sym.respond_to?(:not_yet_defined)
+ end
+ foo
+ foo
+ module Kernel
+ def not_yet_defined = true
+ end
+ foo
+}
+
+# respond_to? with undef method
+assert_equal 'false', %q{
+ module Kernel
+ def to_be_removed = true
+ end
+ def foo
+ :sym.respond_to?(:to_be_removed)
+ end
+ foo
+ foo
+ class Object
+ undef_method :to_be_removed
+ end
+ foo
+}
+
+# respond_to? with respond_to_missing?
+assert_equal 'true', %q{
+ class Foo
+ end
+ def foo(x)
+ x.respond_to?(:bar)
+ end
+ foo(Foo.new)
+ foo(Foo.new)
+ class Foo
+ def respond_to_missing?(*) = true
+ end
+ foo(Foo.new)
+}
+
+# bmethod
+assert_equal '[1, 2, 3]', %q{
+ one = 1
+ define_method(:foo) do
+ one
+ end
+
+ 3.times.map { |i| foo + i }
+}
+
+# return inside bmethod
+assert_equal 'ok', %q{
+ define_method(:foo) do
+ 1.tap { return :ok }
+ end
+
+ foo
+}
+
+# bmethod optional and keywords
+assert_equal '[[1, nil, 2]]', %q{
+ define_method(:opt_and_kwargs) do |a = {}, b: nil, c: nil|
+ [a, b, c]
+ end
+
+ 5.times.map { opt_and_kwargs(1, c: 2) }.uniq
+}
+
+# bmethod with forwarded block
+assert_equal '2', %q{
+ define_method(:foo) do |&block|
+ block.call
+ end
+
+ def bar(&block)
+ foo(&block)
+ end
+
+ bar { 1 }
+ bar { 2 }
+}
+
+# bmethod with forwarded block and arguments
+assert_equal '5', %q{
+ define_method(:foo) do |n, &block|
+ n + block.call
+ end
+
+ def bar(n, &block)
+ foo(n, &block)
+ end
+
+ bar(0) { 1 }
+ bar(3) { 2 }
+}
+
+# bmethod with forwarded unwanted block
+assert_equal '1', %q{
+ one = 1
+ define_method(:foo) do
+ one
+ end
+
+ def bar(&block)
+ foo(&block)
+ end
+
+ bar { }
+ bar { }
+}
+
+# test for return stub lifetime issue
+assert_equal '1', %q{
+ def foo(n)
+ if n == 2
+ return 1.times { Object.define_method(:foo) {} }
+ end
+
+ foo(n + 1)
+ end
+
+ foo(1)
+}
+
+# case-when with redefined ===
+assert_equal 'ok', %q{
+ class Symbol
+ def ===(a)
+ true
+ end
+ end
+
+ def cw(arg)
+ case arg
+ when :b
+ :ok
+ when 4
+ :ng
+ end
+ end
+
+ cw(4)
+}
+
+assert_equal 'threw', %q{
+ def foo(args)
+ wrap(*args)
+ rescue ArgumentError
+ 'threw'
+ end
+
+ def wrap(a)
+ [a]
+ end
+
+ foo([Hash.ruby2_keywords_hash({})])
+}
+
+assert_equal 'threw', %q{
+ # C call
+ def bar(args)
+ Array(*args)
+ rescue ArgumentError
+ 'threw'
+ end
+
+ bar([Hash.ruby2_keywords_hash({})])
+}
+
+# Test instance_of? and is_a?
+assert_equal 'true', %q{
+ 1.instance_of?(Integer) && 1.is_a?(Integer)
+}
+
+# Test instance_of? and is_a? for singleton classes
+assert_equal 'true', %q{
+ a = []
+ def a.test = :test
+ a.instance_of?(Array) && a.is_a?(Array)
+}
+
+# Test instance_of? for singleton_class
+# Yes this does really return false
+assert_equal 'false', %q{
+ a = []
+ def a.test = :test
+ a.instance_of?(a.singleton_class)
+}
+
+# Test is_a? for singleton_class
+assert_equal 'true', %q{
+ a = []
+ def a.test = :test
+ a.is_a?(a.singleton_class)
+}
+
+# Test send with splat to a cfunc
+assert_equal 'true', %q{
+ 1.send(:==, 1, *[])
+}
+
+# Test empty splat with cfunc
+assert_equal '2', %q{
+ def foo
+ Integer.sqrt(4, *[])
+ end
+ # call twice to deal with constant exiting
+ foo
+ foo
+}
+
+# Test non-empty splat with cfunc
+assert_equal 'Hello World', %q{
+ def bar
+ args = ["Hello "]
+ greeting = +"World"
+ greeting.insert(0, *args)
+ greeting
+ end
+ bar
+}
+
+# Regression: this creates a temp stack with > 127 elements
+assert_normal_exit %q{
+ def foo(a)
+ [
+ a, a, a, a, a, a, a, a, a, a,
+ a, a, a, a, a, a, a, a, a, a,
+ a, a, a, a, a, a, a, a, a, a,
+ a, a, a, a, a, a, a, a, a, a,
+ a, a, a, a, a, a, a, a, a, a,
+ a, a, a, a, a, a, a, a, a, a,
+ a, a, a, a, a, a, a, a, a, a,
+ a, a, a, a, a, a, a, a, a, a,
+ a, a, a, a, a, a, a, a, a, a,
+ a, a, a, a, a, a, a, a, a, a,
+ a, a, a, a, a, a, a, a, a, a,
+ a, a, a, a, a, a, a, a, a, a,
+ a, a, a, a, a, a, a, a,
+ ]
+ end
+
+ def entry
+ foo(1)
+ end
+
+ entry
+}
+
+# Test that splat and rest combined
+# properly dupe the array
+assert_equal "[]", %q{
+ def foo(*rest)
+ rest << 1
+ end
+
+ def test(splat)
+ foo(*splat)
+ end
+
+ EMPTY = []
+ custom = Object.new
+ def custom.to_a
+ EMPTY
+ end
+
+ test(custom)
+ test(custom)
+ EMPTY
+}
+
+# Rest with send
+assert_equal '[1, 2, 3]', %q{
+ def bar(x, *rest)
+ rest.insert(0, x)
+ end
+ send(:bar, 1, 2, 3)
+}
+
+# Fix splat block arg bad compilation
+assert_equal "foo", %q{
+ def literal(*args, &block)
+ s = ''.dup
+ literal_append(s, *args, &block)
+ s
+ end
+
+ def literal_append(sql, v)
+ sql << v
+ end
+
+ literal("foo")
+}
+
+# regression test for accidentally having a parameter truncated
+# due to Rust/C signature mismatch. Used to crash with
+# > [BUG] rb_vm_insn_addr2insn: invalid insn address ...
+# or
+# > ... `Err` value: TryFromIntError(())'
+assert_normal_exit %q{
+ n = 16384
+ eval(
+ "def foo(arg); " + "_=arg;" * n + '_=1;' + "Object; end"
+ )
+ foo 1
+}
+
+# Regression test for CantCompile not using starting_ctx
+assert_normal_exit %q{
+ class Integer
+ def ===(other)
+ false
+ end
+ end
+
+ def my_func(x)
+ case x
+ when 1
+ 1
+ when 2
+ 2
+ else
+ 3
+ end
+ end
+
+ my_func(1)
+}
+
+# Regression test for CantCompile not using starting_ctx
+assert_equal "ArgumentError", %q{
+ def literal(*args, &block)
+ s = ''.dup
+ args = [1, 2, 3]
+ literal_append(s, *args, &block)
+ s
+ end
+
+ def literal_append(sql, v)
+ [sql.inspect, v.inspect]
+ end
+
+ begin
+ literal("foo")
+ rescue ArgumentError
+ "ArgumentError"
+ end
+}
+
+# Rest with block
+# Simplified code from railsbench
+assert_equal '[{"/a"=>"b", :as=>:c, :via=>:post}, [], nil]', %q{
+ def match(path, *rest, &block)
+ [path, rest, block]
+ end
+
+ def map_method(method, args, &block)
+ options = args.last
+ args.pop
+ options[:via] = method
+ match(*args, options, &block)
+ end
+
+ def post(*args, &block)
+ map_method(:post, args, &block)
+ end
+
+ post "/a" => "b", as: :c
+}
+
+# Test rest and kw_args
+assert_equal '[true, true, true, true]', %q{
+ def my_func(*args, base: nil, sort: true)
+ [args, base, sort]
+ end
+
+ def calling_my_func
+ results = []
+ results << (my_func("test") == [["test"], nil, true])
+ results << (my_func("test", base: :base) == [["test"], :base, true])
+ results << (my_func("test", sort: false) == [["test"], nil, false])
+ results << (my_func("test", "other", base: :base) == [["test", "other"], :base, true])
+ results
+ end
+ calling_my_func
+}
+
+# Test Integer#[] with 2 args
+assert_equal '0', %q{
+ 3[0, 0]
+}
+
+# unspecified_bits + checkkeyword
+assert_equal '2', %q{
+ def callee = 1
+
+ # checkkeyword should see unspecified_bits=0 (use bar), not Integer 1 (set bar = foo).
+ def foo(foo, bar: foo) = bar
+
+ def entry(&block)
+ # write 1 at stack[3]. Calling #callee spills stack[3].
+ 1 + (1 + (1 + (1 + callee)))
+ # &block is written to a register instead of stack[3]. When &block is popped and
+ # unspecified_bits is pushed, it must be written to stack[3], not to a register.
+ foo(1, bar: 2, &block)
+ end
+
+ entry # call branch_stub_hit (spill temps)
+ entry # doesn't call branch_stub_hit (not spill temps)
+}
+
+# Test rest and optional_params
+assert_equal '[true, true, true, true]', %q{
+ def my_func(stuff, base=nil, sort=true, *args)
+ [stuff, base, sort, args]
+ end
+
+ def calling_my_func
+ results = []
+ results << (my_func("test") == ["test", nil, true, []])
+ results << (my_func("test", :base) == ["test", :base, true, []])
+ results << (my_func("test", :base, false) == ["test", :base, false, []])
+ results << (my_func("test", :base, false, "other", "other") == ["test", :base, false, ["other", "other"]])
+ results
+ end
+ calling_my_func
+}
+
+# Test rest and optional_params and splat
+assert_equal '[true, true, true, true, true]', %q{
+ def my_func(stuff, base=nil, sort=true, *args)
+ [stuff, base, sort, args]
+ end
+
+ def calling_my_func
+ results = []
+ splat = ["test"]
+ results << (my_func(*splat) == ["test", nil, true, []])
+ splat = [:base]
+ results << (my_func("test", *splat) == ["test", :base, true, []])
+ splat = [:base, false]
+ results << (my_func("test", *splat) == ["test", :base, false, []])
+ splat = [:base, false, "other", "other"]
+ results << (my_func("test", *splat) == ["test", :base, false, ["other", "other"]])
+ splat = ["test", :base, false, "other", "other"]
+ results << (my_func(*splat) == ["test", :base, false, ["other", "other"]])
+ results
+ end
+ calling_my_func
+}
+
+# Regression test: rest and optional and splat
+assert_equal 'true', %q{
+ def my_func(base=nil, *args)
+ [base, args]
+ end
+
+ def calling_my_func
+ array = []
+ my_func(:base, :rest1, *array) == [:base, [:rest1]]
+ end
+
+ calling_my_func
+}
+
+# Fix failed case for large splat
+assert_equal 'true', %q{
+ def d(a, b=:b)
+ end
+
+ def calling_func
+ ary = 1380888.times;
+ d(*ary)
+ end
+ begin
+ calling_func
+ rescue ArgumentError
+ true
+ end
+} unless rjit_enabled? # Not yet working on RJIT
+
+# Regression test: register allocator on expandarray
+assert_equal '[]', %q{
+ func = proc { [] }
+ proc do
+ _x, _y = func.call
+ end.call
+}
+
+# Catch TAG_BREAK in a non-FINISH frame with JIT code
+assert_equal '1', %q{
+ def entry
+ catch_break
+ end
+
+ def catch_break
+ while_true do
+ break
+ end
+ 1
+ end
+
+ def while_true
+ while true
+ yield
+ end
+ end
+
+ entry
+}
+
+assert_equal '6', %q{
+ class Base
+ def number = 1 + yield
+ end
+
+ class Sub < Base
+ def number = super + 2
+ end
+
+ Sub.new.number { 3 }
+}
+
+# Integer multiplication and overflow
+assert_equal '[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]', %q{
+ def foo(a, b)
+ a * b
+ end
+
+ r1 = foo(2, 3)
+ r2 = foo(2, -3)
+ r3 = foo(2 << 40, 2 << 41)
+ r4 = foo(2 << 40, -2 << 41)
+ r5 = foo(1 << 62, 1 << 62)
+
+ [r1, r2, r3, r4, r5]
+}
+
+# Integer multiplication and overflow (minimized regression test from test-basic)
+assert_equal '8515157028618240000', %q{2128789257154560000 * 4}
+
+# Inlined method calls
+assert_equal 'nil', %q{
+ def putnil = nil
+ def entry = putnil
+ entry.inspect
+}
+assert_equal '1', %q{
+ def putobject_1 = 1
+ def entry = putobject_1
+ entry
+}
+assert_equal 'false', %q{
+ def putobject(_unused_arg1) = false
+ def entry = putobject(nil)
+ entry
+}
+assert_equal 'true', %q{
+ def entry = yield
+ entry { true }
+}
+assert_equal 'sym', %q{
+ def entry = :sym.to_sym
+ entry
+}
+
+assert_normal_exit %q{
+ ivars = 1024.times.map { |i| "@iv_#{i} = #{i}\n" }.join
+ Foo = Class.new
+ Foo.class_eval "def initialize() #{ivars} end"
+ Foo.new
+}
+
+assert_equal '0', %q{
+ def spill
+ 1.to_i # not inlined
+ end
+
+ def inline(_stack1, _stack2, _stack3, _stack4, _stack5)
+ 0 # inlined
+ end
+
+ def entry
+ # RegTemps is 00111110 prior to the #inline call.
+ # Its return value goes to stack_idx=0, which conflicts with stack_idx=5.
+ inline(spill, 2, 3, 4, 5)
+ end
+
+ entry
+}
+
+# Integer succ and overflow
+assert_equal '[2, 4611686018427387904]', %q{
+ [1.succ, 4611686018427387903.succ]
+}
+
+# Integer right shift
+assert_equal '[0, 1, -4]', %q{
+ [0 >> 1, 2 >> 1, -7 >> 1]
+}
+
+# Integer XOR
+assert_equal '[0, 0, 4]', %q{
+ [0 ^ 0, 1 ^ 1, 7 ^ 3]
+}
+
+assert_equal '[nil, "yield"]', %q{
+ def defined_yield = defined?(yield)
+ [defined_yield, defined_yield {}]
+}
+
+# splat with ruby2_keywords into rest parameter
+assert_equal '[[{:a=>1}], {}]', %q{
+ ruby2_keywords def foo(*args) = args
+
+ def bar(*args, **kw) = [args, kw]
+
+ def pass_bar(*args) = bar(*args)
+
+ def body
+ args = foo(a: 1)
+ pass_bar(*args)
+ end
+
+ body
+}
+
+# concatarray
+assert_equal '[1, 2]', %q{
+ def foo(a, b) = [a, b]
+ arr = [2]
+ foo(*[1], *arr)
+}
+
+# pushtoarray
+assert_equal '[1, 2]', %q{
+ def foo(a, b) = [a, b]
+ arr = [1]
+ foo(*arr, 2)
+}
+
+# pop before fallback
+assert_normal_exit %q{
+ class Foo
+ attr_reader :foo
+
+ def try = foo(0, &nil)
+ end
+
+ Foo.new.try
+}
+
+# a kwrest case
+assert_equal '[1, 2, {:complete=>false}]', %q{
+ def rest(foo: 1, bar: 2, **kwrest)
+ [foo, bar, kwrest]
+ end
+
+ def callsite = rest(complete: false)
+
+ callsite
+}
+
+# splat+kw_splat+opt+rest
+assert_equal '[1, []]', %q{
+ def opt_rest(a = 0, *rest) = [a, rest]
+
+ def call_site(args) = opt_rest(*args, **nil)
+
+ call_site([1])
+}
+
+# splat and nil kw_splat
+assert_equal 'ok', %q{
+ def identity(x) = x
+
+ def splat_nil_kw_splat(args) = identity(*args, **nil)
+
+ splat_nil_kw_splat([:ok])
+}
+
+# empty splat and kwsplat into leaf builtins
+assert_equal '[1, 1, 1]', %q{
+ empty = []
+ [1.abs(*empty), 1.abs(**nil), 1.bit_length(*empty, **nil)]
+}
+
+# splat into C methods with -1 arity
+assert_equal '[[1, 2, 3], [0, 2, 3], [1, 2, 3], [2, 2, 3], [], [], [{}]]', %q{
+ class Foo < Array
+ def push(args) = super(1, *args)
+ end
+
+ def test_cfunc_vargs_splat(sub_instance, array_class, empty_kw_hash)
+ splat = [2, 3]
+ kw_splat = [empty_kw_hash]
+ [
+ sub_instance.push(splat),
+ array_class[0, *splat, **nil],
+ array_class[1, *splat, &nil],
+ array_class[2, *splat, **nil, &nil],
+ array_class.send(:[], *kw_splat),
+ # kw_splat disables keywords hash handling
+ array_class[*kw_splat],
+ array_class[*kw_splat, **nil],
+ ]
+ end
+
+ test_cfunc_vargs_splat(Foo.new, Array, Hash.ruby2_keywords_hash({}))
+}
+
+# Class#new (arity=-1), splat, and ruby2_keywords
+assert_equal '[0, {1=>1}]', %q{
+ class KwInit
+ attr_reader :init_args
+ def initialize(x = 0, **kw)
+ @init_args = [x, kw]
+ end
+ end
+
+ def test(klass, args)
+ klass.new(*args).init_args
+ end
+
+ test(KwInit, [Hash.ruby2_keywords_hash({1 => 1})])
+}
+
+# Chilled string setivar trigger warning
+assert_equal 'literal string will be frozen in the future', %q{
+ Warning[:deprecated] = true
+ $VERBOSE = true
+ $warning = "no-warning"
+ module ::Warning
+ def self.warn(message)
+ $warning = message.split("warning: ").last.strip
+ end
+ end
+
+ class String
+ def setivar!
+ @ivar = 42
+ end
+ end
+
+ def setivar!(str)
+ str.setivar!
+ end
+
+ 10.times { setivar!("mutable".dup) }
+ 10.times do
+ setivar!("frozen".freeze)
+ rescue FrozenError
+ end
+
+ setivar!("chilled") # Emit warning
+ $warning
+}
+
+# arity=-2 cfuncs
+assert_equal '["", "1/2", [0, [:ok, 1]]]', %q{
+ def test_cases(file, chain)
+ new_chain = chain.allocate # to call initialize directly
+ new_chain.send(:initialize, [0], ok: 1)
+
+ [
+ file.join,
+ file.join("1", "2"),
+ new_chain.to_a,
+ ]
+ end
+
+ test_cases(File, Enumerator::Chain)
+}
+
+# singleton class should invalidate Type::CString assumption
+assert_equal 'foo', %q{
+ def define_singleton(str, define)
+ if define
+ # Wrap a C method frame to avoid exiting JIT code on defineclass
+ [nil].reverse_each do
+ class << str
+ def +(_)
+ "foo"
+ end
+ end
+ end
+ end
+ "bar"
+ end
+
+ def entry(define)
+ str = ""
+ # When `define` is false, #+ compiles to rb_str_plus() without a class guard.
+ # When the code is reused with `define` is true, the class of `str` is changed
+ # to a singleton class, so the block should be invalidated.
+ str + define_singleton(str, define)
+ end
+
+ entry(false)
+ entry(true)
+}
+
+assert_equal '[:ok, :ok, :ok]', %q{
+ def identity(x) = x
+ def foo(x, _) = x
+ def bar(_, _, _, _, x) = x
+
+ def tests
+ [
+ identity(:ok),
+ foo(:ok, 2),
+ bar(1, 2, 3, 4, :ok),
+ ]
+ end
+
+ tests
+}
+
+# regression test for invalidating an empty block
+assert_equal '0', %q{
+ def foo = (* = 1).pred
+
+ foo # compile it
+
+ class Integer
+ def to_ary = [] # invalidate
+ end
+
+ foo # try again
+} unless rjit_enabled? # doesn't work on RJIT
+
+# test integer left shift with constant rhs
+assert_equal [0x80000000000, 'a+', :ok].inspect, %q{
+ def shift(val) = val << 43
+
+ def tests
+ int = shift(1)
+ str = shift("a")
+
+ Integer.define_method(:<<) { |_| :ok }
+ redef = shift(1)
+
+ [int, str, redef]
+ end
+
+ tests
+}
+
+# test String#stebyte with arguments that need conversion
+assert_equal "abc", %q{
+ str = +"a00"
+ def change_bytes(str, one, two)
+ str.setbyte(one, "b".ord)
+ str.setbyte(2, two)
+ end
+
+ to_int_1 = Object.new
+ to_int_99 = Object.new
+ def to_int_1.to_int = 1
+ def to_int_99.to_int = 99
+
+ change_bytes(str, to_int_1, to_int_99)
+ str
+}
+
+# test --yjit-verify-ctx for arrays with a singleton class
+assert_equal "ok", %q{
+ class Array
+ def foo
+ self.singleton_class.define_method(:first) { :ok }
+ first
+ end
+ end
+
+ def test = [].foo
+
+ test
+}
+
+assert_equal '["raised", "Module", "Object"]', %q{
+ def foo(obj)
+ obj.superclass.name
+ end
+
+ ret = []
+
+ begin
+ foo(Class.allocate)
+ rescue TypeError
+ ret << 'raised'
+ end
+
+ ret += [foo(Class), foo(Class.new)]
+}
diff --git a/bootstraptest/test_yjit_rust_port.rb b/bootstraptest/test_yjit_rust_port.rb
new file mode 100644
index 0000000000..e399e0e49e
--- /dev/null
+++ b/bootstraptest/test_yjit_rust_port.rb
@@ -0,0 +1,422 @@
+# Simple tests that we know we can pass
+# To keep track of what we got working during the Rust port
+# And avoid breaking/losing functionality
+#
+# Say "Thread" here to dodge WASM CI check. We use ractors here
+# which WASM doesn't support and it only greps for "Thread".
+
+# Test for opt_mod
+assert_equal '2', %q{
+ def mod(a, b)
+ a % b
+ end
+
+ mod(7, 5)
+ mod(7, 5)
+}
+
+# Test for opt_mult
+assert_equal '12', %q{
+ def mult(a, b)
+ a * b
+ end
+
+ mult(6, 2)
+ mult(6, 2)
+}
+
+# Test for opt_div
+assert_equal '3', %q{
+ def div(a, b)
+ a / b
+ end
+
+ div(6, 2)
+ div(6, 2)
+}
+
+assert_equal '5', %q{
+ def plus(a, b)
+ a + b
+ end
+
+ plus(3, 2)
+}
+
+assert_equal '1', %q{
+ def foo(a, b)
+ a - b
+ end
+
+ foo(3, 2)
+}
+
+assert_equal 'true', %q{
+ def foo(a, b)
+ a < b
+ end
+
+ foo(2, 3)
+}
+
+# Bitwise left shift
+assert_equal '4', %q{
+ def foo(a, b)
+ 1 << 2
+ end
+
+ foo(1, 2)
+}
+
+assert_equal '-7', %q{
+ def foo(a, b)
+ -7
+ end
+
+ foo(1, 2)
+}
+
+# Putstring
+assert_equal 'foo', %q{
+ def foo(a, b)
+ "foo"
+ end
+
+ foo(1, 2)
+}
+
+assert_equal '-6', %q{
+ def foo(a, b)
+ a + -7
+ end
+
+ foo(1, 2)
+}
+
+assert_equal 'true', %q{
+ def foo(a, b)
+ a == b
+ end
+
+ foo(3, 3)
+}
+
+assert_equal 'true', %q{
+ def foo(a, b)
+ a < b
+ end
+
+ foo(3, 5)
+}
+
+assert_equal '777', %q{
+ def foo(a)
+ if a
+ 777
+ else
+ 333
+ end
+ end
+
+ foo(true)
+}
+
+assert_equal '5', %q{
+ def foo(a, b)
+ while a < b
+ a += 1
+ end
+ a
+ end
+
+ foo(1, 5)
+}
+
+# opt_aref
+assert_equal '2', %q{
+ def foo(a, b)
+ a[b]
+ end
+
+ foo([0, 1, 2], 2)
+}
+
+# Simple function calls with 0, 1, 2 arguments
+assert_equal '-2', %q{
+ def bar()
+ -2
+ end
+
+ def foo(a, b)
+ bar()
+ end
+
+ foo(3, 2)
+}
+assert_equal '2', %q{
+ def bar(a)
+ a
+ end
+
+ def foo(a, b)
+ bar(b)
+ end
+
+ foo(3, 2)
+}
+assert_equal '1', %q{
+ def bar(a, b)
+ a - b
+ end
+
+ def foo(a, b)
+ bar(a, b)
+ end
+
+ foo(3, 2)
+}
+
+# Regression test for assembler bug
+assert_equal '1', %q{
+ def check_index(index)
+ if 0x40000000 < index
+ return -1
+ end
+ 1
+ end
+
+ check_index 2
+}
+
+# Setivar test
+assert_equal '2', %q{
+ class Klass
+ attr_accessor :a
+
+ def set()
+ @a = 2
+ end
+
+ def get()
+ @a
+ end
+ end
+
+ o = Klass.new
+ o.set()
+ o.a
+}
+
+# Regression for putobject bug
+assert_equal '1.5', %q{
+ def foo(x)
+ x
+ end
+
+ def bar
+ foo(1.5)
+ end
+
+ bar()
+}
+
+# Getivar with an extended ivar table
+assert_equal '3', %q{
+ class Foo
+ def initialize
+ @x1 = 1
+ @x2 = 1
+ @x3 = 1
+ @x4 = 3
+ end
+
+ def bar
+ @x4
+ end
+ end
+
+ f = Foo.new
+ f.bar
+}
+
+assert_equal 'true', %q{
+ x = [[false, true]]
+ for i, j in x
+ ;
+ end
+ j
+}
+
+# Regression for getivar
+assert_equal '[nil]', %q{
+ [TrueClass].each do |klass|
+ klass.class_eval("def foo = @foo")
+ end
+
+ [true].map do |instance|
+ instance.foo
+ end
+}
+
+# Regression for send
+assert_equal 'ok', %q{
+ def bar(baz: 2)
+ baz
+ end
+
+ def foo
+ bar(1, baz: 123)
+ end
+
+ begin
+ foo
+ foo
+ rescue ArgumentError => e
+ print "ok"
+ end
+}
+
+# Array access regression test
+assert_equal '[0, 1, 2, 3, 4, 5]', %q{
+ def expandarray_useless_splat
+ arr = [0, 1, 2, 3, 4, 5]
+ a, * = arr
+ end
+
+ expandarray_useless_splat
+}
+
+# Make sure we're correctly reading RStruct's as.ary union for embedded RStructs
+assert_equal '3,12', %q{
+ pt_struct = Struct.new(:x, :y)
+ p = pt_struct.new(3, 12)
+ def pt_inspect(pt)
+ "#{pt.x},#{pt.y}"
+ end
+
+ # Make sure pt_inspect is JITted
+ 10.times { pt_inspect(p) }
+
+ # Make sure it's returning '3,12' instead of e.g. '3,false'
+ pt_inspect(p)
+}
+
+assert_equal '2', %q{
+ def foo(s)
+ s.foo
+ end
+
+ S = Struct.new(:foo)
+ foo(S.new(1))
+ foo(S.new(2))
+}
+
+# Try to compile new method while OOM
+assert_equal 'ok', %q{
+ def foo
+ :ok
+ end
+
+ RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT)
+
+ foo
+}
+
+# test hitting a branch stub when out of memory
+assert_equal 'ok', %q{
+ def nimai(jita)
+ if jita
+ :ng
+ else
+ :ok
+ end
+ end
+
+ nimai(true)
+ nimai(true)
+
+ RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT)
+
+ nimai(false)
+}
+
+# Ractor.current returns a current ractor
+assert_equal 'Ractor', %q{
+ Ractor.current.class
+}
+
+# Ractor.new returns new Ractor
+assert_equal 'Ractor', %q{
+ Ractor.new{}.class
+}
+
+# Ractor.allocate is not supported
+assert_equal "[:ok, :ok]", %q{
+ rs = []
+ begin
+ Ractor.allocate
+ rescue => e
+ rs << :ok if e.message == 'allocator undefined for Ractor'
+ end
+
+ begin
+ Ractor.new{}.dup
+ rescue
+ rs << :ok if e.message == 'allocator undefined for Ractor'
+ end
+
+ rs
+}
+
+# A return value of a Ractor block will be a message from the Ractor.
+assert_equal 'ok', %q{
+ # join
+ r = Ractor.new do
+ 'ok'
+ end
+ r.take
+}
+
+# Passed arguments to Ractor.new will be a block parameter
+# The values are passed with Ractor-communication pass.
+assert_equal 'ok', %q{
+ # ping-pong with arg
+ r = Ractor.new 'ok' do |msg|
+ msg
+ end
+ r.take
+}
+
+# Pass multiple arguments to Ractor.new
+assert_equal 'ok', %q{
+ # ping-pong with two args
+ r = Ractor.new 'ping', 'pong' do |msg, msg2|
+ [msg, msg2]
+ end
+ 'ok' if r.take == ['ping', 'pong']
+}
+
+# Ractor#send passes an object with copy to a Ractor
+# and Ractor.receive in the Ractor block can receive the passed value.
+assert_equal 'ok', %q{
+ r = Ractor.new do
+ msg = Ractor.receive
+ end
+ r.send 'ok'
+ r.take
+}
+
+assert_equal '[1, 2, 3]', %q{
+ def foo(arr)
+ arr << 1
+ arr << 2
+ arr << 3
+ arr
+ end
+
+ def bar()
+ foo([])
+ end
+
+ bar()
+}