summaryrefslogtreecommitdiff
path: root/test/ruby
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby')
-rw-r--r--test/ruby/box/a.1_1_0.rb17
-rw-r--r--test/ruby/box/a.1_2_0.rb17
-rw-r--r--test/ruby/box/a.rb15
-rw-r--r--test/ruby/box/autoloading.rb8
-rw-r--r--test/ruby/box/blank.rb2
-rw-r--r--test/ruby/box/blank1.rb2
-rw-r--r--test/ruby/box/blank2.rb2
-rw-r--r--test/ruby/box/box.rb10
-rw-r--r--test/ruby/box/call_proc.rb5
-rw-r--r--test/ruby/box/call_toplevel.rb8
-rw-r--r--test/ruby/box/consts.rb148
-rw-r--r--test/ruby/box/define_toplevel.rb5
-rw-r--r--test/ruby/box/global_vars.rb37
-rw-r--r--test/ruby/box/instance_variables.rb21
-rw-r--r--test/ruby/box/line_splitter.rb9
-rw-r--r--test/ruby/box/load_path.rb26
-rw-r--r--test/ruby/box/open_class_with_include.rb31
-rw-r--r--test/ruby/box/proc_callee.rb14
-rw-r--r--test/ruby/box/proc_caller.rb5
-rw-r--r--test/ruby/box/procs.rb64
-rw-r--r--test/ruby/box/raise.rb3
-rw-r--r--test/ruby/box/returns_proc.rb12
-rw-r--r--test/ruby/box/singleton_methods.rb65
-rw-r--r--test/ruby/box/string_ext.rb13
-rw-r--r--test/ruby/box/string_ext_caller.rb5
-rw-r--r--test/ruby/box/string_ext_calling.rb1
-rw-r--r--test/ruby/box/string_ext_eval_caller.rb12
-rw-r--r--test/ruby/box/top_level.rb33
-rw-r--r--test/ruby/enc/test_emoji_breaks.rb2
-rw-r--r--test/ruby/sentence.rb2
-rw-r--r--test/ruby/test_allocation.rb85
-rw-r--r--test/ruby/test_array.rb66
-rw-r--r--test/ruby/test_ast.rb122
-rw-r--r--test/ruby/test_autoload.rb30
-rw-r--r--test/ruby/test_backtrace.rb12
-rw-r--r--test/ruby/test_bignum.rb46
-rw-r--r--test/ruby/test_box.rb874
-rw-r--r--test/ruby/test_call.rb48
-rw-r--r--test/ruby/test_class.rb124
-rw-r--r--test/ruby/test_compile_prism.rb66
-rw-r--r--test/ruby/test_data.rb9
-rw-r--r--test/ruby/test_encoding.rb44
-rw-r--r--test/ruby/test_enumerator.rb28
-rw-r--r--test/ruby/test_env.rb512
-rw-r--r--test/ruby/test_exception.rb29
-rw-r--r--test/ruby/test_fiber.rb2
-rw-r--r--test/ruby/test_file.rb19
-rw-r--r--test/ruby/test_file_exhaustive.rb29
-rw-r--r--test/ruby/test_float.rb4
-rw-r--r--test/ruby/test_frozen.rb16
-rw-r--r--test/ruby/test_gc.rb100
-rw-r--r--test/ruby/test_gc_compact.rb28
-rw-r--r--test/ruby/test_hash.rb47
-rw-r--r--test/ruby/test_integer.rb4
-rw-r--r--test/ruby/test_io.rb103
-rw-r--r--test/ruby/test_io_buffer.rb275
-rw-r--r--test/ruby/test_io_m17n.rb41
-rw-r--r--test/ruby/test_iseq.rb96
-rw-r--r--test/ruby/test_keyword.rb34
-rw-r--r--test/ruby/test_lambda.rb6
-rw-r--r--test/ruby/test_literal.rb5
-rw-r--r--test/ruby/test_m17n.rb118
-rw-r--r--test/ruby/test_marshal.rb36
-rw-r--r--test/ruby/test_math.rb32
-rw-r--r--test/ruby/test_memory_view.rb2
-rw-r--r--test/ruby/test_method.rb15
-rw-r--r--test/ruby/test_module.rb98
-rw-r--r--test/ruby/test_nomethod_error.rb30
-rw-r--r--test/ruby/test_numeric.rb34
-rw-r--r--test/ruby/test_object.rb94
-rw-r--r--test/ruby/test_object_id.rb303
-rw-r--r--test/ruby/test_objectspace.rb52
-rw-r--r--test/ruby/test_optimization.rb19
-rw-r--r--test/ruby/test_parse.rb19
-rw-r--r--test/ruby/test_pattern_matching.rb42
-rw-r--r--test/ruby/test_proc.rb211
-rw-r--r--test/ruby/test_process.rb41
-rw-r--r--test/ruby/test_ractor.rb192
-rw-r--r--test/ruby/test_range.rb7
-rw-r--r--test/ruby/test_rational.rb10
-rw-r--r--test/ruby/test_refinement.rb47
-rw-r--r--test/ruby/test_regexp.rb72
-rw-r--r--test/ruby/test_require.rb46
-rw-r--r--test/ruby/test_rubyoptions.rb93
-rw-r--r--test/ruby/test_set.rb137
-rw-r--r--test/ruby/test_settracefunc.rb212
-rw-r--r--test/ruby/test_shapes.rb257
-rw-r--r--test/ruby/test_signal.rb8
-rw-r--r--test/ruby/test_sleep.rb18
-rw-r--r--test/ruby/test_string.rb336
-rw-r--r--test/ruby/test_struct.rb10
-rw-r--r--test/ruby/test_super.rb15
-rw-r--r--test/ruby/test_syntax.rb92
-rw-r--r--test/ruby/test_thread.rb63
-rw-r--r--test/ruby/test_thread_cv.rb2
-rw-r--r--test/ruby/test_thread_queue.rb14
-rw-r--r--test/ruby/test_time_tz.rb1
-rw-r--r--test/ruby/test_transcode.rb87
-rw-r--r--test/ruby/test_variable.rb115
-rw-r--r--test/ruby/test_vm_dump.rb5
-rw-r--r--test/ruby/test_weakmap.rb27
-rw-r--r--test/ruby/test_yield.rb2
-rw-r--r--test/ruby/test_yjit.rb59
-rw-r--r--test/ruby/test_zjit.rb4063
104 files changed, 9665 insertions, 969 deletions
diff --git a/test/ruby/box/a.1_1_0.rb b/test/ruby/box/a.1_1_0.rb
new file mode 100644
index 0000000000..0322585097
--- /dev/null
+++ b/test/ruby/box/a.1_1_0.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class BOX_A
+ VERSION = "1.1.0"
+
+ def yay
+ "yay #{VERSION}"
+ end
+end
+
+module BOX_B
+ VERSION = "1.1.0"
+
+ def self.yay
+ "yay_b1"
+ end
+end
diff --git a/test/ruby/box/a.1_2_0.rb b/test/ruby/box/a.1_2_0.rb
new file mode 100644
index 0000000000..29813ea57b
--- /dev/null
+++ b/test/ruby/box/a.1_2_0.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class BOX_A
+ VERSION = "1.2.0"
+
+ def yay
+ "yay #{VERSION}"
+ end
+end
+
+module BOX_B
+ VERSION = "1.2.0"
+
+ def self.yay
+ "yay_b1"
+ end
+end
diff --git a/test/ruby/box/a.rb b/test/ruby/box/a.rb
new file mode 100644
index 0000000000..26a622c92b
--- /dev/null
+++ b/test/ruby/box/a.rb
@@ -0,0 +1,15 @@
+class BOX_A
+ FOO = "foo_a1"
+
+ def yay
+ "yay_a1"
+ end
+end
+
+module BOX_B
+ BAR = "bar_b1"
+
+ def self.yay
+ "yay_b1"
+ end
+end
diff --git a/test/ruby/box/autoloading.rb b/test/ruby/box/autoloading.rb
new file mode 100644
index 0000000000..cba57ab377
--- /dev/null
+++ b/test/ruby/box/autoloading.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+autoload :BOX_A, File.join(__dir__, 'a.1_1_0')
+BOX_A.new.yay
+
+module BOX_B
+ autoload :BAR, File.join(__dir__, 'a')
+end
diff --git a/test/ruby/box/blank.rb b/test/ruby/box/blank.rb
new file mode 100644
index 0000000000..6d201b0966
--- /dev/null
+++ b/test/ruby/box/blank.rb
@@ -0,0 +1,2 @@
+module Blank1
+end
diff --git a/test/ruby/box/blank1.rb b/test/ruby/box/blank1.rb
new file mode 100644
index 0000000000..6d201b0966
--- /dev/null
+++ b/test/ruby/box/blank1.rb
@@ -0,0 +1,2 @@
+module Blank1
+end
diff --git a/test/ruby/box/blank2.rb b/test/ruby/box/blank2.rb
new file mode 100644
index 0000000000..ba38c1d6db
--- /dev/null
+++ b/test/ruby/box/blank2.rb
@@ -0,0 +1,2 @@
+module Blank2
+end
diff --git a/test/ruby/box/box.rb b/test/ruby/box/box.rb
new file mode 100644
index 0000000000..3b7da14e9d
--- /dev/null
+++ b/test/ruby/box/box.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+BOX1 = Ruby::Box.new
+BOX1.require_relative('a.1_1_0')
+
+def yay
+ BOX1::BOX_B::yay
+end
+
+yay
diff --git a/test/ruby/box/call_proc.rb b/test/ruby/box/call_proc.rb
new file mode 100644
index 0000000000..8acf538fc1
--- /dev/null
+++ b/test/ruby/box/call_proc.rb
@@ -0,0 +1,5 @@
+module Bar
+ def self.caller(proc_value)
+ proc_value.call
+ end
+end
diff --git a/test/ruby/box/call_toplevel.rb b/test/ruby/box/call_toplevel.rb
new file mode 100644
index 0000000000..c311a37028
--- /dev/null
+++ b/test/ruby/box/call_toplevel.rb
@@ -0,0 +1,8 @@
+foo
+
+#### TODO: this code should be valid, but can't be for now
+# module Foo
+# def self.wow
+# foo
+# end
+# end
diff --git a/test/ruby/box/consts.rb b/test/ruby/box/consts.rb
new file mode 100644
index 0000000000..e40cd5c50c
--- /dev/null
+++ b/test/ruby/box/consts.rb
@@ -0,0 +1,148 @@
+$VERBOSE = nil
+class String
+ STR_CONST1 = 111
+ STR_CONST2 = 222
+ STR_CONST3 = 333
+end
+
+class String
+ STR_CONST1 = 112
+
+ def self.set0(val)
+ const_set(:STR_CONST0, val)
+ end
+
+ def self.remove0
+ remove_const(:STR_CONST0)
+ end
+
+ def refer0
+ STR_CONST0
+ end
+
+ def refer1
+ STR_CONST1
+ end
+
+ def refer2
+ STR_CONST2
+ end
+
+ def refer3
+ STR_CONST3
+ end
+end
+
+module ForConsts
+ CONST1 = 111
+end
+
+TOP_CONST = 10
+
+module ForConsts
+ CONST1 = 112
+ CONST2 = 222
+ CONST3 = 333
+
+ def self.refer_all
+ ForConsts::CONST1
+ ForConsts::CONST2
+ ForConsts::CONST3
+ String::STR_CONST1
+ String::STR_CONST2
+ String::STR_CONST3
+ end
+
+ def self.refer1
+ CONST1
+ end
+
+ def self.get1
+ const_get(:CONST1)
+ end
+
+ def self.refer2
+ CONST2
+ end
+
+ def self.get2
+ const_get(:CONST2)
+ end
+
+ def self.refer3
+ CONST3
+ end
+
+ def self.get3
+ const_get(:CONST3)
+ end
+
+ def self.refer_top_const
+ TOP_CONST
+ end
+
+ # for String
+ class Proxy
+ def call_str_refer0
+ String.new.refer0
+ end
+
+ def call_str_get0
+ String.const_get(:STR_CONST0)
+ end
+
+ def call_str_set0(val)
+ String.set0(val)
+ end
+
+ def call_str_remove0
+ String.remove0
+ end
+
+ def call_str_refer1
+ String.new.refer1
+ end
+
+ def call_str_get1
+ String.const_get(:STR_CONST1)
+ end
+
+ String::STR_CONST2 = 223
+
+ def call_str_refer2
+ String.new.refer2
+ end
+
+ def call_str_get2
+ String.const_get(:STR_CONST2)
+ end
+
+ def call_str_set3
+ String.const_set(:STR_CONST3, 334)
+ end
+
+ def call_str_refer3
+ String.new.refer3
+ end
+
+ def call_str_get3
+ String.const_get(:STR_CONST3)
+ end
+
+ # for Integer
+ Integer::INT_CONST1 = 1
+
+ def refer_int_const1
+ Integer::INT_CONST1
+ end
+ end
+end
+
+# should not raise errors
+ForConsts.refer_all
+String::STR_CONST1
+Integer::INT_CONST1
+
+# If we execute this sentence once, the constant value will be cached on ISeq inline constant cache.
+# And it changes the behavior of ForConsts.refer_consts_directly called from global.
+# ForConsts.refer_consts_directly # should not raise errors too
diff --git a/test/ruby/box/define_toplevel.rb b/test/ruby/box/define_toplevel.rb
new file mode 100644
index 0000000000..aa77db3a13
--- /dev/null
+++ b/test/ruby/box/define_toplevel.rb
@@ -0,0 +1,5 @@
+def foo
+ "foooooooooo"
+end
+
+foo # should not raise errors
diff --git a/test/ruby/box/global_vars.rb b/test/ruby/box/global_vars.rb
new file mode 100644
index 0000000000..590363f617
--- /dev/null
+++ b/test/ruby/box/global_vars.rb
@@ -0,0 +1,37 @@
+module LineSplitter
+ def self.read
+ $-0
+ end
+
+ def self.write(char)
+ $-0 = char
+ end
+end
+
+module FieldSplitter
+ def self.read
+ $,
+ end
+
+ def self.write(char)
+ $, = char
+ end
+end
+
+module UniqueGvar
+ def self.read
+ $used_only_in_box
+ end
+
+ def self.write(val)
+ $used_only_in_box = val
+ end
+
+ def self.write_only(val)
+ $write_only_var_in_box = val
+ end
+
+ def self.gvars_in_box
+ global_variables
+ end
+end
diff --git a/test/ruby/box/instance_variables.rb b/test/ruby/box/instance_variables.rb
new file mode 100644
index 0000000000..1562ad5d45
--- /dev/null
+++ b/test/ruby/box/instance_variables.rb
@@ -0,0 +1,21 @@
+class String
+ class << self
+ attr_reader :str_ivar1
+
+ def str_ivar2
+ @str_ivar2
+ end
+ end
+
+ @str_ivar1 = 111
+ @str_ivar2 = 222
+end
+
+class StringDelegator < BasicObject
+private
+ def method_missing(...)
+ ::String.public_send(...)
+ end
+end
+
+StringDelegatorObj = StringDelegator.new
diff --git a/test/ruby/box/line_splitter.rb b/test/ruby/box/line_splitter.rb
new file mode 100644
index 0000000000..2596975ad7
--- /dev/null
+++ b/test/ruby/box/line_splitter.rb
@@ -0,0 +1,9 @@
+module LineSplitter
+ def self.read
+ $-0
+ end
+
+ def self.write(char)
+ $-0 = char
+ end
+end
diff --git a/test/ruby/box/load_path.rb b/test/ruby/box/load_path.rb
new file mode 100644
index 0000000000..7e5a83ef96
--- /dev/null
+++ b/test/ruby/box/load_path.rb
@@ -0,0 +1,26 @@
+module LoadPathCheck
+ FIRST_LOAD_PATH = $LOAD_PATH.dup
+ FIRST_LOAD_PATH_RESPOND_TO_RESOLVE = $LOAD_PATH.respond_to?(:resolve_feature_path)
+ FIRST_LOADED_FEATURES = $LOADED_FEATURES.dup
+
+ HERE = File.dirname(__FILE__)
+
+ def self.current_load_path
+ $LOAD_PATH
+ end
+
+ def self.current_loaded_features
+ $LOADED_FEATURES
+ end
+
+ def self.require_blank1
+ $LOAD_PATH << HERE
+ require 'blank1'
+ end
+
+ def self.require_blank2
+ require 'blank2'
+ end
+end
+
+LoadPathCheck.require_blank1
diff --git a/test/ruby/box/open_class_with_include.rb b/test/ruby/box/open_class_with_include.rb
new file mode 100644
index 0000000000..ad8fd58ea0
--- /dev/null
+++ b/test/ruby/box/open_class_with_include.rb
@@ -0,0 +1,31 @@
+module StringExt
+ FOO = "foo 1"
+ def say_foo
+ "I'm saying " + FOO
+ end
+end
+
+class String
+ include StringExt
+ def say
+ say_foo
+ end
+end
+
+module OpenClassWithInclude
+ def self.say
+ String.new.say
+ end
+
+ def self.say_foo
+ String.new.say_foo
+ end
+
+ def self.say_with_obj(str)
+ str.say
+ end
+
+ def self.refer_foo
+ String::FOO
+ end
+end
diff --git a/test/ruby/box/proc_callee.rb b/test/ruby/box/proc_callee.rb
new file mode 100644
index 0000000000..d30ab5d9f3
--- /dev/null
+++ b/test/ruby/box/proc_callee.rb
@@ -0,0 +1,14 @@
+module Target
+ def self.foo
+ "fooooo"
+ end
+end
+
+module Foo
+ def self.callee
+ lambda do
+ Target.foo
+ end
+ end
+end
+
diff --git a/test/ruby/box/proc_caller.rb b/test/ruby/box/proc_caller.rb
new file mode 100644
index 0000000000..8acf538fc1
--- /dev/null
+++ b/test/ruby/box/proc_caller.rb
@@ -0,0 +1,5 @@
+module Bar
+ def self.caller(proc_value)
+ proc_value.call
+ end
+end
diff --git a/test/ruby/box/procs.rb b/test/ruby/box/procs.rb
new file mode 100644
index 0000000000..1c39a8231b
--- /dev/null
+++ b/test/ruby/box/procs.rb
@@ -0,0 +1,64 @@
+class String
+ FOO = "foo"
+ def yay
+ "yay"
+ end
+end
+
+module ProcLookupTestA
+ module B
+ VALUE = 222
+ end
+end
+
+module ProcInBox
+ def self.make_proc_from_block(&b)
+ b
+ end
+
+ def self.call_proc(proc_arg)
+ proc_arg.call
+ end
+
+ def self.make_str_proc(type)
+ case type
+ when :proc_new then Proc.new { String.new.yay }
+ when :proc_f then proc { String.new.yay }
+ when :lambda_f then lambda { String.new.yay }
+ when :lambda_l then ->(){ String.new.yay }
+ when :block then make_proc_from_block { String.new.yay }
+ else
+ raise "invalid type :#{type}"
+ end
+ end
+
+ def self.make_const_proc(type)
+ case type
+ when :proc_new then Proc.new { ProcLookupTestA::B::VALUE }
+ when :proc_f then proc { ProcLookupTestA::B::VALUE }
+ when :lambda_f then lambda { ProcLookupTestA::B::VALUE }
+ when :lambda_l then ->(){ ProcLookupTestA::B::VALUE }
+ when :block then make_proc_from_block { ProcLookupTestA::B::VALUE }
+ else
+ raise "invalid type :#{type}"
+ end
+ end
+
+ def self.make_str_const_proc(type)
+ case type
+ when :proc_new then Proc.new { String::FOO }
+ when :proc_f then proc { String::FOO }
+ when :lambda_f then lambda { String::FOO }
+ when :lambda_l then ->(){ String::FOO }
+ when :block then make_proc_from_block { String::FOO }
+ else
+ raise "invalid type :#{type}"
+ end
+ end
+
+ CONST_PROC_NEW = Proc.new { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') }
+ CONST_PROC_F = proc { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') }
+ CONST_LAMBDA_F = lambda { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') }
+ CONST_LAMBDA_L = ->() { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') }
+ CONST_BLOCK = make_proc_from_block { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') }
+end
diff --git a/test/ruby/box/raise.rb b/test/ruby/box/raise.rb
new file mode 100644
index 0000000000..efb67f85c5
--- /dev/null
+++ b/test/ruby/box/raise.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+raise "Yay!"
diff --git a/test/ruby/box/returns_proc.rb b/test/ruby/box/returns_proc.rb
new file mode 100644
index 0000000000..bb816e5024
--- /dev/null
+++ b/test/ruby/box/returns_proc.rb
@@ -0,0 +1,12 @@
+module Foo
+ def self.foo
+ "fooooo"
+ end
+
+ def self.callee
+ lambda do
+ Foo.foo
+ end
+ end
+end
+
diff --git a/test/ruby/box/singleton_methods.rb b/test/ruby/box/singleton_methods.rb
new file mode 100644
index 0000000000..05470932d2
--- /dev/null
+++ b/test/ruby/box/singleton_methods.rb
@@ -0,0 +1,65 @@
+class String
+ def self.greeting
+ "Good evening!"
+ end
+end
+
+class Integer
+ class << self
+ def answer
+ 42
+ end
+ end
+end
+
+class Array
+ def a
+ size
+ end
+ def self.blank
+ []
+ end
+ def b
+ size
+ end
+end
+
+class Hash
+ def a
+ size
+ end
+ class << self
+ def http_200
+ {status: 200, body: 'OK'}
+ end
+ end
+ def b
+ size
+ end
+end
+
+module SingletonMethods
+ def self.string_greeing
+ String.greeting
+ end
+
+ def self.integer_answer
+ Integer.answer
+ end
+
+ def self.array_blank
+ Array.blank
+ end
+
+ def self.hash_http_200
+ Hash.http_200
+ end
+
+ def self.array_instance_methods_return_size(ary)
+ [ary.a, ary.b]
+ end
+
+ def self.hash_instance_methods_return_size(hash)
+ [hash.a, hash.b]
+ end
+end
diff --git a/test/ruby/box/string_ext.rb b/test/ruby/box/string_ext.rb
new file mode 100644
index 0000000000..d8c5a3d661
--- /dev/null
+++ b/test/ruby/box/string_ext.rb
@@ -0,0 +1,13 @@
+class String
+ def yay
+ "yay"
+ end
+end
+
+String.new.yay # check this doesn't raise NoMethodError
+
+module Bar
+ def self.yay
+ String.new.yay
+ end
+end
diff --git a/test/ruby/box/string_ext_caller.rb b/test/ruby/box/string_ext_caller.rb
new file mode 100644
index 0000000000..b8345d98ed
--- /dev/null
+++ b/test/ruby/box/string_ext_caller.rb
@@ -0,0 +1,5 @@
+module Foo
+ def self.yay
+ String.new.yay
+ end
+end
diff --git a/test/ruby/box/string_ext_calling.rb b/test/ruby/box/string_ext_calling.rb
new file mode 100644
index 0000000000..6467b728dd
--- /dev/null
+++ b/test/ruby/box/string_ext_calling.rb
@@ -0,0 +1 @@
+Foo.yay
diff --git a/test/ruby/box/string_ext_eval_caller.rb b/test/ruby/box/string_ext_eval_caller.rb
new file mode 100644
index 0000000000..0e6b20c19f
--- /dev/null
+++ b/test/ruby/box/string_ext_eval_caller.rb
@@ -0,0 +1,12 @@
+module Baz
+ def self.yay
+ eval 'String.new.yay'
+ end
+
+ def self.yay_with_binding
+ suffix = ", yay!"
+ eval 'String.new.yay + suffix', binding
+ end
+end
+
+Baz.yay # should not raise NeMethodError
diff --git a/test/ruby/box/top_level.rb b/test/ruby/box/top_level.rb
new file mode 100644
index 0000000000..90df145578
--- /dev/null
+++ b/test/ruby/box/top_level.rb
@@ -0,0 +1,33 @@
+def yaaay
+ "yay!"
+end
+
+module Foo
+ def self.foo
+ yaaay
+ end
+end
+
+eval 'def foo; "foo"; end'
+
+Foo.foo # Should not raise NameError
+
+foo
+
+module Bar
+ def self.bar
+ foo
+ end
+end
+
+Bar.bar
+
+$def_retval_in_namespace = def boooo
+ "boo"
+end
+
+module Baz
+ def self.baz
+ raise "#{$def_retval_in_namespace}"
+ end
+end
diff --git a/test/ruby/enc/test_emoji_breaks.rb b/test/ruby/enc/test_emoji_breaks.rb
index bb5114680e..0873e681c3 100644
--- a/test/ruby/enc/test_emoji_breaks.rb
+++ b/test/ruby/enc/test_emoji_breaks.rb
@@ -53,7 +53,7 @@ class TestEmojiBreaks < Test::Unit::TestCase
EMOJI_DATA_FILES = %w[emoji-sequences emoji-test emoji-zwj-sequences].map do |basename|
BreakFile.new(basename, EMOJI_DATA_PATH, EMOJI_VERSION)
end
- UNICODE_DATA_FILE = BreakFile.new('emoji-variation-sequences', UNICODE_DATA_PATH, UNICODE_VERSION)
+ UNICODE_DATA_FILE = BreakFile.new('emoji-variation-sequences', UNICODE_DATA_PATH, EMOJI_VERSION)
EMOJI_DATA_FILES << UNICODE_DATA_FILE
def self.data_files_available?
diff --git a/test/ruby/sentence.rb b/test/ruby/sentence.rb
index 9bfd7c7599..99ced05d2f 100644
--- a/test/ruby/sentence.rb
+++ b/test/ruby/sentence.rb
@@ -211,7 +211,7 @@ class Sentence
# returns new sentence object which
# _target_ is substituted by the block.
#
- # Sentence#subst invokes <tt>_target_ === _string_</tt> for each
+ # Sentence#subst invokes <tt>target === string</tt> for each
# string in the sentence.
# The strings which === returns true are substituted by the block.
# The block is invoked with the substituting string.
diff --git a/test/ruby/test_allocation.rb b/test/ruby/test_allocation.rb
index 2b181b1bdc..90d7c04f9b 100644
--- a/test/ruby/test_allocation.rb
+++ b/test/ruby/test_allocation.rb
@@ -2,6 +2,12 @@
require 'test/unit'
class TestAllocation < Test::Unit::TestCase
+ def setup
+ # The namespace changes on i686 platform triggers a bug to allocate objects unexpectedly.
+ # For now, skip these tests only on i686
+ pend if RUBY_PLATFORM =~ /^i686/
+ end
+
def munge_checks(checks)
checks
end
@@ -60,9 +66,7 @@ class TestAllocation < Test::Unit::TestCase
#{checks}
- unless failures.empty?
- assert_equal(true, false, failures.join("\n"))
- end
+ assert_empty(failures)
RUBY
end
@@ -523,6 +527,59 @@ class TestAllocation < Test::Unit::TestCase
RUBY
end
+ def test_anonymous_splat_parameter
+ only_block = block.empty? ? block : block[2..]
+ check_allocations(<<~RUBY)
+ def self.anon_splat(*#{block}); end
+
+ check_allocations(1, 1, "anon_splat(1, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "anon_splat(1, **nil#{block})")
+ check_allocations(1, 0, "anon_splat(1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(1, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat(1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "anon_splat(*nil, **nill#{block})")
+ check_allocations(0, 0, "anon_splat(*array1, **nill#{block})")
+ check_allocations(0, 0, "anon_splat(*array1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(0, 0, "anon_splat(#{only_block})")
+ check_allocations(1, 1, "anon_splat(a: 2#{block})")
+ check_allocations(0, 0, "anon_splat(**empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, **hash1, **empty_hash#{block})")
+
+ unless defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled?
+ check_allocations(0, 0, "anon_splat(*array1, **nil#{block})")
+ check_allocations(1, 0, "anon_splat(*r2k_empty_array#{block})")
+ check_allocations(1, 1, "anon_splat(*r2k_array#{block})")
+ check_allocations(1, 0, "anon_splat(*r2k_empty_array1#{block})")
+ check_allocations(1, 1, "anon_splat(*r2k_array1#{block})")
+ end
+ RUBY
+ end
+
def test_anonymous_splat_and_anonymous_keyword_splat_parameters
only_block = block.empty? ? block : block[2..]
check_allocations(<<~RUBY)
@@ -775,6 +832,7 @@ class TestAllocation < Test::Unit::TestCase
def test_no_array_allocation_with_splat_and_nonstatic_keywords
check_allocations(<<~RUBY)
def self.keyword(a: nil, b: nil#{block}); end
+ def self.Object; Object end
check_allocations(0, 1, "keyword(*nil, a: empty_array#{block})") # LVAR
check_allocations(0, 1, "keyword(*empty_array, a: empty_array#{block})") # LVAR
@@ -782,7 +840,8 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "$x = empty_array; keyword(*empty_array, a: $x#{block})") # GVAR
check_allocations(0, 1, "@x = empty_array; keyword(*empty_array, a: @x#{block})") # IVAR
check_allocations(0, 1, "self.class.const_set(:X, empty_array); keyword(*empty_array, a: X#{block})") # CONST
- check_allocations(0, 1, "keyword(*empty_array, a: Object::X#{block})") # COLON2
+ check_allocations(0, 1, "keyword(*empty_array, a: Object::X#{block})") # COLON2 - safe
+ check_allocations(1, 1, "keyword(*empty_array, a: Object()::X#{block})") # COLON2 - unsafe
check_allocations(0, 1, "keyword(*empty_array, a: ::X#{block})") # COLON3
check_allocations(0, 1, "T = self; #{'B = block' unless block.empty?}; class Object; @@x = X; T.keyword(*X, a: @@x#{', &B' unless block.empty?}) end") # CVAR
check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1#{block})") # INTEGER
@@ -799,6 +858,13 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "keyword(*empty_array, a: ->{}#{block})") # LAMBDA
check_allocations(0, 1, "keyword(*empty_array, a: $1#{block})") # NTH_REF
check_allocations(0, 1, "keyword(*empty_array, a: $`#{block})") # BACK_REF
+
+ # LIST: Only 1 array (literal [:c]), not 2 (one for [:c] and one for *empty_array)
+ check_allocations(1, 1, "keyword(*empty_array, a: empty_array, b: [:c]#{block})")
+ check_allocations(1, 1, "keyword(*empty_array, a: empty_array, b: [:c, $x]#{block})")
+ # LIST unsafe: 2 (one for [Object()] and one for *empty_array)
+ check_allocations(2, 1, "keyword(*empty_array, a: empty_array, b: [Object()]#{block})")
+ check_allocations(2, 1, "keyword(*empty_array, a: empty_array, b: [:c, $x, Object()]#{block})")
RUBY
end
@@ -844,13 +910,15 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(<<~RUBY)
keyword = keyword = proc{ |a: nil, b: nil #{block}| }
+ def self.Object; Object end
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array#{block})") # LVAR
check_allocations(0, 1, "->{keyword.(*empty_array, a: empty_array#{block})}.call") # DVAR
check_allocations(0, 1, "$x = empty_array; keyword.(*empty_array, a: $x#{block})") # GVAR
check_allocations(0, 1, "@x = empty_array; keyword.(*empty_array, a: @x#{block})") # IVAR
check_allocations(0, 1, "self.class.const_set(:X, empty_array); keyword.(*empty_array, a: X#{block})") # CONST
- check_allocations(0, 1, "keyword.(*empty_array, a: Object::X#{block})") # COLON2
+ check_allocations(0, 1, "keyword.(*empty_array, a: Object::X#{block})") # COLON2 - safe
+ check_allocations(1, 1, "keyword.(*empty_array, a: Object()::X#{block})") # COLON2 - unsafe
check_allocations(0, 1, "keyword.(*empty_array, a: ::X#{block})") # COLON3
check_allocations(0, 1, "T = keyword; #{'B = block' unless block.empty?}; class Object; @@x = X; T.(*X, a: @@x#{', &B' unless block.empty?}) end") # CVAR
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1#{block})") # INTEGER
@@ -867,6 +935,13 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "keyword.(*empty_array, a: ->{}#{block})") # LAMBDA
check_allocations(0, 1, "keyword.(*empty_array, a: $1#{block})") # NTH_REF
check_allocations(0, 1, "keyword.(*empty_array, a: $`#{block})") # BACK_REF
+
+ # LIST safe: Only 1 array (literal [:c]), not 2 (one for [:c] and one for *empty_array)
+ check_allocations(1, 1, "keyword.(*empty_array, a: empty_array, b: [:c]#{block})")
+ check_allocations(1, 1, "keyword.(*empty_array, a: empty_array, b: [:c, $x]#{block})")
+ # LIST unsafe: 2 (one for [:c] and one for *empty_array)
+ check_allocations(2, 1, "keyword.(*empty_array, a: empty_array, b: [Object()]#{block})")
+ check_allocations(2, 1, "keyword.(*empty_array, a: empty_array, b: [:c, $x, Object()]#{block})")
RUBY
end
diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb
index 19f79d236d..04e15b6d87 100644
--- a/test/ruby/test_array.rb
+++ b/test/ruby/test_array.rb
@@ -1309,32 +1309,7 @@ class TestArray < Test::Unit::TestCase
assert_equal(ary.join(':'), ary2.join(':'))
assert_not_nil(x =~ /def/)
-=begin
- skipping "Not tested:
- D,d & double-precision float, native format\\
- E & double-precision float, little-endian byte order\\
- e & single-precision float, little-endian byte order\\
- F,f & single-precision float, native format\\
- G & double-precision float, network (big-endian) byte order\\
- g & single-precision float, network (big-endian) byte order\\
- I & unsigned integer\\
- i & integer\\
- L & unsigned long\\
- l & long\\
-
- N & long, network (big-endian) byte order\\
- n & short, network (big-endian) byte-order\\
- P & pointer to a structure (fixed-length string)\\
- p & pointer to a null-terminated string\\
- S & unsigned short\\
- s & short\\
- V & long, little-endian byte order\\
- v & short, little-endian byte order\\
- X & back up a byte\\
- x & null byte\\
- Z & ASCII string (null padded, count is width)\\
-"
-=end
+ # more comprehensive tests are in test_pack.rb
end
def test_pack_with_buffer
@@ -1361,6 +1336,28 @@ class TestArray < Test::Unit::TestCase
assert_equal(@cls[@cls[1,2], nil, 'dog', 'cat'], a.prepend(@cls[1, 2]))
end
+ def test_tolerant_to_redefinition
+ *code = __FILE__, __LINE__+1, "#{<<-"{#"}\n#{<<-'};'}"
+ {#
+ module M
+ def <<(a)
+ super(a * 2)
+ end
+ end
+ class Array; prepend M; end
+ ary = [*1..10]
+ mapped = ary.map {|i| i}
+ selected = ary.select {true}
+ module M
+ remove_method :<<
+ end
+ assert_equal(ary, mapped)
+ assert_equal(ary, selected)
+ };
+ assert_separately(%w[--disable-yjit], *code)
+ assert_separately(%w[--enable-yjit], *code)
+ end
+
def test_push
a = @cls[1, 2, 3]
assert_equal(@cls[1, 2, 3, 4, 5], a.push(4, 5))
@@ -3609,6 +3606,23 @@ class TestArray < Test::Unit::TestCase
assert_equal((1..67).to_a.reverse, var_0)
end
+ def test_find
+ ary = [1, 2, 3, 4, 5]
+ assert_equal(2, ary.find {|x| x % 2 == 0 })
+ assert_equal(nil, ary.find {|x| false })
+ assert_equal(:foo, ary.find(proc { :foo }) {|x| false })
+ end
+
+ def test_rfind
+ ary = [1, 2, 3, 4, 5]
+ assert_equal(4, ary.rfind {|x| x % 2 == 0 })
+ assert_equal(1, ary.rfind {|x| x < 2 })
+ assert_equal(5, ary.rfind {|x| x > 4 })
+ assert_equal(nil, ary.rfind {|x| false })
+ assert_equal(:foo, ary.rfind(proc { :foo }) {|x| false })
+ assert_equal(nil, ary.rfind {|x| ary.clear; false })
+ end
+
private
def need_continuation
unless respond_to?(:callcc, true)
diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb
index 37b23e8db5..22ccbfb604 100644
--- a/test/ruby/test_ast.rb
+++ b/test/ruby/test_ast.rb
@@ -48,7 +48,7 @@ class TestAst < Test::Unit::TestCase
@path = path
@errors = []
@debug = false
- @ast = RubyVM::AbstractSyntaxTree.parse(src) if src
+ @ast = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse(src) } if src
end
def validate_range
@@ -67,7 +67,7 @@ class TestAst < Test::Unit::TestCase
def ast
return @ast if defined?(@ast)
- @ast = RubyVM::AbstractSyntaxTree.parse_file(@path)
+ @ast = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse_file(@path) }
end
private
@@ -135,7 +135,7 @@ class TestAst < Test::Unit::TestCase
Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
define_method("test_all_tokens:#{path}") do
- node = RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true)
+ node = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true) }
tokens = node.all_tokens.sort_by { [_1.last[0], _1.last[1]] }
tokens_bytes = tokens.map { _1[2]}.join.bytes
source_bytes = File.read("#{SRCDIR}/#{path}").bytes
@@ -337,6 +337,19 @@ class TestAst < Test::Unit::TestCase
assert_parse("END {defined? yield}")
end
+ def test_invalid_yield_no_memory_leak
+ # [Bug #21383]
+ assert_no_memory_leak([], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true)
+ code = proc do
+ eval("class C; yield; end")
+ rescue SyntaxError
+ end
+ 1_000.times(&code)
+ begin;
+ 100_000.times(&code)
+ end;
+ end
+
def test_node_id_for_location
omit if ParserSupport.prism_enabled?
@@ -352,6 +365,50 @@ class TestAst < Test::Unit::TestCase
assert_equal node.node_id, node_id
end
+ def add(x, y)
+ end
+
+ def test_node_id_for_backtrace_location_of_method_definition
+ omit if ParserSupport.prism_enabled?
+
+ begin
+ add(1)
+ rescue ArgumentError => exc
+ loc = exc.backtrace_locations.first
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
+ node = RubyVM::AbstractSyntaxTree.of(method(:add))
+ assert_equal node.node_id, node_id
+ end
+ end
+
+ def test_node_id_for_backtrace_location_of_lambda
+ omit if ParserSupport.prism_enabled?
+
+ v = -> {}
+ begin
+ v.call(1)
+ rescue ArgumentError => exc
+ loc = exc.backtrace_locations.first
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
+ node = RubyVM::AbstractSyntaxTree.of(v)
+ assert_equal node.node_id, node_id
+ end
+ end
+
+ def test_node_id_for_backtrace_location_of_lambda_method
+ omit if ParserSupport.prism_enabled?
+
+ v = lambda {}
+ begin
+ v.call(1)
+ rescue ArgumentError => exc
+ loc = exc.backtrace_locations.first
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
+ node = RubyVM::AbstractSyntaxTree.of(v)
+ assert_equal node.node_id, node_id
+ end
+ end
+
def test_node_id_for_backtrace_location_raises_argument_error
bug19262 = '[ruby-core:111435]'
@@ -788,7 +845,7 @@ dummy
node_proc = RubyVM::AbstractSyntaxTree.of(proc, keep_script_lines: true)
node_method = RubyVM::AbstractSyntaxTree.of(method, keep_script_lines: true)
- assert_equal("{ 1 + 2 }", node_proc.source)
+ assert_equal("Proc.new { 1 + 2 }", node_proc.source)
assert_equal("def test_keep_script_lines_for_of\n", node_method.source.lines.first)
end
@@ -865,7 +922,7 @@ dummy
omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess?
assert_in_out_err(["-e", "def foo; end; pp RubyVM::AbstractSyntaxTree.of(method(:foo)).type"],
- "", [":SCOPE"], [])
+ "", [":DEFN"], [])
end
def test_error_tolerant
@@ -1173,7 +1230,7 @@ dummy
args: nil
body:
(LAMBDA@1:0-2:3
- (SCOPE@1:2-2:3
+ (SCOPE@1:0-2:3
tbl: []
args:
(ARGS@1:2-1:2
@@ -1384,6 +1441,32 @@ dummy
assert_locations(node.children[-1].locations, [[1, 0, 1, 16], [1, 0, 1, 5], [1, 8, 1, 9], [1, 13, 1, 16]])
end
+ def test_colon2_locations
+ node = ast_parse("A::B")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 4], [1, 1, 1, 3], [1, 3, 1, 4]])
+
+ node = ast_parse("A::B::C")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 4, 1, 6], [1, 6, 1, 7]])
+ assert_locations(node.children[-1].children[0].locations, [[1, 0, 1, 4], [1, 1, 1, 3], [1, 3, 1, 4]])
+ end
+
+ def test_colon3_locations
+ node = ast_parse("::A")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 3], [1, 0, 1, 2], [1, 2, 1, 3]])
+
+ node = ast_parse("::A::B")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 3, 1, 5], [1, 5, 1, 6]])
+ assert_locations(node.children[-1].children[0].locations, [[1, 0, 1, 3], [1, 0, 1, 2], [1, 2, 1, 3]])
+ end
+
+ def test_defined_locations
+ node = ast_parse("defined? x")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 10], [1, 0, 1, 8]])
+
+ node = ast_parse("defined?(x)")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 11], [1, 0, 1, 8]])
+ end
+
def test_dot2_locations
node = ast_parse("1..2")
assert_locations(node.children[-1].locations, [[1, 0, 1, 4], [1, 1, 1, 3]])
@@ -1452,6 +1535,11 @@ dummy
assert_locations(node.children[-1].locations, [[1, 0, 1, 20], [1, 0, 1, 2], [1, 10, 1, 12], [1, 17, 1, 20]])
end
+ def test_module_locations
+ node = ast_parse('module A end')
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 12], [1, 0, 1, 6], [1, 9, 1, 12]])
+ end
+
def test_if_locations
node = ast_parse("if cond then 1 else 2 end")
assert_locations(node.children[-1].locations, [[1, 0, 1, 25], [1, 0, 1, 2], [1, 8, 1, 12], [1, 22, 1, 25]])
@@ -1470,6 +1558,20 @@ dummy
assert_locations(node.children[-1].children[1].children[0].locations, [[1, 11, 1, 17], [1, 13, 1, 15], nil, nil])
end
+ def test_in_locations
+ node = ast_parse("case 1; in 2 then 3; end")
+ assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 20], [1, 8, 1, 10], [1, 13, 1, 17], nil])
+
+ node = ast_parse("1 => a")
+ assert_locations(node.children[-1].children[1].locations, [[1, 5, 1, 6], nil, nil, [1, 2, 1, 4]])
+
+ node = ast_parse("1 in a")
+ assert_locations(node.children[-1].children[1].locations, [[1, 5, 1, 6], [1, 2, 1, 4], nil, nil])
+
+ node = ast_parse("case 1; in 2; 3; end")
+ assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 16], [1, 8, 1, 10], [1, 12, 1, 13], nil])
+ end
+
def test_next_locations
node = ast_parse("loop { next 1 }")
assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 13], [1, 7, 1, 11]])
@@ -1534,6 +1636,14 @@ dummy
assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 0, 1, 6]])
end
+ def test_sclass_locations
+ node = ast_parse("class << self; end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 18], [1, 0, 1, 5], [1, 6, 1, 8], [1, 15, 1, 18]])
+
+ node = ast_parse("class << obj; foo; end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 22], [1, 0, 1, 5], [1, 6, 1, 8], [1, 19, 1, 22]])
+ end
+
def test_splat_locations
node = ast_parse("a = *1")
assert_locations(node.children[-1].children[1].locations, [[1, 4, 1, 6], [1, 4, 1, 5]])
diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb
index ca3e3d5f7f..82bf2d9d2c 100644
--- a/test/ruby/test_autoload.rb
+++ b/test/ruby/test_autoload.rb
@@ -224,11 +224,18 @@ p Foo::Bar
Kernel.module_eval do
alias old_require require
end
+ Ruby::Box.module_eval do
+ alias old_require require
+ end
called_with = []
Kernel.send :define_method, :require do |path|
called_with << path
old_require path
end
+ Ruby::Box.send :define_method, :require do |path|
+ called_with << path
+ old_require path
+ end
yield called_with
ensure
Kernel.module_eval do
@@ -236,6 +243,11 @@ p Foo::Bar
alias require old_require
undef old_require
end
+ Ruby::Box.module_eval do
+ undef require
+ alias require old_require
+ undef old_require
+ end
end
def test_require_implemented_in_ruby_is_called
@@ -249,7 +261,8 @@ p Foo::Bar
ensure
remove_autoload_constant
end
- assert_equal [file.path], called_with
+ # .dup to prevent breaking called_with by autoloading pp, etc
+ assert_equal [file.path], called_with.dup
}
end
end
@@ -267,7 +280,8 @@ p Foo::Bar
ensure
remove_autoload_constant
end
- assert_equal [a.path, b.path], called_with
+ # .dup to prevent breaking called_with by autoloading pp, etc
+ assert_equal [a.path, b.path], called_with.dup
end
end
end
@@ -560,7 +574,7 @@ p Foo::Bar
autoload_path = File.join(tmpdir, "autoload_parallel_race.rb")
File.write(autoload_path, 'module Foo; end; module Bar; end')
- assert_separately([], <<-RUBY, timeout: 100)
+ assert_ruby_status([], <<-RUBY, timeout: 100)
autoload_path = #{File.realpath(autoload_path).inspect}
# This should work with no errors or failures.
@@ -599,4 +613,14 @@ p Foo::Bar
RUBY
end
end
+
+ private
+
+ def assert_separately(*args, **kwargs)
+ super(*args, timeout: 60, **kwargs)
+ end
+
+ def assert_ruby_status(*args, **kwargs)
+ super(*args, timeout: 60, **kwargs)
+ end
end
diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb
index fca7b62030..dad7dfcb55 100644
--- a/test/ruby/test_backtrace.rb
+++ b/test/ruby/test_backtrace.rb
@@ -454,4 +454,16 @@ class TestBacktrace < Test::Unit::TestCase
foo::Bar.baz
end;
end
+
+ def test_backtrace_internal_frame
+ backtrace = tap { break caller_locations(0) }
+ assert_equal(__FILE__, backtrace[1].path) # not "<internal:kernel>"
+ assert_equal("Kernel#tap", backtrace[1].label)
+ end
+
+ def test_backtrace_on_argument_error
+ lineno = __LINE__; [1, 2].inject(:tap)
+ rescue ArgumentError
+ assert_equal("#{ __FILE__ }:#{ lineno }:in 'Kernel#tap'", $!.backtrace[0].to_s)
+ end
end
diff --git a/test/ruby/test_bignum.rb b/test/ruby/test_bignum.rb
index beef33e2a6..c366f794b2 100644
--- a/test/ruby/test_bignum.rb
+++ b/test/ruby/test_bignum.rb
@@ -605,6 +605,49 @@ class TestBignum < Test::Unit::TestCase
assert_equal(1, (-2**(BIGNUM_MIN_BITS*4))[BIGNUM_MIN_BITS*4])
end
+ def test_aref2
+ x = (0x123456789abcdef << (BIGNUM_MIN_BITS + 32)) | 0x12345678
+ assert_equal(x, x[0, x.bit_length])
+ assert_equal(x >> 10, x[10, x.bit_length])
+ assert_equal(0x45678, x[0, 20])
+ assert_equal(0x6780, x[-4, 16])
+ assert_equal(0x123456, x[x.bit_length - 21, 40])
+ assert_equal(0x6789ab, x[x.bit_length - 41, 24])
+ assert_equal(0, x[-20, 10])
+ assert_equal(0, x[x.bit_length + 10, 10])
+
+ assert_equal(0, x[5, 0])
+ assert_equal(0, (-x)[5, 0])
+
+ assert_equal(x >> 5, x[5, -1])
+ assert_equal(x << 5, x[-5, -1])
+ assert_equal((-x) >> 5, (-x)[5, -1])
+ assert_equal((-x) << 5, (-x)[-5, -1])
+
+ assert_equal(x << 5, x[-5, FIXNUM_MAX])
+ assert_equal(x >> 5, x[5, FIXNUM_MAX])
+ assert_equal(0, x[FIXNUM_MIN, 100])
+ assert_equal(0, (-x)[FIXNUM_MIN, 100])
+
+ y = (x << 160) | 0x1234_0000_0000_0000_1234_0000_0000_0000
+ assert_equal(0xffffedcc00, (-y)[40, 40])
+ assert_equal(0xfffffffedc, (-y)[52, 40])
+ assert_equal(0xffffedcbff, (-y)[104, 40])
+ assert_equal(0xfffff6e5d4, (-y)[y.bit_length - 20, 40])
+ assert_equal(0, (-y)[-20, 10])
+ assert_equal(0xfff, (-y)[y.bit_length + 10, 12])
+
+ z = (1 << (BIGNUM_MIN_BITS * 2)) - 1
+ assert_equal(0x400, (-z)[-10, 20])
+ assert_equal(1, (-z)[0, 20])
+ assert_equal(0, (-z)[10, 20])
+ assert_equal(1, (-z)[0, z.bit_length])
+ assert_equal(0, (-z)[z.bit_length - 10, 10])
+ assert_equal(0x400, (-z)[z.bit_length - 10, 11])
+ assert_equal(0xfff, (-z)[z.bit_length, 12])
+ assert_equal(0xfff00, (-z)[z.bit_length - 8, 20])
+ end
+
def test_hash
assert_nothing_raised { T31P.hash }
end
@@ -778,6 +821,9 @@ class TestBignum < Test::Unit::TestCase
assert_equal([7215, 2413, 6242], T1024P.digits(10_000).first(3))
assert_equal([11], 11.digits(T1024P))
assert_equal([T1024P - 1, 1], (T1024P + T1024P - 1).digits(T1024P))
+ bug21680 = '[ruby-core:123769] [Bug #21680]'
+ assert_equal([0] * 64 + [1], (2**512).digits(256), bug21680)
+ assert_equal([0] * 128 + [1], (123**128).digits(123), bug21680)
end
def test_digits_for_negative_numbers
diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb
new file mode 100644
index 0000000000..bb98a2efbe
--- /dev/null
+++ b/test/ruby/test_box.rb
@@ -0,0 +1,874 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'rbconfig'
+
+class TestBox < Test::Unit::TestCase
+ EXPERIMENTAL_WARNING_LINE_PATTERNS = [
+ /#{RbConfig::CONFIG["ruby_install_name"] || "ruby"}(\.exe)?: warning: Ruby::Box is experimental, and the behavior may change in the future!/,
+ %r{See https://docs.ruby-lang.org/en/(master|\d\.\d)/Ruby/Box.html for known issues, etc.}
+ ]
+ ENV_ENABLE_BOX = {'RUBY_BOX' => '1', 'TEST_DIR' => __dir__}
+
+ def setup
+ @box = nil
+ @dir = __dir__
+ end
+
+ def teardown
+ @box = nil
+ end
+
+ def setup_box
+ pend unless Ruby::Box.enabled?
+ @box = Ruby::Box.new
+ end
+
+ def test_box_availability_in_default
+ assert_separately(['RUBY_BOX'=>nil], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ assert_nil ENV['RUBY_BOX']
+ assert_not_predicate Ruby::Box, :enabled?
+ end;
+ end
+
+ def test_box_availability_when_enabled
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ assert_equal '1', ENV['RUBY_BOX']
+ assert_predicate Ruby::Box, :enabled?
+ end;
+ end
+
+ def test_current_box_in_main
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ assert_equal Ruby::Box.main, Ruby::Box.current
+ assert_predicate Ruby::Box.main, :main?
+ end;
+ end
+
+ def test_require_rb_separately
+ setup_box
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+
+ @box.require(File.join(__dir__, 'box', 'a.1_1_0'))
+
+ assert_not_nil @box::BOX_A
+ assert_not_nil @box::BOX_B
+ assert_equal "1.1.0", @box::BOX_A::VERSION
+ assert_equal "yay 1.1.0", @box::BOX_A.new.yay
+ assert_equal "1.1.0", @box::BOX_B::VERSION
+ assert_equal "yay_b1", @box::BOX_B.yay
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+ end
+
+ def test_require_relative_rb_separately
+ setup_box
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+
+ @box.require_relative('box/a.1_1_0')
+
+ assert_not_nil @box::BOX_A
+ assert_not_nil @box::BOX_B
+ assert_equal "1.1.0", @box::BOX_A::VERSION
+ assert_equal "yay 1.1.0", @box::BOX_A.new.yay
+ assert_equal "1.1.0", @box::BOX_B::VERSION
+ assert_equal "yay_b1", @box::BOX_B.yay
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+ end
+
+ def test_load_separately
+ setup_box
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+
+ @box.load(File.join(__dir__, 'box', 'a.1_1_0.rb'))
+
+ assert_not_nil @box::BOX_A
+ assert_not_nil @box::BOX_B
+ assert_equal "1.1.0", @box::BOX_A::VERSION
+ assert_equal "yay 1.1.0", @box::BOX_A.new.yay
+ assert_equal "1.1.0", @box::BOX_B::VERSION
+ assert_equal "yay_b1", @box::BOX_B.yay
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+ end
+
+ def test_box_in_box
+ setup_box
+
+ assert_raise(NameError) { BOX1 }
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+
+ @box.require_relative('box/box')
+
+ assert_not_nil @box::BOX1
+ assert_not_nil @box::BOX1::BOX_A
+ assert_not_nil @box::BOX1::BOX_B
+ assert_equal "1.1.0", @box::BOX1::BOX_A::VERSION
+ assert_equal "yay 1.1.0", @box::BOX1::BOX_A.new.yay
+ assert_equal "1.1.0", @box::BOX1::BOX_B::VERSION
+ assert_equal "yay_b1", @box::BOX1::BOX_B.yay
+
+ assert_raise(NameError) { BOX1 }
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+ end
+
+ def test_require_rb_2versiobox
+ setup_box
+
+ assert_raise(NameError) { BOX_A }
+
+ @box.require(File.join(__dir__, 'box', 'a.1_2_0'))
+ assert_equal "1.2.0", @box::BOX_A::VERSION
+ assert_equal "yay 1.2.0", @box::BOX_A.new.yay
+
+ n2 = Ruby::Box.new
+ n2.require(File.join(__dir__, 'box', 'a.1_1_0'))
+ assert_equal "1.1.0", n2::BOX_A::VERSION
+ assert_equal "yay 1.1.0", n2::BOX_A.new.yay
+
+ # recheck @box is not affected by the following require
+ assert_equal "1.2.0", @box::BOX_A::VERSION
+ assert_equal "yay 1.2.0", @box::BOX_A.new.yay
+
+ assert_raise(NameError) { BOX_A }
+ end
+
+ def test_raising_errors_in_require
+ setup_box
+
+ assert_raise(RuntimeError, "Yay!") { @box.require(File.join(__dir__, 'box', 'raise')) }
+ assert_include Ruby::Box.current.inspect, "main"
+ end
+
+ def test_autoload_in_box
+ setup_box
+
+ assert_raise(NameError) { BOX_A }
+
+ @box.require_relative('box/autoloading')
+ # autoloaded A is visible from global
+ assert_equal '1.1.0', @box::BOX_A::VERSION
+
+ assert_raise(NameError) { BOX_A }
+
+ # autoload trigger BOX_B::BAR is valid even from global
+ assert_equal 'bar_b1', @box::BOX_B::BAR
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+ end
+
+ def test_continuous_top_level_method_in_a_box
+ setup_box
+
+ @box.require_relative('box/define_toplevel')
+ @box.require_relative('box/call_toplevel')
+
+ assert_raise(NameError) { foo }
+ end
+
+ def test_top_level_methods_in_box
+ pend # TODO: fix loading/current box detection
+ setup_box
+ @box.require_relative('box/top_level')
+ assert_equal "yay!", @box::Foo.foo
+ assert_raise(NameError) { yaaay }
+ assert_equal "foo", @box::Bar.bar
+ assert_raise_with_message(RuntimeError, "boooo") { @box::Baz.baz }
+ end
+
+ def test_proc_defined_in_box_refers_module_in_box
+ setup_box
+
+ # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ box1 = Ruby::Box.new
+ box1.require("#{here}/box/proc_callee")
+ proc_v = box1::Foo.callee
+ assert_raise(NameError) { Target }
+ assert box1::Target
+ assert_equal "fooooo", proc_v.call # refers Target in the box box1
+ box1.require("#{here}/box/proc_caller")
+ assert_equal "fooooo", box1::Bar.caller(proc_v)
+
+ box2 = Ruby::Box.new
+ box2.require("#{here}/box/proc_caller")
+ assert_raise(NameError) { box2::Target }
+ assert_equal "fooooo", box2::Bar.caller(proc_v) # refers Target in the box box1
+ end;
+ end
+
+ def test_proc_defined_globally_refers_global_module
+ setup_box
+
+ # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ require("#{here}/box/proc_callee")
+ def Target.foo
+ "yay"
+ end
+ proc_v = Foo.callee
+ assert Target
+ assert_equal "yay", proc_v.call # refers global Foo
+ box1 = Ruby::Box.new
+ box1.require("#{here}/box/proc_caller")
+ assert_equal "yay", box1::Bar.caller(proc_v)
+
+ box2 = Ruby::Box.new
+ box2.require("#{here}/box/proc_callee")
+ box2.require("#{here}/box/proc_caller")
+ assert_equal "fooooo", box2::Foo.callee.call
+ assert_equal "yay", box2::Bar.caller(proc_v) # should refer the global Target, not Foo in box2
+ end;
+ end
+
+ def test_instance_variable
+ setup_box
+
+ @box.require_relative('box/instance_variables')
+
+ assert_equal [], String.instance_variables
+ assert_equal [:@str_ivar1, :@str_ivar2], @box::StringDelegatorObj.instance_variables
+ assert_equal 111, @box::StringDelegatorObj.str_ivar1
+ assert_equal 222, @box::StringDelegatorObj.str_ivar2
+ assert_equal 222, @box::StringDelegatorObj.instance_variable_get(:@str_ivar2)
+
+ @box::StringDelegatorObj.instance_variable_set(:@str_ivar3, 333)
+ assert_equal 333, @box::StringDelegatorObj.instance_variable_get(:@str_ivar3)
+ @box::StringDelegatorObj.remove_instance_variable(:@str_ivar1)
+ assert_nil @box::StringDelegatorObj.str_ivar1
+ assert_equal [:@str_ivar2, :@str_ivar3], @box::StringDelegatorObj.instance_variables
+
+ assert_equal [], String.instance_variables
+ end
+
+ def test_methods_added_in_box_are_invisible_globally
+ setup_box
+
+ @box.require_relative('box/string_ext')
+
+ assert_equal "yay", @box::Bar.yay
+
+ assert_raise(NoMethodError){ String.new.yay }
+ end
+
+ def test_continuous_method_definitions_in_a_box
+ setup_box
+
+ @box.require_relative('box/string_ext')
+ assert_equal "yay", @box::Bar.yay
+
+ @box.require_relative('box/string_ext_caller')
+ assert_equal "yay", @box::Foo.yay
+
+ @box.require_relative('box/string_ext_calling')
+ end
+
+ def test_methods_added_in_box_later_than_caller_code
+ setup_box
+
+ @box.require_relative('box/string_ext_caller')
+ @box.require_relative('box/string_ext')
+
+ assert_equal "yay", @box::Bar.yay
+ assert_equal "yay", @box::Foo.yay
+ end
+
+ def test_method_added_in_box_are_available_on_eval
+ setup_box
+
+ @box.require_relative('box/string_ext')
+ @box.require_relative('box/string_ext_eval_caller')
+
+ assert_equal "yay", @box::Baz.yay
+ end
+
+ def test_method_added_in_box_are_available_on_eval_with_binding
+ setup_box
+
+ @box.require_relative('box/string_ext')
+ @box.require_relative('box/string_ext_eval_caller')
+
+ assert_equal "yay, yay!", @box::Baz.yay_with_binding
+ end
+
+ def test_methods_and_constants_added_by_include
+ setup_box
+
+ @box.require_relative('box/open_class_with_include')
+
+ assert_equal "I'm saying foo 1", @box::OpenClassWithInclude.say
+ assert_equal "I'm saying foo 1", @box::OpenClassWithInclude.say_foo
+ assert_equal "I'm saying foo 1", @box::OpenClassWithInclude.say_with_obj("wow")
+
+ assert_raise(NameError) { String::FOO }
+
+ assert_equal "foo 1", @box::OpenClassWithInclude.refer_foo
+ end
+end
+
+module ProcLookupTestA
+ module B
+ VALUE = 111
+ end
+end
+
+class TestBox < Test::Unit::TestCase
+ def make_proc_from_block(&b)
+ b
+ end
+
+ def test_proc_from_main_works_with_global_definitions
+ setup_box
+
+ @box.require_relative('box/procs')
+
+ proc_and_labels = [
+ [Proc.new { String.new.yay }, "Proc.new"],
+ [proc { String.new.yay }, "proc{}"],
+ [lambda { String.new.yay }, "lambda{}"],
+ [->(){ String.new.yay }, "->(){}"],
+ [make_proc_from_block { String.new.yay }, "make_proc_from_block"],
+ [@box::ProcInBox.make_proc_from_block { String.new.yay }, "make_proc_from_block in @box"],
+ ]
+
+ proc_and_labels.each do |str_pr|
+ pr, pr_label = str_pr
+ assert_raise(NoMethodError, "NoMethodError expected: #{pr_label}, called in main") { pr.call }
+ assert_raise(NoMethodError, "NoMethodError expected: #{pr_label}, called in @box") { @box::ProcInBox.call_proc(pr) }
+ end
+
+ const_and_labels = [
+ [Proc.new { ProcLookupTestA::B::VALUE }, "Proc.new"],
+ [proc { ProcLookupTestA::B::VALUE }, "proc{}"],
+ [lambda { ProcLookupTestA::B::VALUE }, "lambda{}"],
+ [->(){ ProcLookupTestA::B::VALUE }, "->(){}"],
+ [make_proc_from_block { ProcLookupTestA::B::VALUE }, "make_proc_from_block"],
+ [@box::ProcInBox.make_proc_from_block { ProcLookupTestA::B::VALUE }, "make_proc_from_block in @box"],
+ ]
+
+ const_and_labels.each do |const_pr|
+ pr, pr_label = const_pr
+ assert_equal 111, pr.call, "111 expected, #{pr_label} called in main"
+ assert_equal 111, @box::ProcInBox.call_proc(pr), "111 expected, #{pr_label} called in @box"
+ end
+ end
+
+ def test_proc_from_box_works_with_definitions_in_box
+ setup_box
+
+ @box.require_relative('box/procs')
+
+ proc_types = [:proc_new, :proc_f, :lambda_f, :lambda_l, :block]
+
+ proc_types.each do |proc_type|
+ assert_equal 222, @box::ProcInBox.make_const_proc(proc_type).call, "ProcLookupTestA::B::VALUE should be 222 in @box"
+ assert_equal "foo", @box::ProcInBox.make_str_const_proc(proc_type).call, "String::FOO should be \"foo\" in @box"
+ assert_equal "yay", @box::ProcInBox.make_str_proc(proc_type).call, "String#yay should be callable in @box"
+ #
+ # TODO: method calls not-in-methods nor procs can't handle the current box correctly.
+ #
+ # assert_equal "yay,foo,222",
+ # @box::ProcInBox.const_get(('CONST_' + proc_type.to_s.upcase).to_sym).call,
+ # "Proc assigned to constants should refer constants correctly in @box"
+ end
+ end
+
+ def test_class_module_singleton_methods
+ setup_box
+
+ @box.require_relative('box/singleton_methods')
+
+ assert_equal "Good evening!", @box::SingletonMethods.string_greeing # def self.greeting
+ assert_equal 42, @box::SingletonMethods.integer_answer # class << self; def answer
+ assert_equal([], @box::SingletonMethods.array_blank) # def self.blank w/ instance methods
+ assert_equal({status: 200, body: 'OK'}, @box::SingletonMethods.hash_http_200) # class << self; def ... w/ instance methods
+
+ assert_equal([4, 4], @box::SingletonMethods.array_instance_methods_return_size([1, 2, 3, 4]))
+ assert_equal([3, 3], @box::SingletonMethods.hash_instance_methods_return_size({a: 2, b: 4, c: 8}))
+
+ assert_raise(NoMethodError) { String.greeting }
+ assert_raise(NoMethodError) { Integer.answer }
+ assert_raise(NoMethodError) { Array.blank }
+ assert_raise(NoMethodError) { Hash.http_200 }
+ end
+
+ def test_add_constants_in_box
+ setup_box
+
+ @box.require('envutil')
+
+ String.const_set(:STR_CONST0, 999)
+ assert_equal 999, String::STR_CONST0
+ assert_equal 999, String.const_get(:STR_CONST0)
+
+ assert_raise(NameError) { String.const_get(:STR_CONST1) }
+ assert_raise(NameError) { String::STR_CONST2 }
+ assert_raise(NameError) { String::STR_CONST3 }
+ assert_raise(NameError) { Integer.const_get(:INT_CONST1) }
+
+ EnvUtil.verbose_warning do
+ @box.require_relative('box/consts')
+ end
+
+ assert_equal 999, String::STR_CONST0
+ assert_raise(NameError) { String::STR_CONST1 }
+ assert_raise(NameError) { String::STR_CONST2 }
+ assert_raise(NameError) { Integer::INT_CONST1 }
+
+ assert_not_nil @box::ForConsts.refer_all
+
+ assert_equal 112, @box::ForConsts.refer1
+ assert_equal 112, @box::ForConsts.get1
+ assert_equal 112, @box::ForConsts::CONST1
+ assert_equal 222, @box::ForConsts.refer2
+ assert_equal 222, @box::ForConsts.get2
+ assert_equal 222, @box::ForConsts::CONST2
+ assert_equal 333, @box::ForConsts.refer3
+ assert_equal 333, @box::ForConsts.get3
+ assert_equal 333, @box::ForConsts::CONST3
+
+ @box::EnvUtil.suppress_warning do
+ @box::ForConsts.const_set(:CONST3, 334)
+ end
+ assert_equal 334, @box::ForConsts::CONST3
+ assert_equal 334, @box::ForConsts.refer3
+ assert_equal 334, @box::ForConsts.get3
+
+ assert_equal 10, @box::ForConsts.refer_top_const
+
+ # use Proxy object to use usual methods instead of singleton methods
+ proxy = @box::ForConsts::Proxy.new
+
+ assert_raise(NameError){ proxy.call_str_refer0 }
+ assert_raise(NameError){ proxy.call_str_get0 }
+
+ proxy.call_str_set0(30)
+ assert_equal 30, proxy.call_str_refer0
+ assert_equal 30, proxy.call_str_get0
+ assert_equal 999, String::STR_CONST0
+
+ proxy.call_str_remove0
+ assert_raise(NameError){ proxy.call_str_refer0 }
+ assert_raise(NameError){ proxy.call_str_get0 }
+
+ assert_equal 112, proxy.call_str_refer1
+ assert_equal 112, proxy.call_str_get1
+ assert_equal 223, proxy.call_str_refer2
+ assert_equal 223, proxy.call_str_get2
+ assert_equal 333, proxy.call_str_refer3
+ assert_equal 333, proxy.call_str_get3
+
+ EnvUtil.suppress_warning do
+ proxy.call_str_set3
+ end
+ assert_equal 334, proxy.call_str_refer3
+ assert_equal 334, proxy.call_str_get3
+
+ assert_equal 1, proxy.refer_int_const1
+
+ assert_equal 999, String::STR_CONST0
+ assert_raise(NameError) { String::STR_CONST1 }
+ assert_raise(NameError) { String::STR_CONST2 }
+ assert_raise(NameError) { String::STR_CONST3 }
+ assert_raise(NameError) { Integer::INT_CONST1 }
+ end
+
+ def test_global_variables
+ default_l = $-0
+ default_f = $,
+
+ setup_box
+
+ assert_equal "\n", $-0 # equal to $/, line splitter
+ assert_equal nil, $, # field splitter
+
+ @box.require_relative('box/global_vars')
+
+ # read first
+ assert_equal "\n", @box::LineSplitter.read
+ @box::LineSplitter.write("\r\n")
+ assert_equal "\r\n", @box::LineSplitter.read
+ assert_equal "\n", $-0
+
+ # write first
+ @box::FieldSplitter.write(",")
+ assert_equal ",", @box::FieldSplitter.read
+ assert_equal nil, $,
+
+ # used only in box
+ assert_not_include? global_variables, :$used_only_in_box
+ @box::UniqueGvar.write(123)
+ assert_equal 123, @box::UniqueGvar.read
+ assert_nil $used_only_in_box
+
+ # Kernel#global_variables returns the sum of all gvars.
+ global_gvars = global_variables.sort
+ assert_equal global_gvars, @box::UniqueGvar.gvars_in_box.sort
+ @box::UniqueGvar.write_only(456)
+ assert_equal (global_gvars + [:$write_only_var_in_box]).sort, @box::UniqueGvar.gvars_in_box.sort
+ assert_equal (global_gvars + [:$write_only_var_in_box]).sort, global_variables.sort
+ ensure
+ EnvUtil.suppress_warning do
+ $-0 = default_l
+ $, = default_f
+ end
+ end
+
+ def test_load_path_and_loaded_features
+ setup_box
+
+ assert_respond_to $LOAD_PATH, :resolve_feature_path
+
+ @box.require_relative('box/load_path')
+
+ assert_not_equal $LOAD_PATH, @box::LoadPathCheck::FIRST_LOAD_PATH
+
+ assert @box::LoadPathCheck::FIRST_LOAD_PATH_RESPOND_TO_RESOLVE
+
+ box_dir = File.join(__dir__, 'box')
+ # TODO: $LOADED_FEATURES in method calls should refer the current box in addition to the loading box.
+ # assert_include @box::LoadPathCheck.current_loaded_features, File.join(box_dir, 'blank1.rb')
+ # assert_not_include @box::LoadPathCheck.current_loaded_features, File.join(box_dir, 'blank2.rb')
+ # assert_predicate @box::LoadPathCheck, :require_blank2
+ # assert_include(@box::LoadPathCheck.current_loaded_features, File.join(box_dir, 'blank2.rb'))
+
+ assert_not_include $LOADED_FEATURES, File.join(box_dir, 'blank1.rb')
+ assert_not_include $LOADED_FEATURES, File.join(box_dir, 'blank2.rb')
+ end
+
+ def test_eval_basic
+ setup_box
+
+ # Test basic evaluation
+ result = @box.eval("1 + 1")
+ assert_equal 2, result
+
+ # Test string evaluation
+ result = @box.eval("'hello ' + 'world'")
+ assert_equal "hello world", result
+ end
+
+ def test_eval_with_constants
+ setup_box
+
+ # Define a constant in the box via eval
+ @box.eval("TEST_CONST = 42")
+ assert_equal 42, @box::TEST_CONST
+
+ # Constant should not be visible in main box
+ assert_raise(NameError) { TEST_CONST }
+ end
+
+ def test_eval_with_classes
+ setup_box
+
+ # Define a class in the box via eval
+ @box.eval("class TestClass; def hello; 'from box'; end; end")
+
+ # Class should be accessible in the box
+ instance = @box::TestClass.new
+ assert_equal "from box", instance.hello
+
+ # Class should not be visible in main box
+ assert_raise(NameError) { TestClass }
+ end
+
+ def test_eval_isolation
+ setup_box
+
+ # Create another box
+ n2 = Ruby::Box.new
+
+ # Define different constants in each box
+ @box.eval("ISOLATION_TEST = 'first'")
+ n2.eval("ISOLATION_TEST = 'second'")
+
+ # Each box should have its own constant
+ assert_equal "first", @box::ISOLATION_TEST
+ assert_equal "second", n2::ISOLATION_TEST
+
+ # Constants should not interfere with each other
+ assert_not_equal @box::ISOLATION_TEST, n2::ISOLATION_TEST
+ end
+
+ def test_eval_with_variables
+ setup_box
+
+ # Test local variable access (should work within the eval context)
+ result = @box.eval("x = 10; y = 20; x + y")
+ assert_equal 30, result
+ end
+
+ def test_eval_error_handling
+ setup_box
+
+ # Test syntax error
+ assert_raise(SyntaxError) { @box.eval("1 +") }
+
+ # Test name error
+ assert_raise(NameError) { @box.eval("undefined_variable") }
+
+ # Test that box is properly restored after error
+ begin
+ @box.eval("raise RuntimeError, 'test error'")
+ rescue RuntimeError
+ # Should be able to continue using the box
+ result = @box.eval("2 + 2")
+ assert_equal 4, result
+ end
+ end
+
+ # Tests which run always (w/o RUBY_BOX=1 globally)
+
+ def test_prelude_gems_and_loaded_features
+ assert_in_out_err([ENV_ENABLE_BOX, "--enable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error|
+ begin;
+ puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join
+ puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join
+
+ require "error_highlight"
+
+ puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join
+ puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join
+ end;
+
+ # No additional warnings except for experimental warnings
+ assert_equal 2, error.size
+ assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[0], error[0]
+ assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[1], error[1]
+
+ assert_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb'
+ assert_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb'
+ assert_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb'
+ assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb'
+ end
+ end
+
+ def test_prelude_gems_and_loaded_features_with_disable_gems
+ assert_in_out_err([ENV_ENABLE_BOX, "--disable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error|
+ begin;
+ puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join
+ puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join
+
+ require "error_highlight"
+
+ puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join
+ puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join
+ end;
+
+ assert_equal 2, error.size
+ assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[0], error[0]
+ assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[1], error[1]
+
+ refute_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb'
+ refute_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb'
+ refute_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb'
+ assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb'
+ end
+ end
+
+ def test_root_and_main_methods
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ pend unless Ruby::Box.respond_to?(:root) and Ruby::Box.respond_to?(:main) # for RUBY_DEBUG > 0
+
+ assert_respond_to Ruby::Box.root, :root?
+ assert_respond_to Ruby::Box.main, :main?
+
+ assert_predicate Ruby::Box.root, :root?
+ assert_predicate Ruby::Box.main, :main?
+ assert_equal Ruby::Box.main, Ruby::Box.current
+
+ $a = 1
+ $LOADED_FEATURES.push("/tmp/foobar")
+
+ assert_equal 2, Ruby::Box.root.eval('$a = 2; $a')
+ assert_not_include Ruby::Box.root.eval('$LOADED_FEATURES.push("/tmp/barbaz"); $LOADED_FEATURES'), "/tmp/foobar"
+ assert_equal "FooClass", Ruby::Box.root.eval('class FooClass; end; Object.const_get(:FooClass).to_s')
+
+ assert_equal 1, $a
+ assert_not_include $LOADED_FEATURES, "/tmp/barbaz"
+ assert_not_operator Object, :const_defined?, :FooClass
+ end;
+ end
+
+ def test_basic_box_detections
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ box = Ruby::Box.new
+ $gvar1 = 'bar'
+ code = <<~EOC
+ BOX1 = Ruby::Box.current
+ $gvar1 = 'foo'
+
+ def toplevel = $gvar1
+
+ class Foo
+ BOX2 = Ruby::Box.current
+ BOX2_proc = ->(){ BOX2 }
+ BOX3_proc = ->(){ Ruby::Box.current }
+
+ def box4 = Ruby::Box.current
+ def self.box5 = BOX2
+ def self.box6 = Ruby::Box.current
+ def self.box6_proc = ->(){ Ruby::Box.current }
+ def self.box7
+ res = []
+ [1,2].chunk{ it.even? }.each do |bool, members|
+ res << Ruby::Box.current.object_id.to_s + ":" + bool.to_s + ":" + members.map(&:to_s).join(",")
+ end
+ res
+ end
+
+ def self.yield_block = yield
+ def self.call_block(&b) = b.call
+
+ def self.gvar1 = $gvar1
+ def self.call_toplevel = toplevel
+ end
+ FOO_NAME = Foo.name
+
+ module Kernel
+ def foo_box = Ruby::Box.current
+ module_function :foo_box
+ end
+
+ BOX_X = Foo.new.box4
+ BOX_Y = foo_box
+ EOC
+ box.eval(code)
+ outer = Ruby::Box.current
+ assert_equal box, box::BOX1 # on TOP frame
+ assert_equal box, box::Foo::BOX2 # on CLASS frame
+ assert_equal box, box::Foo::BOX2_proc.call # proc -> a const on CLASS
+ assert_equal box, box::Foo::BOX3_proc.call # proc -> the current
+ assert_equal box, box::Foo.new.box4 # instance method -> the current
+ assert_equal box, box::Foo.box5 # singleton method -> a const on CLASS
+ assert_equal box, box::Foo.box6 # singleton method -> the current
+ assert_equal box, box::Foo.box6_proc.call # method returns a proc -> the current
+
+ # a block after CFUNC/IFUNC in a method -> the current
+ assert_equal ["#{box.object_id}:false:1", "#{box.object_id}:true:2"], box::Foo.box7
+
+ assert_equal outer, box::Foo.yield_block{ Ruby::Box.current } # method yields
+ assert_equal outer, box::Foo.call_block{ Ruby::Box.current } # method calls a block
+
+ assert_equal 'foo', box::Foo.gvar1 # method refers gvar
+ assert_equal 'bar', $gvar1 # gvar value out of the box
+ assert_equal 'foo', box::Foo.call_toplevel # toplevel method referring gvar
+
+ assert_equal box, box::BOX_X # on TOP frame, referring a class in the current
+ assert_equal box, box::BOX_Y # on TOP frame, referring Kernel method defined by a CFUNC method
+
+ assert_equal "Foo", box::FOO_NAME
+ assert_equal "Foo", box::Foo.name
+ end;
+ end
+
+ def test_loading_extension_libs_in_main_box_1
+ pend if /mswin|mingw/ =~ RUBY_PLATFORM # timeout on windows environments
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ require "prism"
+ require "optparse"
+ require "date"
+ require "time"
+ require "delegate"
+ require "singleton"
+ require "pp"
+ require "fileutils"
+ require "tempfile"
+ require "tmpdir"
+ require "json"
+ require "psych"
+ require "yaml"
+ expected = 1
+ assert_equal expected, 1
+ end;
+ end
+
+ def test_loading_extension_libs_in_main_box_2
+ pend if /mswin|mingw/ =~ RUBY_PLATFORM # timeout on windows environments
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ require "zlib"
+ require "open3"
+ require "ipaddr"
+ require "net/http"
+ require "openssl"
+ require "socket"
+ require "uri"
+ require "digest"
+ require "erb"
+ require "stringio"
+ require "monitor"
+ require "timeout"
+ require "securerandom"
+ expected = 1
+ assert_equal expected, 1
+ end;
+ end
+
+ def test_mark_box_object_referred_only_from_binding
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ box = Ruby::Box.new
+ box.eval('class Integer; def +(*)=42; end')
+ b = box.eval('binding')
+ box = nil # remove direct reference to the box
+
+ assert_equal 42, b.eval('1+2')
+
+ GC.stress = true
+ GC.start
+
+ assert_equal 42, b.eval('1+2')
+ end;
+ end
+
+ def test_loaded_extension_deleted_in_user_box
+ require 'tmpdir'
+ Dir.mktmpdir do |tmpdir|
+ env = ENV_ENABLE_BOX.merge({'TMPDIR'=>tmpdir})
+ assert_ruby_status([env], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ require "json"
+ end;
+ assert_empty(Dir.children(tmpdir))
+ end
+ end
+
+ def test_root_box_iclasses_should_be_boxable
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ Ruby::Box.root.eval("class IMath; include Math; end") # (*)
+ module Math
+ def foo = :foo
+ end
+ # This test crashes here if iclasses (created at the line (*) is not boxable)
+ class IMath2; include Math; end
+ assert_equal :foo, IMath2.new.foo
+ assert_raise NoMethodError do
+ Ruby::Box.root.eval("IMath.new.foo")
+ end
+ end;
+ end
+end
diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb
index 7843f3b476..dd1936c4e2 100644
--- a/test/ruby/test_call.rb
+++ b/test/ruby/test_call.rb
@@ -123,6 +123,25 @@ class TestCall < Test::Unit::TestCase
assert_equal([1, 2, {kw: 3}], f(*a, kw: 3))
end
+ def test_forward_argument_init
+ o = Object.new
+ def o.simple_forward_argument_init(a=eval('b'), b=1)
+ [a, b]
+ end
+
+ def o.complex_forward_argument_init(a=eval('b'), b=eval('kw'), kw: eval('kw2'), kw2: 3)
+ [a, b, kw, kw2]
+ end
+
+ def o.keyword_forward_argument_init(a: eval('b'), b: eval('kw'), kw: eval('kw2'), kw2: 3)
+ [a, b, kw, kw2]
+ end
+
+ assert_equal [nil, 1], o.simple_forward_argument_init
+ assert_equal [nil, nil, 3, 3], o.complex_forward_argument_init
+ assert_equal [nil, nil, 3, 3], o.keyword_forward_argument_init
+ end
+
def test_call_bmethod_proc
pr = proc{|sym| sym}
define_singleton_method(:a, &pr)
@@ -423,6 +442,35 @@ class TestCall < Test::Unit::TestCase
assert_equal([1, 2, {}], s(*r2ka))
end
+ def test_anon_splat_mutated_bug_21757
+ args = [1, 2]
+ kw = {bug: true}
+
+ def self.m(*); end
+ m(*args, bug: true)
+ assert_equal(2, args.length)
+
+ proc = ->(*) { }
+ proc.(*args, bug: true)
+ assert_equal(2, args.length)
+
+ def self.m2(*); end
+ m2(*args, **kw)
+ assert_equal(2, args.length)
+
+ proc = ->(*) { }
+ proc.(*args, **kw)
+ assert_equal(2, args.length)
+
+ def self.m3(*, **nil); end
+ assert_raise(ArgumentError) { m3(*args, bug: true) }
+ assert_equal(2, args.length)
+
+ proc = ->(*, **nil) { }
+ assert_raise(ArgumentError) { proc.(*args, bug: true) }
+ assert_equal(2, args.length)
+ end
+
def test_kwsplat_block_eval_order
def self.t(**kw, &b) [kw, b] end
diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb
index 456362ef21..8f12e06685 100644
--- a/test/ruby/test_class.rb
+++ b/test/ruby/test_class.rb
@@ -259,6 +259,46 @@ class TestClass < Test::Unit::TestCase
assert_raise(TypeError) { BasicObject.dup }
end
+ def test_class_hierarchy_inside_initialize_dup_bug_21538
+ ancestors = sc_ancestors = nil
+ b = Class.new
+ b.define_singleton_method(:initialize_dup) do |x|
+ ancestors = self.ancestors
+ sc_ancestors = singleton_class.ancestors
+ super(x)
+ end
+
+ a = Class.new(b)
+
+ c = a.dup
+
+ expected_ancestors = [c, b, *Object.ancestors]
+ expected_sc_ancestors = [c.singleton_class, b.singleton_class, *Object.singleton_class.ancestors]
+ assert_equal expected_ancestors, ancestors
+ assert_equal expected_sc_ancestors, sc_ancestors
+ assert_equal expected_ancestors, c.ancestors
+ assert_equal expected_sc_ancestors, c.singleton_class.ancestors
+ end
+
+ def test_class_hierarchy_inside_initialize_clone_bug_21538
+ ancestors = sc_ancestors = nil
+ a = Class.new
+ a.define_singleton_method(:initialize_clone) do |x|
+ ancestors = self.ancestors
+ sc_ancestors = singleton_class.ancestors
+ super(x)
+ end
+
+ c = a.clone
+
+ expected_ancestors = [c, *Object.ancestors]
+ expected_sc_ancestors = [c.singleton_class, *Object.singleton_class.ancestors]
+ assert_equal expected_ancestors, ancestors
+ assert_equal expected_sc_ancestors, sc_ancestors
+ assert_equal expected_ancestors, c.ancestors
+ assert_equal expected_sc_ancestors, c.singleton_class.ancestors
+ end
+
def test_singleton_class
assert_raise(TypeError) { 1.extend(Module.new) }
assert_raise(TypeError) { 1.0.extend(Module.new) }
@@ -283,12 +323,8 @@ class TestClass < Test::Unit::TestCase
assert_raise(TypeError, bug6863) { Class.new(Class.allocate) }
allocator = Class.instance_method(:allocate)
- assert_raise_with_message(TypeError, /prohibited/) {
- allocator.bind(Rational).call
- }
- assert_raise_with_message(TypeError, /prohibited/) {
- allocator.bind_call(Rational)
- }
+ assert_nothing_raised { allocator.bind(Rational).call }
+ assert_nothing_raised { allocator.bind_call(Rational) }
end
def test_nonascii_name
@@ -565,7 +601,7 @@ class TestClass < Test::Unit::TestCase
obj = Object.new
c = obj.singleton_class
obj.freeze
- assert_raise_with_message(FrozenError, /frozen object/) {
+ assert_raise_with_message(FrozenError, /frozen Object/) {
c.class_eval {def f; end}
}
end
@@ -700,9 +736,11 @@ class TestClass < Test::Unit::TestCase
def test_namescope_error_message
m = Module.new
o = m.module_eval "class A\u{3042}; self; end.new"
- assert_raise_with_message(TypeError, /A\u{3042}/) {
- o::Foo
- }
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /A\u{3042}/) {
+ o::Foo
+ }
+ end
end
def test_redefinition_mismatch
@@ -841,4 +879,70 @@ CODE
klass.define_method(:bar) {}
assert_equal klass, klass.remove_method(:bar), '[Bug #19164]'
end
+
+ def test_method_table_assignment_just_after_class_init
+ assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", 'm_tbl assignment should be done only when Class object is not promoted'
+ begin;
+ GC.stress = true
+ class C; end
+ end;
+ end
+
+ def test_singleton_cc_invalidation
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}")
+ begin;
+ class T
+ def hi
+ "hi"
+ end
+ end
+
+ t = T.new
+ t.singleton_class
+
+ def hello(t)
+ t.hi
+ end
+
+ 5.times do
+ hello(t) # populate inline cache on `t.singleton_class`.
+ end
+
+ class T
+ remove_method :hi # invalidate `t.singleton_class` ccs for `hi`
+ end
+
+ assert_raise NoMethodError do
+ hello(t)
+ end
+ end;
+ end
+
+ def test_safe_multi_ractor_subclasses_list_mutation
+ assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}"
+ begin;
+ 4.times.map do
+ Ractor.new do
+ 20_000.times do
+ Object.new.singleton_class
+ end
+ end
+ end.each(&:join)
+ end;
+ end
+
+ def test_safe_multi_ractor_singleton_class_access
+ assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}"
+ begin;
+ class A; end
+ 4.times.map do
+ Ractor.new do
+ a = A
+ 100.times do
+ a = a.singleton_class
+ end
+ end
+ end.each(&:join)
+ end;
+ end
end
diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb
index 819d0d35aa..76b961b37e 100644
--- a/test/ruby/test_compile_prism.rb
+++ b/test/ruby/test_compile_prism.rb
@@ -1046,13 +1046,19 @@ module Prism
end
def test_ForNode
- assert_prism_eval("for i in [1,2] do; i; end")
- assert_prism_eval("for @i in [1,2] do; @i; end")
- assert_prism_eval("for $i in [1,2] do; $i; end")
+ assert_prism_eval("r = []; for i in [1,2] do; r << i; end; r")
+ assert_prism_eval("r = []; for @i in [1,2] do; r << @i; end; r")
+ assert_prism_eval("r = []; for $i in [1,2] do; r << $i; end; r")
- assert_prism_eval("for foo, in [1,2,3] do end")
+ assert_prism_eval("r = []; for foo, in [1,2,3] do r << foo end; r")
- assert_prism_eval("for i, j in {a: 'b'} do; i; j; end")
+ assert_prism_eval("r = []; for i, j in {a: 'b'} do; r << [i, j]; end; r")
+
+ # Test splat node as index in for loop
+ assert_prism_eval("r = []; for *x in [[1,2], [3,4]] do; r << x; end; r")
+ assert_prism_eval("r = []; for * in [[1,2], [3,4]] do; r << 'ok'; end; r")
+ assert_prism_eval("r = []; for x, * in [[1,2], [3,4]] do; r << x; end; r")
+ assert_prism_eval("r = []; for x, *y in [[1,2], [3,4]] do; r << [x, y]; end; r")
end
############################################################################
@@ -2180,6 +2186,56 @@ end
RUBY
end
+ def test_ForwardingArgumentsNode_instruction_sequence_consistency
+ # Test that both parsers generate identical instruction sequences for forwarding arguments
+ # This prevents regressions like the one fixed in prism_compile.c for PM_FORWARDING_ARGUMENTS_NODE
+
+ # Test case from the bug report: def bar(buz, ...) = foo(buz, ...)
+ source = <<~RUBY
+ def foo(*, &block) = block
+ def bar(buz, ...) = foo(buz, ...)
+ RUBY
+
+ compare_instruction_sequences(source)
+
+ # Test simple forwarding
+ source = <<~RUBY
+ def target(...) = nil
+ def forwarder(...) = target(...)
+ RUBY
+
+ compare_instruction_sequences(source)
+
+ # Test mixed forwarding with regular arguments
+ source = <<~RUBY
+ def target(a, b, c) = [a, b, c]
+ def forwarder(x, ...) = target(x, ...)
+ RUBY
+
+ compare_instruction_sequences(source)
+
+ # Test forwarding with splat
+ source = <<~RUBY
+ def target(a, b, c) = [a, b, c]
+ def forwarder(x, ...); target(*x, ...); end
+ RUBY
+
+ compare_instruction_sequences(source)
+ end
+
+ private
+
+ def compare_instruction_sequences(source)
+ # Get instruction sequences from both parsers
+ parsey_iseq = RubyVM::InstructionSequence.compile_parsey(source)
+ prism_iseq = RubyVM::InstructionSequence.compile_prism(source)
+
+ # Compare instruction sequences
+ assert_equal parsey_iseq.disasm, prism_iseq.disasm
+ end
+
+ public
+
def test_ForwardingSuperNode
assert_prism_eval("class Forwarding; def to_s; super; end; end")
assert_prism_eval("class Forwarding; def eval(code); super { code }; end; end")
diff --git a/test/ruby/test_data.rb b/test/ruby/test_data.rb
index bb38f8ec91..fbc3205d63 100644
--- a/test/ruby/test_data.rb
+++ b/test/ruby/test_data.rb
@@ -259,9 +259,10 @@ class TestData < Test::Unit::TestCase
assert_equal(klass.new, test)
assert_not_equal(Data.define.new, test)
- assert_equal('#<data >', test.inspect)
+ assert_equal('#<data>', test.inspect)
assert_equal([], test.members)
assert_equal({}, test.to_h)
+ assert_predicate(test, :frozen?)
end
def test_dup
@@ -280,4 +281,10 @@ class TestData < Test::Unit::TestCase
assert_not_same(test, loaded)
assert_predicate(loaded, :frozen?)
end
+
+ def test_frozen_subclass
+ test = Class.new(Data.define(:a)).freeze.new(a: 0)
+ assert_kind_of(Data, test)
+ assert_equal([:a], test.members)
+ end
end
diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb
index 388b94df39..0cd5bf49dc 100644
--- a/test/ruby/test_encoding.rb
+++ b/test/ruby/test_encoding.rb
@@ -33,7 +33,7 @@ class TestEncoding < Test::Unit::TestCase
encodings.each do |e|
assert_raise(TypeError) { e.dup }
assert_raise(TypeError) { e.clone }
- assert_equal(e.object_id, Marshal.load(Marshal.dump(e)).object_id)
+ assert_same(e, Marshal.load(Marshal.dump(e)))
end
end
@@ -130,10 +130,50 @@ class TestEncoding < Test::Unit::TestCase
def test_ractor_load_encoding
assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
begin;
- Ractor.new{}.take
+ Ractor.new{}.join
$-w = nil
Encoding.default_external = Encoding::ISO8859_2
assert "[Bug #19562]"
end;
end
+
+ def test_ractor_lazy_load_encoding_concurrently
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ rs = []
+ autoload_encodings = Encoding.list.select { |e| e.inspect.include?("(autoload)") }.freeze
+ 7.times do
+ rs << Ractor.new(autoload_encodings) do |encodings|
+ str = "abc".dup
+ encodings.each do |enc|
+ str.force_encoding(enc)
+ end
+ end
+ end
+ while rs.any?
+ r, _obj = Ractor.select(*rs)
+ rs.delete(r)
+ end
+ assert_empty rs
+ end;
+ end
+
+ def test_ractor_set_default_external_string
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $-w = nil
+ rs = []
+ 7.times do |i|
+ rs << Ractor.new(i) do |i|
+ Encoding.default_external = "us-ascii"
+ end
+ end
+
+ while rs.any?
+ r, _obj = Ractor.select(*rs)
+ rs.delete(r)
+ end
+ assert_empty rs
+ end;
+ end
end
diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb
index cd62cd8acb..9b972d7b22 100644
--- a/test/ruby/test_enumerator.rb
+++ b/test/ruby/test_enumerator.rb
@@ -886,6 +886,7 @@ class TestEnumerator < Test::Unit::TestCase
def test_produce
assert_raise(ArgumentError) { Enumerator.produce }
+ assert_raise(ArgumentError) { Enumerator.produce(a: 1, b: 1) {} }
# Without initial object
passed_args = []
@@ -903,14 +904,6 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal [1, 2, 3], enum.take(3)
assert_equal [1, 2], passed_args
- # With initial keyword arguments
- passed_args = []
- enum = Enumerator.produce(a: 1, b: 1) { |obj| passed_args << obj; obj.shift if obj.respond_to?(:shift)}
- assert_instance_of(Enumerator, enum)
- assert_equal Float::INFINITY, enum.size
- assert_equal [{b: 1}, [1], :a, nil], enum.take(4)
- assert_equal [{b: 1}, [1], :a], passed_args
-
# Raising StopIteration
words = "The quick brown fox jumps over the lazy dog.".scan(/\w+/)
enum = Enumerator.produce { words.shift or raise StopIteration }
@@ -935,6 +928,25 @@ class TestEnumerator < Test::Unit::TestCase
"abc",
], enum.to_a
}
+
+ # With size keyword argument
+ enum = Enumerator.produce(1, size: 10) { |obj| obj.succ }
+ assert_equal 10, enum.size
+ assert_equal [1, 2, 3], enum.take(3)
+
+ enum = Enumerator.produce(1, size: -> { 5 }) { |obj| obj.succ }
+ assert_equal 5, enum.size
+
+ enum = Enumerator.produce(1, size: nil) { |obj| obj.succ }
+ assert_equal nil, enum.size
+
+ enum = Enumerator.produce(1, size: Float::INFINITY) { |obj| obj.succ }
+ assert_equal Float::INFINITY, enum.size
+
+ # Without initial value but with size
+ enum = Enumerator.produce(size: 3) { |obj| (obj || 0).succ }
+ assert_equal 3, enum.size
+ assert_equal [1, 2, 3], enum.take(3)
end
def test_chain_each_lambda
diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb
index c9ec920ea9..d17e300bce 100644
--- a/test/ruby/test_env.rb
+++ b/test/ruby/test_env.rb
@@ -601,13 +601,13 @@ class TestEnv < Test::Unit::TestCase
rescue Exception => e
#{exception_var} = e
end
- Ractor.yield #{exception_var}.class
+ port.send #{exception_var}.class
end;
end
def str_for_assert_raise_on_yielded_exception_class(expected_error_class, ractor_var)
<<-"end;"
- error_class = #{ractor_var}.take
+ error_class = #{ractor_var}.receive
assert_raise(#{expected_error_class}) do
if error_class < Exception
raise error_class
@@ -649,100 +649,101 @@ class TestEnv < Test::Unit::TestCase
def test_bracket_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- Ractor.yield ENV['test']
- Ractor.yield ENV['TEST']
+ Ractor.new port = Ractor::Port.new do |port|
+ port << ENV['test']
+ port << ENV['TEST']
ENV['test'] = 'foo'
- Ractor.yield ENV['test']
- Ractor.yield ENV['TEST']
+ port << ENV['test']
+ port << ENV['TEST']
ENV['TEST'] = 'bar'
- Ractor.yield ENV['TEST']
- Ractor.yield ENV['test']
+ port << ENV['TEST']
+ port << ENV['test']
#{str_for_yielding_exception_class("ENV[1]")}
#{str_for_yielding_exception_class("ENV[1] = 'foo'")}
#{str_for_yielding_exception_class("ENV['test'] = 0")}
end
- assert_nil(r.take)
- assert_nil(r.take)
- assert_equal('foo', r.take)
+ assert_nil(port.receive)
+ assert_nil(port.receive)
+ assert_equal('foo', port.receive)
if #{ignore_case_str}
- assert_equal('foo', r.take)
+ assert_equal('foo', port.receive)
else
- assert_nil(r.take)
+ assert_nil(port.receive)
end
- assert_equal('bar', r.take)
+ assert_equal('bar', port.receive)
if #{ignore_case_str}
- assert_equal('bar', r.take)
+ assert_equal('bar', port.receive)
else
- assert_equal('foo', r.take)
+ assert_equal('foo', port.receive)
end
3.times do
- #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")}
+ #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")}
end
end;
end
def test_dup_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
#{str_for_yielding_exception_class("ENV.dup")}
end
- #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")}
+ #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")}
end;
end
def test_has_value_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ port = Ractor::Port.new
+ Ractor.new port do |port|
val = 'a'
val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase)
ENV['test'] = val[0...-1]
- Ractor.yield(ENV.has_value?(val))
- Ractor.yield(ENV.has_value?(val.upcase))
+ port.send(ENV.has_value?(val))
+ port.send(ENV.has_value?(val.upcase))
ENV['test'] = val
- Ractor.yield(ENV.has_value?(val))
- Ractor.yield(ENV.has_value?(val.upcase))
+ port.send(ENV.has_value?(val))
+ port.send(ENV.has_value?(val.upcase))
ENV['test'] = val.upcase
- Ractor.yield ENV.has_value?(val)
- Ractor.yield ENV.has_value?(val.upcase)
- end
- assert_equal(false, r.take)
- assert_equal(false, r.take)
- assert_equal(true, r.take)
- assert_equal(false, r.take)
- assert_equal(false, r.take)
- assert_equal(true, r.take)
+ port.send ENV.has_value?(val)
+ port.send ENV.has_value?(val.upcase)
+ end
+ assert_equal(false, port.receive)
+ assert_equal(false, port.receive)
+ assert_equal(true, port.receive)
+ assert_equal(false, port.receive)
+ assert_equal(false, port.receive)
+ assert_equal(true, port.receive)
end;
end
def test_key_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
val = 'a'
val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase)
ENV['test'] = val[0...-1]
- Ractor.yield ENV.key(val)
- Ractor.yield ENV.key(val.upcase)
+ port.send ENV.key(val)
+ port.send ENV.key(val.upcase)
ENV['test'] = val
- Ractor.yield ENV.key(val)
- Ractor.yield ENV.key(val.upcase)
+ port.send ENV.key(val)
+ port.send ENV.key(val.upcase)
ENV['test'] = val.upcase
- Ractor.yield ENV.key(val)
- Ractor.yield ENV.key(val.upcase)
+ port.send ENV.key(val)
+ port.send ENV.key(val.upcase)
end
- assert_nil(r.take)
- assert_nil(r.take)
+ assert_nil(port.receive)
+ assert_nil(port.receive)
if #{ignore_case_str}
- assert_equal('TEST', r.take.upcase)
+ assert_equal('TEST', port.receive.upcase)
else
- assert_equal('test', r.take)
+ assert_equal('test', port.receive)
end
- assert_nil(r.take)
- assert_nil(r.take)
+ assert_nil(port.receive)
+ assert_nil(port.receive)
if #{ignore_case_str}
- assert_equal('TEST', r.take.upcase)
+ assert_equal('TEST', port.receive.upcase)
else
- assert_equal('test', r.take)
+ assert_equal('test', port.receive)
end
end;
@@ -750,87 +751,87 @@ class TestEnv < Test::Unit::TestCase
def test_delete_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
#{str_to_yield_invalid_envvar_errors("v", "ENV.delete(v)")}
- Ractor.yield ENV.delete("TEST")
+ port.send ENV.delete("TEST")
#{str_for_yielding_exception_class("ENV.delete('#{PATH_ENV}')")}
- Ractor.yield(ENV.delete("TEST"){|name| "NO "+name})
+ port.send(ENV.delete("TEST"){|name| "NO "+name})
end
- #{str_to_receive_invalid_envvar_errors("r")}
- assert_nil(r.take)
- exception_class = r.take
+ #{str_to_receive_invalid_envvar_errors("port")}
+ assert_nil(port.receive)
+ exception_class = port.receive
assert_equal(NilClass, exception_class)
- assert_equal("NO TEST", r.take)
+ assert_equal("NO TEST", port.receive)
end;
end
def test_getenv_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
#{str_to_yield_invalid_envvar_errors("v", "ENV[v]")}
ENV["#{PATH_ENV}"] = ""
- Ractor.yield ENV["#{PATH_ENV}"]
- Ractor.yield ENV[""]
+ port.send ENV["#{PATH_ENV}"]
+ port.send ENV[""]
end
- #{str_to_receive_invalid_envvar_errors("r")}
- assert_equal("", r.take)
- assert_nil(r.take)
+ #{str_to_receive_invalid_envvar_errors("port")}
+ assert_equal("", port.receive)
+ assert_nil(port.receive)
end;
end
def test_fetch_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV["test"] = "foo"
- Ractor.yield ENV.fetch("test")
+ port.send ENV.fetch("test")
ENV.delete("test")
#{str_for_yielding_exception_class("ENV.fetch('test')", exception_var: "ex")}
- Ractor.yield ex.receiver.object_id
- Ractor.yield ex.key
- Ractor.yield ENV.fetch("test", "foo")
- Ractor.yield(ENV.fetch("test"){"bar"})
+ port.send ex.receiver.object_id
+ port.send ex.key
+ port.send ENV.fetch("test", "foo")
+ port.send(ENV.fetch("test"){"bar"})
#{str_to_yield_invalid_envvar_errors("v", "ENV.fetch(v)")}
#{str_for_yielding_exception_class("ENV.fetch('#{PATH_ENV}', 'foo')")}
ENV['#{PATH_ENV}'] = ""
- Ractor.yield ENV.fetch('#{PATH_ENV}')
- end
- assert_equal("foo", r.take)
- #{str_for_assert_raise_on_yielded_exception_class(KeyError, "r")}
- assert_equal(ENV.object_id, r.take)
- assert_equal("test", r.take)
- assert_equal("foo", r.take)
- assert_equal("bar", r.take)
- #{str_to_receive_invalid_envvar_errors("r")}
- exception_class = r.take
+ port.send ENV.fetch('#{PATH_ENV}')
+ end
+ assert_equal("foo", port.receive)
+ #{str_for_assert_raise_on_yielded_exception_class(KeyError, "port")}
+ assert_equal(ENV.object_id, port.receive)
+ assert_equal("test", port.receive)
+ assert_equal("foo", port.receive)
+ assert_equal("bar", port.receive)
+ #{str_to_receive_invalid_envvar_errors("port")}
+ exception_class = port.receive
assert_equal(NilClass, exception_class)
- assert_equal("", r.take)
+ assert_equal("", port.receive)
end;
end
def test_aset_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
#{str_for_yielding_exception_class("ENV['test'] = nil")}
ENV["test"] = nil
- Ractor.yield ENV["test"]
+ port.send ENV["test"]
#{str_to_yield_invalid_envvar_errors("v", "ENV[v] = 'test'")}
#{str_to_yield_invalid_envvar_errors("v", "ENV['test'] = v")}
end
- exception_class = r.take
+ exception_class = port.receive
assert_equal(NilClass, exception_class)
- assert_nil(r.take)
- #{str_to_receive_invalid_envvar_errors("r")}
- #{str_to_receive_invalid_envvar_errors("r")}
+ assert_nil(port.receive)
+ #{str_to_receive_invalid_envvar_errors("port")}
+ #{str_to_receive_invalid_envvar_errors("port")}
end;
end
def test_keys_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
a = ENV.keys
- Ractor.yield a
+ port.send a
end
- a = r.take
+ a = port.receive
assert_kind_of(Array, a)
a.each {|k| assert_kind_of(String, k) }
end;
@@ -839,11 +840,11 @@ class TestEnv < Test::Unit::TestCase
def test_each_key_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- ENV.each_key {|k| Ractor.yield(k)}
- Ractor.yield "finished"
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.each_key {|k| port.send(k)}
+ port.send "finished"
end
- while((x=r.take) != "finished")
+ while((x=port.receive) != "finished")
assert_kind_of(String, x)
end
end;
@@ -851,11 +852,11 @@ class TestEnv < Test::Unit::TestCase
def test_values_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
a = ENV.values
- Ractor.yield a
+ port.send a
end
- a = r.take
+ a = port.receive
assert_kind_of(Array, a)
a.each {|k| assert_kind_of(String, k) }
end;
@@ -863,11 +864,11 @@ class TestEnv < Test::Unit::TestCase
def test_each_value_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- ENV.each_value {|k| Ractor.yield(k)}
- Ractor.yield "finished"
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.each_value {|k| port.send(k)}
+ port.send "finished"
end
- while((x=r.take) != "finished")
+ while((x=port.receive) != "finished")
assert_kind_of(String, x)
end
end;
@@ -875,11 +876,11 @@ class TestEnv < Test::Unit::TestCase
def test_each_pair_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- ENV.each_pair {|k, v| Ractor.yield([k,v])}
- Ractor.yield "finished"
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.each_pair {|k, v| port.send([k,v])}
+ port.send "finished"
end
- while((k,v=r.take) != "finished")
+ while((k,v=port.receive) != "finished")
assert_kind_of(String, k)
assert_kind_of(String, v)
end
@@ -888,116 +889,116 @@ class TestEnv < Test::Unit::TestCase
def test_reject_bang_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
ENV["test"] = "foo"
ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
h2 = {}
ENV.each_pair {|k, v| h2[k] = v }
- Ractor.yield [h1, h2]
- Ractor.yield(ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" })
+ port.send [h1, h2]
+ port.send(ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" })
end
- h1, h2 = r.take
+ h1, h2 = port.receive
assert_equal(h1, h2)
- assert_nil(r.take)
+ assert_nil(port.receive)
end;
end
def test_delete_if_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
ENV["test"] = "foo"
ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
h2 = {}
ENV.each_pair {|k, v| h2[k] = v }
- Ractor.yield [h1, h2]
- Ractor.yield (ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }).object_id
+ port.send [h1, h2]
+ port.send (ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" })
end
- h1, h2 = r.take
+ h1, h2 = port.receive
assert_equal(h1, h2)
- assert_equal(ENV.object_id, r.take)
+ assert_same(ENV, port.receive)
end;
end
def test_select_bang_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
ENV["test"] = "foo"
ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }
h2 = {}
ENV.each_pair {|k, v| h2[k] = v }
- Ractor.yield [h1, h2]
- Ractor.yield(ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
+ port.send [h1, h2]
+ port.send(ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
end
- h1, h2 = r.take
+ h1, h2 = port.receive
assert_equal(h1, h2)
- assert_nil(r.take)
+ assert_nil(port.receive)
end;
end
def test_filter_bang_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
ENV["test"] = "foo"
ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }
h2 = {}
ENV.each_pair {|k, v| h2[k] = v }
- Ractor.yield [h1, h2]
- Ractor.yield(ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
+ port.send [h1, h2]
+ port.send(ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
end
- h1, h2 = r.take
+ h1, h2 = port.receive
assert_equal(h1, h2)
- assert_nil(r.take)
+ assert_nil(port.receive)
end;
end
def test_keep_if_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
ENV["test"] = "foo"
ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }
h2 = {}
ENV.each_pair {|k, v| h2[k] = v }
- Ractor.yield [h1, h2]
- Ractor.yield (ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }).object_id
+ port.send [h1, h2]
+ port.send (ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
end
- h1, h2 = r.take
+ h1, h2 = port.receive
assert_equal(h1, h2)
- assert_equal(ENV.object_id, r.take)
+ assert_equal(ENV, port.receive)
end;
end
def test_values_at_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV["test"] = "foo"
- Ractor.yield ENV.values_at("test", "test")
+ port.send ENV.values_at("test", "test")
end
- assert_equal(["foo", "foo"], r.take)
+ assert_equal(["foo", "foo"], port.receive)
end;
end
def test_select_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV["test"] = "foo"
h = ENV.select {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
- Ractor.yield h.size
+ port.send h.size
k = h.keys.first
v = h.values.first
- Ractor.yield [k, v]
+ port.send [k, v]
end
- assert_equal(1, r.take)
- k, v = r.take
+ assert_equal(1, port.receive)
+ k, v = port.receive
if #{ignore_case_str}
assert_equal("TEST", k.upcase)
assert_equal("FOO", v.upcase)
@@ -1010,16 +1011,16 @@ class TestEnv < Test::Unit::TestCase
def test_filter_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV["test"] = "foo"
h = ENV.filter {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
- Ractor.yield(h.size)
+ port.send(h.size)
k = h.keys.first
v = h.values.first
- Ractor.yield [k, v]
+ port.send [k, v]
end
- assert_equal(1, r.take)
- k, v = r.take
+ assert_equal(1, port.receive)
+ k, v = port.receive
if #{ignore_case_str}
assert_equal("TEST", k.upcase)
assert_equal("FOO", v.upcase)
@@ -1032,49 +1033,49 @@ class TestEnv < Test::Unit::TestCase
def test_slice_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
ENV["bar"] = "rab"
- Ractor.yield(ENV.slice())
- Ractor.yield(ENV.slice(""))
- Ractor.yield(ENV.slice("unknown"))
- Ractor.yield(ENV.slice("foo", "baz"))
- end
- assert_equal({}, r.take)
- assert_equal({}, r.take)
- assert_equal({}, r.take)
- assert_equal({"foo"=>"bar", "baz"=>"qux"}, r.take)
+ port.send(ENV.slice())
+ port.send(ENV.slice(""))
+ port.send(ENV.slice("unknown"))
+ port.send(ENV.slice("foo", "baz"))
+ end
+ assert_equal({}, port.receive)
+ assert_equal({}, port.receive)
+ assert_equal({}, port.receive)
+ assert_equal({"foo"=>"bar", "baz"=>"qux"}, port.receive)
end;
end
def test_except_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
ENV["bar"] = "rab"
- Ractor.yield ENV.except()
- Ractor.yield ENV.except("")
- Ractor.yield ENV.except("unknown")
- Ractor.yield ENV.except("foo", "baz")
- end
- assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take)
- assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take)
- assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take)
- assert_equal({"bar"=>"rab"}, r.take)
+ port.send ENV.except()
+ port.send ENV.except("")
+ port.send ENV.except("unknown")
+ port.send ENV.except("foo", "baz")
+ end
+ assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, port.receive)
+ assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, port.receive)
+ assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, port.receive)
+ assert_equal({"bar"=>"rab"}, port.receive)
end;
end
def test_clear_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
- Ractor.yield ENV.size
+ port.send ENV.size
end
- assert_equal(0, r.take)
+ assert_equal(0, port.receive)
end;
end
@@ -1083,20 +1084,20 @@ class TestEnv < Test::Unit::TestCase
r = Ractor.new do
ENV.to_s
end
- assert_equal("ENV", r.take)
+ assert_equal("ENV", r.value)
end;
end
def test_inspect_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
s = ENV.inspect
- Ractor.yield s
+ port.send s
end
- s = r.take
+ s = port.receive
expected = ['"foo" => "bar"', '"baz" => "qux"']
unless s.start_with?(/\{"foo"/i)
expected.reverse!
@@ -1112,14 +1113,14 @@ class TestEnv < Test::Unit::TestCase
def test_to_a_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
a = ENV.to_a
- Ractor.yield a
+ port.send a
end
- a = r.take
+ a = port.receive
assert_equal(2, a.size)
expected = [%w(baz qux), %w(foo bar)]
if #{ignore_case_str}
@@ -1136,59 +1137,59 @@ class TestEnv < Test::Unit::TestCase
r = Ractor.new do
ENV.rehash
end
- assert_nil(r.take)
+ assert_nil(r.value)
end;
end
def test_size_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
s = ENV.size
ENV["test"] = "foo"
- Ractor.yield [s, ENV.size]
+ port.send [s, ENV.size]
end
- s, s2 = r.take
+ s, s2 = port.receive
assert_equal(s + 1, s2)
end;
end
def test_empty_p_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
- Ractor.yield ENV.empty?
+ port.send ENV.empty?
ENV["test"] = "foo"
- Ractor.yield ENV.empty?
+ port.send ENV.empty?
end
- assert r.take
- assert !r.take
+ assert port.receive
+ assert !port.receive
end;
end
def test_has_key_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- Ractor.yield ENV.has_key?("test")
+ Ractor.new port = Ractor::Port.new do |port|
+ port.send ENV.has_key?("test")
ENV["test"] = "foo"
- Ractor.yield ENV.has_key?("test")
+ port.send ENV.has_key?("test")
#{str_to_yield_invalid_envvar_errors("v", "ENV.has_key?(v)")}
end
- assert !r.take
- assert r.take
- #{str_to_receive_invalid_envvar_errors("r")}
+ assert !port.receive
+ assert port.receive
+ #{str_to_receive_invalid_envvar_errors("port")}
end;
end
def test_assoc_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- Ractor.yield ENV.assoc("test")
+ Ractor.new port = Ractor::Port.new do |port|
+ port.send ENV.assoc("test")
ENV["test"] = "foo"
- Ractor.yield ENV.assoc("test")
+ port.send ENV.assoc("test")
#{str_to_yield_invalid_envvar_errors("v", "ENV.assoc(v)")}
end
- assert_nil(r.take)
- k, v = r.take
+ assert_nil(port.receive)
+ k, v = port.receive
if #{ignore_case_str}
assert_equal("TEST", k.upcase)
assert_equal("FOO", v.upcase)
@@ -1196,7 +1197,7 @@ class TestEnv < Test::Unit::TestCase
assert_equal("test", k)
assert_equal("foo", v)
end
- #{str_to_receive_invalid_envvar_errors("r")}
+ #{str_to_receive_invalid_envvar_errors("port")}
encoding = /mswin|mingw/ =~ RUBY_PLATFORM ? Encoding::UTF_8 : Encoding.find("locale")
assert_equal(encoding, v.encoding)
end;
@@ -1204,29 +1205,29 @@ class TestEnv < Test::Unit::TestCase
def test_has_value2_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
- Ractor.yield ENV.has_value?("foo")
+ port.send ENV.has_value?("foo")
ENV["test"] = "foo"
- Ractor.yield ENV.has_value?("foo")
+ port.send ENV.has_value?("foo")
end
- assert !r.take
- assert r.take
+ assert !port.receive
+ assert port.receive
end;
end
def test_rassoc_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
- Ractor.yield ENV.rassoc("foo")
+ port.send ENV.rassoc("foo")
ENV["foo"] = "bar"
ENV["test"] = "foo"
ENV["baz"] = "qux"
- Ractor.yield ENV.rassoc("foo")
+ port.send ENV.rassoc("foo")
end
- assert_nil(r.take)
- k, v = r.take
+ assert_nil(port.receive)
+ k, v = port.receive
if #{ignore_case_str}
assert_equal("TEST", k.upcase)
assert_equal("FOO", v.upcase)
@@ -1239,39 +1240,39 @@ class TestEnv < Test::Unit::TestCase
def test_to_hash_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h = {}
ENV.each {|k, v| h[k] = v }
- Ractor.yield [h, ENV.to_hash]
+ port.send [h, ENV.to_hash]
end
- h, h2 = r.take
+ h, h2 = port.receive
assert_equal(h, h2)
end;
end
def test_to_h_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- Ractor.yield [ENV.to_hash, ENV.to_h]
- Ractor.yield [ENV.map {|k, v| ["$\#{k}", v.size]}.to_h, ENV.to_h {|k, v| ["$\#{k}", v.size]}]
+ Ractor.new port = Ractor::Port.new do |port|
+ port.send [ENV.to_hash, ENV.to_h]
+ port.send [ENV.map {|k, v| ["$\#{k}", v.size]}.to_h, ENV.to_h {|k, v| ["$\#{k}", v.size]}]
end
- a, b = r.take
+ a, b = port.receive
assert_equal(a,b)
- c, d = r.take
+ c, d = port.receive
assert_equal(c,d)
end;
end
def test_reject_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
ENV["test"] = "foo"
h2 = ENV.reject {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
- Ractor.yield [h1, h2]
+ port.send [h1, h2]
end
- h1, h2 = r.take
+ h1, h2 = port.receive
assert_equal(h1, h2)
end;
end
@@ -1279,86 +1280,86 @@ class TestEnv < Test::Unit::TestCase
def test_shift_in_ractor
assert_ractor(<<-"end;")
#{STR_DEFINITION_FOR_CHECK}
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
a = ENV.shift
b = ENV.shift
- Ractor.yield [a,b]
- Ractor.yield ENV.shift
+ port.send [a,b]
+ port.send ENV.shift
end
- a,b = r.take
+ a,b = port.receive
check([a, b], [%w(foo bar), %w(baz qux)])
- assert_nil(r.take)
+ assert_nil(port.receive)
end;
end
def test_invert_in_ractor
assert_ractor(<<-"end;")
#{STR_DEFINITION_FOR_CHECK}
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
- Ractor.yield(ENV.invert)
+ port.send(ENV.invert)
end
- check(r.take.to_a, [%w(bar foo), %w(qux baz)])
+ check(port.receive.to_a, [%w(bar foo), %w(qux baz)])
end;
end
def test_replace_in_ractor
assert_ractor(<<-"end;")
#{STR_DEFINITION_FOR_CHECK}
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV["foo"] = "xxx"
ENV.replace({"foo"=>"bar", "baz"=>"qux"})
- Ractor.yield ENV.to_hash
+ port.send ENV.to_hash
ENV.replace({"Foo"=>"Bar", "Baz"=>"Qux"})
- Ractor.yield ENV.to_hash
+ port.send ENV.to_hash
end
- check(r.take.to_a, [%w(foo bar), %w(baz qux)])
- check(r.take.to_a, [%w(Foo Bar), %w(Baz Qux)])
+ check(port.receive.to_a, [%w(foo bar), %w(baz qux)])
+ check(port.receive.to_a, [%w(Foo Bar), %w(Baz Qux)])
end;
end
def test_update_in_ractor
assert_ractor(<<-"end;")
#{STR_DEFINITION_FOR_CHECK}
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
ENV.update({"baz"=>"quux","a"=>"b"})
- Ractor.yield ENV.to_hash
+ port.send ENV.to_hash
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 }
- Ractor.yield ENV.to_hash
+ port.send ENV.to_hash
end
- check(r.take.to_a, [%w(foo bar), %w(baz quux), %w(a b)])
- check(r.take.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
+ check(port.receive.to_a, [%w(foo bar), %w(baz quux), %w(a b)])
+ check(port.receive.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
end;
end
def test_huge_value_in_ractor
assert_ractor(<<-"end;")
huge_value = "bar" * 40960
- r = Ractor.new huge_value do |v|
+ Ractor.new port = Ractor::Port.new, huge_value do |port, v|
ENV["foo"] = "bar"
#{str_for_yielding_exception_class("ENV['foo'] = v ")}
- Ractor.yield ENV["foo"]
+ port.send ENV["foo"]
end
if /mswin|ucrt/ =~ RUBY_PLATFORM
- #{str_for_assert_raise_on_yielded_exception_class(Errno::EINVAL, "r")}
- result = r.take
+ #{str_for_assert_raise_on_yielded_exception_class(Errno::EINVAL, "port")}
+ result = port.receive
assert_equal("bar", result)
else
- exception_class = r.take
+ exception_class = port.receive
assert_equal(NilClass, exception_class)
- result = r.take
+ result = port.receive
assert_equal(huge_value, result)
end
end;
@@ -1366,42 +1367,43 @@ class TestEnv < Test::Unit::TestCase
def test_frozen_env_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
#{str_for_yielding_exception_class("ENV.freeze")}
end
- #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")}
+ #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")}
end;
end
def test_frozen_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV["#{PATH_ENV}"] = "/"
ENV.each do |k, v|
- Ractor.yield [k.frozen?]
- Ractor.yield [v.frozen?]
+ port.send [k]
+ port.send [v]
end
ENV.each_key do |k|
- Ractor.yield [k.frozen?]
+ port.send [k]
end
ENV.each_value do |v|
- Ractor.yield [v.frozen?]
+ port.send [v]
end
ENV.each_key do |k|
- Ractor.yield [ENV[k].frozen?, "[\#{k.dump}]"]
- Ractor.yield [ENV.fetch(k).frozen?, "fetch(\#{k.dump})"]
+ port.send [ENV[k], "[\#{k.dump}]"]
+ port.send [ENV.fetch(k), "fetch(\#{k.dump})"]
end
- Ractor.yield "finished"
+ port.send "finished"
end
- while((params=r.take) != "finished")
- assert(*params)
+ while((params=port.receive) != "finished")
+ value, *params = params
+ assert_predicate(value, :frozen?, *params)
end
end;
end
def test_shared_substring_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
bug12475 = '[ruby-dev:49655] [Bug #12475]'
n = [*"0".."9"].join("")*3
e0 = ENV[n0 = "E\#{n}"]
@@ -1411,9 +1413,9 @@ class TestEnv < Test::Unit::TestCase
ENV[n1.chop] = "T\#{n}.".chop
ENV[n0], e0 = e0, ENV[n0]
ENV[n1], e1 = e1, ENV[n1]
- Ractor.yield [n, e0, e1, bug12475]
+ port.send [n, e0, e1, bug12475]
end
- n, e0, e1, bug12475 = r.take
+ n, e0, e1, bug12475 = port.receive
assert_equal("T\#{n}", e0, bug12475)
assert_nil(e1, bug12475)
end;
@@ -1429,7 +1431,7 @@ class TestEnv < Test::Unit::TestCase
rescue Ractor::IsolationError => e
e
end
- assert_equal Ractor::IsolationError, r_get.take.class
+ assert_equal Ractor::IsolationError, r_get.value.class
r_get = Ractor.new do
ENV.instance_eval{ @a }
@@ -1437,7 +1439,7 @@ class TestEnv < Test::Unit::TestCase
e
end
- assert_equal Ractor::IsolationError, r_get.take.class
+ assert_equal Ractor::IsolationError, r_get.value.class
r_set = Ractor.new do
ENV.instance_eval{ @b = "hello" }
@@ -1445,7 +1447,7 @@ class TestEnv < Test::Unit::TestCase
e
end
- assert_equal Ractor::IsolationError, r_set.take.class
+ assert_equal Ractor::IsolationError, r_set.value.class
RUBY
end
diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb
index 84581180b6..31e5aa9f6b 100644
--- a/test/ruby/test_exception.rb
+++ b/test/ruby/test_exception.rb
@@ -992,7 +992,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
assert_equal 1, outs.size
assert_equal 0, errs.size
err = outs.first.force_encoding('utf-8')
- assert err.valid_encoding?, 'must be valid encoding'
+ assert_predicate err, :valid_encoding?
assert_match %r/\u3042/, err
end
end
@@ -1525,4 +1525,31 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
assert_in_out_err(%W[-r#{lib} #{main}], "", [], [:*, "\n""path=#{main}\n", :*])
end
end
+
+ class Ex; end
+
+ def test_exception_message_for_unexpected_implicit_conversion_type
+ a = Ex.new
+ def self.x(a) = nil
+
+ assert_raise_with_message(TypeError, "no implicit conversion of TestException::Ex into Hash") do
+ x(**a)
+ end
+ assert_raise_with_message(TypeError, "no implicit conversion of TestException::Ex into Proc") do
+ x(&a)
+ end
+
+ def a.to_a = 1
+ def a.to_hash = 1
+ def a.to_proc = 1
+ assert_raise_with_message(TypeError, "can't convert TestException::Ex to Array (TestException::Ex#to_a gives Integer)") do
+ x(*a)
+ end
+ assert_raise_with_message(TypeError, "can't convert TestException::Ex to Hash (TestException::Ex#to_hash gives Integer)") do
+ x(**a)
+ end
+ assert_raise_with_message(TypeError, "can't convert TestException::Ex to Proc (TestException::Ex#to_proc gives Integer)") do
+ x(&a)
+ end
+ end
end
diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb
index 1f78307748..b7d2b71c19 100644
--- a/test/ruby/test_fiber.rb
+++ b/test/ruby/test_fiber.rb
@@ -498,7 +498,7 @@ class TestFiber < Test::Unit::TestCase
end
def test_machine_stack_gc
- assert_normal_exit <<-RUBY, '[Bug #14561]', timeout: 10
+ assert_normal_exit <<-RUBY, '[Bug #14561]', timeout: 60
enum = Enumerator.new { |y| y << 1 }
thread = Thread.new { enum.peek }
thread.join
diff --git a/test/ruby/test_file.rb b/test/ruby/test_file.rb
index eae9a8e7b0..a3d6221c0f 100644
--- a/test/ruby/test_file.rb
+++ b/test/ruby/test_file.rb
@@ -372,9 +372,9 @@ class TestFile < Test::Unit::TestCase
end
def test_stat
- tb = Process.clock_gettime(Process::CLOCK_REALTIME)
+ btime = Process.clock_gettime(Process::CLOCK_REALTIME)
Tempfile.create("stat") {|file|
- tb = (tb + Process.clock_gettime(Process::CLOCK_REALTIME)) / 2
+ btime = (btime + Process.clock_gettime(Process::CLOCK_REALTIME)) / 2
file.close
path = file.path
@@ -384,33 +384,32 @@ class TestFile < Test::Unit::TestCase
sleep 2
- t1 = measure_time do
+ mtime = measure_time do
File.write(path, "bar")
end
sleep 2
- t2 = measure_time do
- File.read(path)
+ ctime = measure_time do
File.chmod(0644, path)
end
sleep 2
- t3 = measure_time do
+ atime = measure_time do
File.read(path)
end
delta = 1
stat = File.stat(path)
- assert_in_delta tb, stat.birthtime.to_f, delta
- assert_in_delta t1, stat.mtime.to_f, delta
+ assert_in_delta btime, stat.birthtime.to_f, delta
+ assert_in_delta mtime, stat.mtime.to_f, delta
if stat.birthtime != stat.ctime
- assert_in_delta t2, stat.ctime.to_f, delta
+ assert_in_delta ctime, stat.ctime.to_f, delta
end
if /mswin|mingw/ !~ RUBY_PLATFORM && !Bug::File::Fs.noatime?(path)
# Windows delays updating atime
- assert_in_delta t3, stat.atime.to_f, delta
+ assert_in_delta atime, stat.atime.to_f, delta
end
}
rescue NotImplementedError
diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb
index bb64483a79..394dc47603 100644
--- a/test/ruby/test_file_exhaustive.rb
+++ b/test/ruby/test_file_exhaustive.rb
@@ -197,12 +197,32 @@ class TestFileExhaustive < Test::Unit::TestCase
[regular_file, utf8_file].each do |file|
assert_equal(file, File.open(file) {|f| f.path})
assert_equal(file, File.path(file))
- o = Object.new
- class << o; self; end.class_eval do
- define_method(:to_path) { file }
- end
+ o = Struct.new(:to_path).new(file)
+ assert_equal(file, File.path(o))
+ o = Struct.new(:to_str).new(file)
assert_equal(file, File.path(o))
end
+
+ conv_error = ->(method, msg = "converting with #{method}") {
+ test = ->(&new) do
+ o = new.(42)
+ assert_raise(TypeError, msg) {File.path(o)}
+
+ o = new.("abc".encode(Encoding::UTF_32BE))
+ assert_raise(Encoding::CompatibilityError, msg) {File.path(o)}
+
+ ["\0", "a\0", "a\0c"].each do |path|
+ o = new.(path)
+ assert_raise(ArgumentError, msg) {File.path(o)}
+ end
+ end
+
+ test.call(&:itself)
+ test.call(&Struct.new(method).method(:new))
+ }
+
+ conv_error[:to_path]
+ conv_error[:to_str]
end
def assert_integer(n)
@@ -1477,6 +1497,7 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def test_test
+ omit 'timestamp check is unstable on macOS' if RUBY_PLATFORM =~ /darwin/
fn1 = regular_file
hardlinkfile
sleep(1.1)
diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb
index b865d339a9..d0d180593a 100644
--- a/test/ruby/test_float.rb
+++ b/test/ruby/test_float.rb
@@ -861,7 +861,9 @@ class TestFloat < Test::Unit::TestCase
assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-32le"))}
assert_raise(Encoding::CompatibilityError) {Float("0".encode("iso-2022-jp"))}
- assert_raise_with_message(ArgumentError, /\u{1f4a1}/) {Float("\u{1f4a1}")}
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(ArgumentError, /\u{1f4a1}/) {Float("\u{1f4a1}")}
+ end
end
def test_invalid_str
diff --git a/test/ruby/test_frozen.rb b/test/ruby/test_frozen.rb
index 2918a2afd8..6721cb1128 100644
--- a/test/ruby/test_frozen.rb
+++ b/test/ruby/test_frozen.rb
@@ -27,4 +27,20 @@ class TestFrozen < Test::Unit::TestCase
str.freeze
assert_raise(FrozenError) { str.instance_variable_set(:@b, 1) }
end
+
+ def test_setting_ivar_on_frozen_string_with_singleton_class
+ str = "str"
+ str.singleton_class
+ str.freeze
+ assert_raise_with_message(FrozenError, "can't modify frozen String: \"str\"") { str.instance_variable_set(:@a, 1) }
+ end
+
+ class A
+ freeze
+ end
+
+ def test_setting_ivar_on_frozen_class
+ assert_raise_with_message(FrozenError, "can't modify frozen Class: TestFrozen::A") { A.instance_variable_set(:@a, 1) }
+ assert_raise_with_message(FrozenError, "can't modify frozen Class: #<Class:TestFrozen::A>") { A.singleton_class.instance_variable_set(:@a, 1) }
+ end
end
diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb
index a1229fc87a..09199c34b1 100644
--- a/test/ruby/test_gc.rb
+++ b/test/ruby/test_gc.rb
@@ -75,12 +75,9 @@ class TestGc < Test::Unit::TestCase
GC.start
end
- def test_gc_config_setting_returns_nil_for_missing_keys
- missing_value = GC.config(no_such_key: true)[:no_such_key]
- assert_nil(missing_value)
- ensure
- GC.config(full_mark: true)
- GC.start
+ def test_gc_config_setting_returns_config_hash
+ hash = GC.config(no_such_key: true)
+ assert_equal(GC.config, hash)
end
def test_gc_config_disable_major
@@ -234,6 +231,9 @@ class TestGc < Test::Unit::TestCase
end
assert_equal (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]) * (2**i), stat_heap[:slot_size]
+ assert_operator stat_heap[:heap_live_slots], :<=, stat[:heap_live_slots]
+ assert_operator stat_heap[:heap_free_slots], :<=, stat[:heap_free_slots]
+ assert_operator stat_heap[:heap_final_slots], :<=, stat[:heap_final_slots]
assert_operator stat_heap[:heap_eden_pages], :<=, stat[:heap_eden_pages]
assert_operator stat_heap[:heap_eden_slots], :>=, 0
assert_operator stat_heap[:total_allocated_pages], :>=, 0
@@ -264,7 +264,7 @@ class TestGc < Test::Unit::TestCase
GC.stat_heap(i, stat_heap)
# Remove keys that can vary between invocations
- %i(total_allocated_objects).each do |sym|
+ %i(total_allocated_objects heap_live_slots heap_free_slots).each do |sym|
stat_heap[sym] = stat_heap_all[i][sym] = 0
end
@@ -289,6 +289,9 @@ class TestGc < Test::Unit::TestCase
hash.each { |k, v| stat_heap_sum[k] += v }
end
+ assert_equal stat[:heap_live_slots], stat_heap_sum[:heap_live_slots]
+ assert_equal stat[:heap_free_slots], stat_heap_sum[:heap_free_slots]
+ assert_equal stat[:heap_final_slots], stat_heap_sum[:heap_final_slots]
assert_equal stat[:heap_eden_pages], stat_heap_sum[:heap_eden_pages]
assert_equal stat[:heap_available_slots], stat_heap_sum[:heap_eden_slots]
assert_equal stat[:total_allocated_objects], stat_heap_sum[:total_allocated_objects]
@@ -296,7 +299,7 @@ class TestGc < Test::Unit::TestCase
end
def test_measure_total_time
- assert_separately([], __FILE__, __LINE__, <<~RUBY)
+ assert_separately([], __FILE__, __LINE__, <<~RUBY, timeout: 60)
GC.measure_total_time = false
time_before = GC.stat(:time)
@@ -379,53 +382,36 @@ class TestGc < Test::Unit::TestCase
def test_latest_gc_info_weak_references_count
assert_separately([], __FILE__, __LINE__, <<~RUBY)
GC.disable
- count = 10_000
+ COUNT = 10_000
# Some weak references may be created, so allow some margin of error
error_tolerance = 100
- # Run full GC to clear out weak references
- GC.start
- # Run full GC again to collect stats about weak references
+ # Run full GC to collect stats about weak references
GC.start
before_weak_references_count = GC.latest_gc_info(:weak_references_count)
- before_retained_weak_references_count = GC.latest_gc_info(:retained_weak_references_count)
- # Create some objects and place it in a WeakMap
- wmap = ObjectSpace::WeakMap.new
- ary = Array.new(count)
- enum = count.times
- enum.each.with_index do |i|
- obj = Object.new
- ary[i] = obj
- wmap[obj] = nil
+ # Create some WeakMaps
+ ary = Array.new(COUNT)
+ COUNT.times.with_index do |i|
+ ary[i] = ObjectSpace::WeakMap.new
end
# Run full GC to collect stats about weak references
GC.start
- assert_operator(GC.latest_gc_info(:weak_references_count), :>=, before_weak_references_count + count - error_tolerance)
- assert_operator(GC.latest_gc_info(:retained_weak_references_count), :>=, before_retained_weak_references_count + count - error_tolerance)
- assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, GC.latest_gc_info(:weak_references_count))
+ assert_operator(GC.latest_gc_info(:weak_references_count), :>=, before_weak_references_count + COUNT - error_tolerance)
before_weak_references_count = GC.latest_gc_info(:weak_references_count)
- before_retained_weak_references_count = GC.latest_gc_info(:retained_weak_references_count)
# Clear ary, so if ary itself is somewhere on the stack, it won't hold all references
ary.clear
ary = nil
- # Free ary, which should empty out the wmap
- GC.start
- # Run full GC again to collect stats about weak references
+ # Free ary, which should GC all the WeakMaps
GC.start
- # Sometimes the WeakMap has a few elements, which might be held on by registers.
- assert_operator(wmap.size, :<=, 2)
-
- assert_operator(GC.latest_gc_info(:weak_references_count), :<=, before_weak_references_count - count + error_tolerance)
- assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, before_retained_weak_references_count - count + error_tolerance)
- assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, GC.latest_gc_info(:weak_references_count))
+ assert_operator(GC.latest_gc_info(:weak_references_count), :<=, before_weak_references_count - COUNT + error_tolerance)
RUBY
end
@@ -452,7 +438,7 @@ class TestGc < Test::Unit::TestCase
end
def test_singleton_method_added
- assert_in_out_err([], <<-EOS, [], [], "[ruby-dev:44436]")
+ assert_in_out_err([], <<-EOS, [], [], "[ruby-dev:44436]", timeout: 30)
class BasicObject
undef singleton_method_added
def singleton_method_added(mid)
@@ -467,13 +453,6 @@ class TestGc < Test::Unit::TestCase
end
def test_gc_parameter
- env = {
- "RUBY_GC_HEAP_INIT_SLOTS" => "100"
- }
- assert_in_out_err([env, "-W0", "-e", "exit"], "", [], [])
- assert_in_out_err([env, "-W:deprecated", "-e", "exit"], "", [],
- /The environment variable RUBY_GC_HEAP_INIT_SLOTS is deprecated; use environment variables RUBY_GC_HEAP_%d_INIT_SLOTS instead/)
-
env = {}
GC.stat_heap.keys.each do |heap|
env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = "200000"
@@ -703,7 +682,14 @@ class TestGc < Test::Unit::TestCase
allocate_large_object
end
- assert_operator(GC.stat(:heap_available_slots), :<, COUNT * 2)
+ # Running GC here is required to prevent this test from being flaky because
+ # the heap for the small transient objects may not have been cleared by the
+ # GC causing heap_available_slots to be slightly over 2 * COUNT.
+ GC.start
+
+ heap_available_slots = GC.stat(:heap_available_slots)
+
+ assert_operator(heap_available_slots, :<, COUNT * 2, "GC.stat: #{GC.stat}\nGC.stat_heap: #{GC.stat_heap}")
RUBY
end
@@ -740,6 +726,7 @@ class TestGc < Test::Unit::TestCase
end
def test_interrupt_in_finalizer
+ omit 'randomly hangs on many platforms' if ENV.key?('GITHUB_ACTIONS')
bug10595 = '[ruby-core:66825] [Bug #10595]'
src = <<-'end;'
Signal.trap(:INT, 'DEFAULT')
@@ -755,7 +742,7 @@ class TestGc < Test::Unit::TestCase
ObjectSpace.define_finalizer(Object.new, f)
end
end;
- out, err, status = assert_in_out_err(["-e", src], "", [], [], bug10595, signal: :SEGV) do |*result|
+ out, err, status = assert_in_out_err(["-e", src], "", [], [], bug10595, signal: :SEGV, timeout: 100) do |*result|
break result
end
unless /mswin|mingw/ =~ RUBY_PLATFORM
@@ -822,6 +809,8 @@ class TestGc < Test::Unit::TestCase
end
def test_exception_in_finalizer_procs
+ require '-test-/stack'
+ omit 'failing with ASAN' if Thread.asan?
assert_in_out_err(["-W0"], "#{<<~"begin;"}\n#{<<~'end;'}", %w[c1 c2])
c1 = proc do
puts "c1"
@@ -842,6 +831,8 @@ class TestGc < Test::Unit::TestCase
end
def test_exception_in_finalizer_method
+ require '-test-/stack'
+ omit 'failing with ASAN' if Thread.asan?
assert_in_out_err(["-W0"], "#{<<~"begin;"}\n#{<<~'end;'}", %w[c1 c2])
def self.c1(x)
puts "c1"
@@ -907,4 +898,25 @@ class TestGc < Test::Unit::TestCase
assert_include ObjectSpace.dump(young_obj), '"old":true'
end
end
+
+ def test_finalizer_not_run_with_vm_lock
+ assert_ractor(<<~'RUBY')
+ Thread.new do
+ loop do
+ Encoding.list.each do |enc|
+ enc.names
+ end
+ end
+ end
+
+ o = Object.new
+ ObjectSpace.define_finalizer(o, proc do
+ sleep 0.5 # finalizer shouldn't be run with VM lock, otherwise this context switch will crash
+ end)
+ o = nil
+ 4.times do
+ GC.start
+ end
+ RUBY
+ end
end
diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb
index 3eaa93dfae..7e0c499dd9 100644
--- a/test/ruby/test_gc_compact.rb
+++ b/test/ruby/test_gc_compact.rb
@@ -30,7 +30,7 @@ class TestGCCompact < Test::Unit::TestCase
def test_enable_autocompact
before = GC.auto_compact
GC.auto_compact = true
- assert GC.auto_compact
+ assert_predicate GC, :auto_compact
ensure
GC.auto_compact = before
end
@@ -151,12 +151,12 @@ class TestGCCompact < Test::Unit::TestCase
def walk_ast ast
children = ast.children.grep(RubyVM::AbstractSyntaxTree::Node)
children.each do |child|
- assert child.type
+ assert_predicate child, :type
walk_ast child
end
end
ast = RubyVM::AbstractSyntaxTree.parse_file #{__FILE__.dump}
- assert GC.compact
+ assert_predicate GC, :compact
walk_ast ast
end;
end
@@ -324,7 +324,7 @@ class TestGCCompact < Test::Unit::TestCase
}.resume
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
- assert_operator(stats.dig(:moved_up, :T_ARRAY) || 0, :>=, ARY_COUNT - 10)
+ assert_operator(stats.dig(:moved_up, :T_ARRAY) || 0, :>=, (0.9995 * ARY_COUNT).to_i)
refute_empty($arys.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
end;
end
@@ -356,11 +356,27 @@ class TestGCCompact < Test::Unit::TestCase
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
- assert_operator(stats.dig(:moved_up, :T_OBJECT) || 0, :>=, OBJ_COUNT - 10)
+ assert_operator(stats.dig(:moved_up, :T_OBJECT) || 0, :>=, OBJ_COUNT - 15)
refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
end;
end
+ def test_compact_objects_of_varying_sizes
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
+ begin;
+ $objects = []
+ 160.times do |n|
+ obj = Class.new.new
+ n.times { |i| obj.instance_variable_set("@foo" + i.to_s, 0) }
+ $objects << obj
+ end
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+ end;
+ end
+
def test_moving_strings_up_heaps
omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
@@ -377,7 +393,7 @@ class TestGCCompact < Test::Unit::TestCase
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
- assert_operator(stats[:moved_up][:T_STRING], :>=, STR_COUNT - 10)
+ assert_operator(stats[:moved_up][:T_STRING], :>=, STR_COUNT - 15)
refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
end;
end
diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb
index b6c18ea958..32384f5a5c 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -880,21 +880,20 @@ class TestHash < Test::Unit::TestCase
assert_equal(quote1, eval(quote1).inspect)
assert_equal(quote2, eval(quote2).inspect)
assert_equal(quote3, eval(quote3).inspect)
- begin
- verbose_bak, $VERBOSE = $VERBOSE, nil
- enc = Encoding.default_external
- Encoding.default_external = Encoding::ASCII
+
+ EnvUtil.with_default_external(Encoding::ASCII) do
utf8_ascii_hash = '{"\\u3042": 1}'
assert_equal(eval(utf8_ascii_hash).inspect, utf8_ascii_hash)
- Encoding.default_external = Encoding::UTF_8
+ end
+
+ EnvUtil.with_default_external(Encoding::UTF_8) do
utf8_hash = "{\u3042: 1}"
assert_equal(eval(utf8_hash).inspect, utf8_hash)
- Encoding.default_external = Encoding::Windows_31J
+ end
+
+ EnvUtil.with_default_external(Encoding::Windows_31J) do
sjis_hash = "{\x87]: 1}".force_encoding('sjis')
assert_equal(eval(sjis_hash).inspect, sjis_hash)
- ensure
- Encoding.default_external = enc
- $VERBOSE = verbose_bak
end
end
@@ -1297,6 +1296,17 @@ class TestHash < Test::Unit::TestCase
assert_equal(@cls[a: 10, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], h)
end
+ def test_update_modify_in_block
+ a = @cls[]
+ (1..1337).each {|k| a[k] = k}
+ b = {1=>1338}
+ assert_raise_with_message(RuntimeError, /rehash during iteration/) do
+ a.update(b) {|k, o, n|
+ a.rehash
+ }
+ end
+ end
+
def test_update_on_identhash
key = +'a'
i = @cls[].compare_by_identity
@@ -1853,6 +1863,14 @@ class TestHash < Test::Unit::TestCase
end
end
assert_equal(@cls[a: 2, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], x)
+
+ x = (1..1337).to_h {|k| [k, k]}
+ assert_raise_with_message(RuntimeError, /rehash during iteration/) do
+ x.transform_values! {|v|
+ x.rehash if v == 1337
+ v * 2
+ }
+ end
end
def hrec h, n, &b
@@ -1989,7 +2007,7 @@ class TestHashOnly < Test::Unit::TestCase
EnvUtil.without_gc do
before = ObjectSpace.count_objects[:T_STRING]
- 5.times{ h["abc"] }
+ 5.times{ h["abc".freeze] }
assert_equal before, ObjectSpace.count_objects[:T_STRING]
end
end
@@ -2119,7 +2137,9 @@ class TestHashOnly < Test::Unit::TestCase
def test_iterlevel_in_ivar_bug19589
h = { a: nil }
- hash_iter_recursion(h, 200)
+ # Recursion level should be over 127 to actually test iterlevel being set in an instance variable,
+ # but it should be under 131 not to overflow the stack under MN threads/ractors.
+ hash_iter_recursion(h, 130)
assert true
end
@@ -2336,6 +2356,11 @@ class TestHashOnly < Test::Unit::TestCase
end
end
+ def test_bug_21357
+ h = {x: []}.merge(x: nil) { |_k, v1, _v2| v1 }
+ assert_equal({x: []}, h)
+ end
+
def test_any_hash_fixable
20.times do
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb
index fb7aabba35..f9bf4fa20c 100644
--- a/test/ruby/test_integer.rb
+++ b/test/ruby/test_integer.rb
@@ -158,7 +158,9 @@ class TestInteger < Test::Unit::TestCase
assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("utf-32le"))}
assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("iso-2022-jp"))}
- assert_raise_with_message(ArgumentError, /\u{1f4a1}/) {Integer("\u{1f4a1}")}
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(ArgumentError, /\u{1f4a1}/) {Integer("\u{1f4a1}")}
+ end
obj = Struct.new(:s).new(%w[42 not-an-integer])
def obj.to_str; s.shift; end
diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb
index 32d7519bdf..1adf47ac51 100644
--- a/test/ruby/test_io.rb
+++ b/test/ruby/test_io.rb
@@ -467,6 +467,24 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_each_codepoint_with_ungetc
+ bug21562 = '[ruby-core:123176] [Bug #21562]'
+ with_read_pipe("") {|p|
+ p.binmode
+ p.ungetc("aa")
+ a = ""
+ p.each_codepoint { |c| a << c }
+ assert_equal("aa", a, bug21562)
+ }
+ with_read_pipe("") {|p|
+ p.set_encoding("ascii-8bit", universal_newline: true)
+ p.ungetc("aa")
+ a = ""
+ p.each_codepoint { |c| a << c }
+ assert_equal("aa", a, bug21562)
+ }
+ end
+
def test_rubydev33072
t = make_tempfile
path = t.path
@@ -2601,36 +2619,15 @@ class TestIO < Test::Unit::TestCase
assert_equal({:a=>1}, open(o, {a: 1}))
end
- def test_open_pipe
- assert_deprecated_warning(/Kernel#open with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
- open("|" + EnvUtil.rubybin, "r+") do |f|
- f.puts "puts 'foo'"
- f.close_write
- assert_equal("foo\n", f.read)
- end
- end
- end
+ def test_path_with_pipe
+ mkcdtmpdir do
+ cmd = "|echo foo"
+ assert_file.not_exist?(cmd)
- def test_read_command
- assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
- assert_equal("foo\n", IO.read("|echo foo"))
- end
- assert_raise(Errno::ENOENT, Errno::EINVAL) do
- File.read("|#{EnvUtil.rubybin} -e puts")
- end
- assert_raise(Errno::ENOENT, Errno::EINVAL) do
- File.binread("|#{EnvUtil.rubybin} -e puts")
- end
- assert_raise(Errno::ENOENT, Errno::EINVAL) do
- Class.new(IO).read("|#{EnvUtil.rubybin} -e puts")
- end
- assert_raise(Errno::ENOENT, Errno::EINVAL) do
- Class.new(IO).binread("|#{EnvUtil.rubybin} -e puts")
- end
- assert_raise(Errno::ESPIPE) do
- assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
- IO.read("|#{EnvUtil.rubybin} -e 'puts :foo'", 1, 1)
- end
+ pipe_errors = [Errno::ENOENT, Errno::EINVAL, Errno::EACCES, Errno::EPERM]
+ assert_raise(*pipe_errors) { open(cmd, "r+") }
+ assert_raise(*pipe_errors) { IO.read(cmd) }
+ assert_raise(*pipe_errors) { IO.foreach(cmd) {|x| assert false } }
end
end
@@ -2835,19 +2832,6 @@ class TestIO < Test::Unit::TestCase
end
def test_foreach
- a = []
-
- assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
- IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :foo; puts :bar; puts :baz'") {|x| a << x }
- end
- assert_equal(["foo\n", "bar\n", "baz\n"], a)
-
- a = []
- assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
- IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :zot'", :open_args => ["r"]) {|x| a << x }
- end
- assert_equal(["zot\n"], a)
-
make_tempfile {|t|
a = []
IO.foreach(t.path) {|x| a << x }
@@ -2923,10 +2907,10 @@ class TestIO < Test::Unit::TestCase
end
def test_print_separators
- EnvUtil.suppress_warning {
- $, = ':'
- $\ = "\n"
- }
+ assert_deprecated_warning(/non-nil '\$,'/) {$, = ":"}
+ assert_raise(TypeError) {$, = 1}
+ assert_deprecated_warning(/non-nil '\$\\'/) {$\ = "\n"}
+ assert_raise(TypeError) {$/ = 1}
pipe(proc do |w|
w.print('a')
EnvUtil.suppress_warning {w.print('a','b','c')}
@@ -3826,7 +3810,7 @@ __END__
end
tempfiles = []
- (0..fd_setsize+1).map {|i|
+ (0...fd_setsize).map {|i|
tempfiles << Tempfile.create("test_io_select_with_many_files")
}
@@ -4416,4 +4400,29 @@ __END__
end
RUBY
end
+
+ def test_fork_close
+ omit "fork is not supported" unless Process.respond_to?(:fork)
+
+ assert_separately([], <<~'RUBY')
+ r, w = IO.pipe
+
+ thread = Thread.new do
+ r.read
+ end
+
+ Thread.pass until thread.status == "sleep"
+
+ pid = fork do
+ r.close
+ end
+
+ w.close
+
+ status = Process.wait2(pid).last
+ thread.join
+
+ assert_predicate(status, :success?)
+ RUBY
+ end
end
diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb
index 55296c1f23..706ce16c42 100644
--- a/test/ruby/test_io_buffer.rb
+++ b/test/ruby/test_io_buffer.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: false
require 'tempfile'
+require 'rbconfig/sizeof'
class TestIOBuffer < Test::Unit::TestCase
experimental = Warning[:experimental]
@@ -45,22 +46,22 @@ class TestIOBuffer < Test::Unit::TestCase
def test_new_internal
buffer = IO::Buffer.new(1024, IO::Buffer::INTERNAL)
assert_equal 1024, buffer.size
- refute buffer.external?
- assert buffer.internal?
- refute buffer.mapped?
+ refute_predicate buffer, :external?
+ assert_predicate buffer, :internal?
+ refute_predicate buffer, :mapped?
end
def test_new_mapped
buffer = IO::Buffer.new(1024, IO::Buffer::MAPPED)
assert_equal 1024, buffer.size
- refute buffer.external?
- refute buffer.internal?
- assert buffer.mapped?
+ refute_predicate buffer, :external?
+ refute_predicate buffer, :internal?
+ assert_predicate buffer, :mapped?
end
def test_new_readonly
buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::READONLY)
- assert buffer.readonly?
+ assert_predicate buffer, :readonly?
assert_raise IO::Buffer::AccessError do
buffer.set_string("")
@@ -73,12 +74,64 @@ class TestIOBuffer < Test::Unit::TestCase
def test_file_mapped
buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)}
- contents = buffer.get_string
+ assert_equal File.size(__FILE__), buffer.size
+ contents = buffer.get_string
assert_include contents, "Hello World"
assert_equal Encoding::BINARY, contents.encoding
end
+ def test_file_mapped_with_size
+ buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, 30, 0, IO::Buffer::READONLY)}
+ assert_equal 30, buffer.size
+
+ contents = buffer.get_string
+ assert_equal "# frozen_string_literal: false", contents
+ assert_equal Encoding::BINARY, contents.encoding
+ end
+
+ def test_file_mapped_size_too_large
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 200_000, 0, IO::Buffer::READONLY)}
+ end
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, File.size(__FILE__) + 1, 0, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_size_just_enough
+ File.open(__FILE__) {|file|
+ assert_equal File.size(__FILE__), IO::Buffer.map(file, File.size(__FILE__), 0, IO::Buffer::READONLY).size
+ }
+ end
+
+ def test_file_mapped_offset_too_large
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, nil, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)}
+ end
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 20, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_zero_size
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 0, 0, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_negative_size
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, -10, 0, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_negative_offset
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 20, -1, IO::Buffer::READONLY)}
+ end
+ end
+
def test_file_mapped_invalid
assert_raise TypeError do
IO::Buffer.map("foobar")
@@ -88,19 +141,19 @@ class TestIOBuffer < Test::Unit::TestCase
def test_string_mapped
string = "Hello World"
buffer = IO::Buffer.for(string)
- assert buffer.readonly?
+ assert_predicate buffer, :readonly?
end
def test_string_mapped_frozen
string = "Hello World".freeze
buffer = IO::Buffer.for(string)
- assert buffer.readonly?
+ assert_predicate buffer, :readonly?
end
def test_string_mapped_mutable
string = "Hello World"
IO::Buffer.for(string) do |buffer|
- refute buffer.readonly?
+ refute_predicate buffer, :readonly?
buffer.set_value(:U8, 0, "h".ord)
@@ -121,6 +174,16 @@ class TestIOBuffer < Test::Unit::TestCase
end
end
+ def test_string_mapped_buffer_frozen
+ string = "Hello World".freeze
+ IO::Buffer.for(string) do |buffer|
+ assert_raise IO::Buffer::AccessError, "Buffer is not writable!" do
+ buffer.set_string("abc")
+ end
+ assert_equal "H".ord, buffer.get_value(:U8, 0)
+ end
+ end
+
def test_non_string
not_string = Object.new
@@ -343,10 +406,17 @@ class TestIOBuffer < Test::Unit::TestCase
:u64 => [0, 2**64-1],
:s64 => [-2**63, 0, 2**63-1],
+ :U128 => [0, 2**64, 2**127-1, 2**128-1],
+ :S128 => [-2**127, -2**63-1, -1, 0, 2**63, 2**127-1],
+ :u128 => [0, 2**64, 2**127-1, 2**128-1],
+ :s128 => [-2**127, -2**63-1, -1, 0, 2**63, 2**127-1],
+
:F32 => [-1.0, 0.0, 0.5, 1.0, 128.0],
:F64 => [-1.0, 0.0, 0.5, 1.0, 128.0],
}
+ SIZE_MAX = RbConfig::LIMITS["SIZE_MAX"]
+
def test_get_set_value
buffer = IO::Buffer.new(128)
@@ -355,6 +425,16 @@ class TestIOBuffer < Test::Unit::TestCase
buffer.set_value(data_type, 0, value)
assert_equal value, buffer.get_value(data_type, 0), "Converting #{value} as #{data_type}."
end
+ assert_raise(ArgumentError) {buffer.get_value(data_type, 128)}
+ assert_raise(ArgumentError) {buffer.set_value(data_type, 128, 0)}
+ case data_type
+ when :U8, :S8
+ else
+ assert_raise(ArgumentError) {buffer.get_value(data_type, 127)}
+ assert_raise(ArgumentError) {buffer.set_value(data_type, 127, 0)}
+ assert_raise(ArgumentError) {buffer.get_value(data_type, SIZE_MAX)}
+ assert_raise(ArgumentError) {buffer.set_value(data_type, SIZE_MAX, 0)}
+ end
end
end
@@ -411,6 +491,7 @@ class TestIOBuffer < Test::Unit::TestCase
buffer = IO::Buffer.for(string)
assert_equal string.bytes, buffer.each_byte.to_a
+ assert_equal string.bytes[3, 5], buffer.each_byte(3, 5).to_a
end
def test_zero_length_each_byte
@@ -421,7 +502,21 @@ class TestIOBuffer < Test::Unit::TestCase
def test_clear
buffer = IO::Buffer.new(16)
- buffer.set_string("Hello World!")
+ assert_equal "\0" * 16, buffer.get_string
+ buffer.clear(1)
+ assert_equal "\1" * 16, buffer.get_string
+ buffer.clear(2, 1, 2)
+ assert_equal "\1" + "\2"*2 + "\1"*13, buffer.get_string
+ buffer.clear(2, 1)
+ assert_equal "\1" + "\2"*15, buffer.get_string
+ buffer.clear(260)
+ assert_equal "\4" * 16, buffer.get_string
+ assert_raise(TypeError) {buffer.clear("x")}
+
+ assert_raise(ArgumentError) {buffer.clear(0, 20)}
+ assert_raise(ArgumentError) {buffer.clear(0, 0, 20)}
+ assert_raise(ArgumentError) {buffer.clear(0, 10, 10)}
+ assert_raise(ArgumentError) {buffer.clear(0, SIZE_MAX-7, 10)}
end
def test_invalidation
@@ -620,8 +715,8 @@ class TestIOBuffer < Test::Unit::TestCase
buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE)
begin
- assert buffer.private?
- refute buffer.readonly?
+ assert_predicate buffer, :private?
+ refute_predicate buffer, :readonly?
buffer.set_string("J")
@@ -683,4 +778,156 @@ class TestIOBuffer < Test::Unit::TestCase
buf.set_string('a', 0, 0)
assert_predicate buf, :empty?
end
+
+ # https://bugs.ruby-lang.org/issues/21210
+ def test_bug_21210
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ str = +"hello"
+ buf = IO::Buffer.for(str)
+ assert_predicate buf, :valid?
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_predicate buf, :valid?
+ end
+
+ def test_128_bit_integers
+ buffer = IO::Buffer.new(32)
+
+ # Test unsigned 128-bit integers
+ test_values_u128 = [
+ 0,
+ 1,
+ 2**64 - 1,
+ 2**64,
+ 2**127 - 1,
+ 2**128 - 1,
+ ]
+
+ test_values_u128.each do |value|
+ buffer.set_value(:u128, 0, value)
+ assert_equal value, buffer.get_value(:u128, 0), "u128: #{value}"
+
+ buffer.set_value(:U128, 0, value)
+ assert_equal value, buffer.get_value(:U128, 0), "U128: #{value}"
+ end
+
+ # Test signed 128-bit integers
+ test_values_s128 = [
+ -2**127,
+ -2**63 - 1,
+ -1,
+ 0,
+ 1,
+ 2**63,
+ 2**127 - 1,
+ ]
+
+ test_values_s128.each do |value|
+ buffer.set_value(:s128, 0, value)
+ assert_equal value, buffer.get_value(:s128, 0), "s128: #{value}"
+
+ buffer.set_value(:S128, 0, value)
+ assert_equal value, buffer.get_value(:S128, 0), "S128: #{value}"
+ end
+
+ # Test size_of
+ assert_equal 16, IO::Buffer.size_of(:u128)
+ assert_equal 16, IO::Buffer.size_of(:U128)
+ assert_equal 16, IO::Buffer.size_of(:s128)
+ assert_equal 16, IO::Buffer.size_of(:S128)
+ assert_equal 32, IO::Buffer.size_of([:u128, :u128])
+ end
+
+ def test_integer_endianness_swapping
+ # Test that byte order is swapped correctly for all signed and unsigned integers > 1 byte
+ host_is_le = IO::Buffer::HOST_ENDIAN == IO::Buffer::LITTLE_ENDIAN
+ host_is_be = IO::Buffer::HOST_ENDIAN == IO::Buffer::BIG_ENDIAN
+
+ # Test values that will produce different byte patterns when swapped
+ # Format: [little_endian_type, big_endian_type, test_value, expected_swapped_value]
+ # expected_swapped_value is the result when writing as le_type and reading as be_type
+ # (or vice versa) on a little-endian host
+ test_cases = [
+ [:u16, :U16, 0x1234, 0x3412],
+ [:s16, :S16, 0x1234, 0x3412],
+ [:u32, :U32, 0x12345678, 0x78563412],
+ [:s32, :S32, 0x12345678, 0x78563412],
+ [:u64, :U64, 0x0123456789ABCDEF, 0xEFCDAB8967452301],
+ [:s64, :S64, 0x0123456789ABCDEF, -1167088121787636991],
+ [:u128, :U128, 0x0123456789ABCDEF0123456789ABCDEF, 0xEFCDAB8967452301EFCDAB8967452301],
+ [:u128, :U128, 0x0123456789ABCDEFFEDCBA9876543210, 0x1032547698BADCFEEFCDAB8967452301],
+ [:u128, :U128, 0xFEDCBA98765432100123456789ABCDEF, 0xEFCDAB89674523011032547698BADCFE],
+ [:u128, :U128, 0x123456789ABCDEF0FEDCBA9876543210, 0x1032547698BADCFEF0DEBC9A78563412],
+ [:s128, :S128, 0x0123456789ABCDEF0123456789ABCDEF, -21528975894082904073953971026863512831],
+ [:s128, :S128, 0x0123456789ABCDEFFEDCBA9876543210, 0x1032547698BADCFEEFCDAB8967452301],
+ ]
+
+ test_cases.each do |le_type, be_type, value, expected_swapped|
+ buffer_size = IO::Buffer.size_of(le_type)
+ buffer = IO::Buffer.new(buffer_size * 2)
+
+ # Test little-endian round-trip
+ buffer.set_value(le_type, 0, value)
+ result_le = buffer.get_value(le_type, 0)
+ assert_equal value, result_le, "#{le_type}: round-trip failed"
+
+ # Test big-endian round-trip
+ buffer.set_value(be_type, buffer_size, value)
+ result_be = buffer.get_value(be_type, buffer_size)
+ assert_equal value, result_be, "#{be_type}: round-trip failed"
+
+ # Verify byte patterns are different when endianness differs from host
+ if host_is_le
+ # On little-endian host: le_type should match host, be_type should be swapped
+ # So the byte patterns should be different (unless value is symmetric)
+ # Read back with opposite endianness to verify swapping
+ result_le_read_as_be = buffer.get_value(be_type, 0)
+ result_be_read_as_le = buffer.get_value(le_type, buffer_size)
+
+ # The swapped reads should NOT equal the original value (unless it's symmetric)
+ # For most values, this will be different
+ if value != 0 && value != -1 && value.abs != 1
+ refute_equal value, result_le_read_as_be, "#{le_type} written, read as #{be_type} should be swapped on LE host"
+ refute_equal value, result_be_read_as_le, "#{be_type} written, read as #{le_type} should be swapped on LE host"
+ end
+
+ # Verify that reading back with correct endianness works
+ assert_equal value, buffer.get_value(le_type, 0), "#{le_type} should read correctly on LE host"
+ assert_equal value, buffer.get_value(be_type, buffer_size), "#{be_type} should read correctly on LE host (with swapping)"
+ elsif host_is_be
+ # On big-endian host: be_type should match host, le_type should be swapped
+ result_le_read_as_be = buffer.get_value(be_type, 0)
+ result_be_read_as_le = buffer.get_value(le_type, buffer_size)
+
+ # The swapped reads should NOT equal the original value (unless it's symmetric)
+ if value != 0 && value != -1 && value.abs != 1
+ refute_equal value, result_le_read_as_be, "#{le_type} written, read as #{be_type} should be swapped on BE host"
+ refute_equal value, result_be_read_as_le, "#{be_type} written, read as #{le_type} should be swapped on BE host"
+ end
+
+ # Verify that reading back with correct endianness works
+ assert_equal value, buffer.get_value(be_type, buffer_size), "#{be_type} should read correctly on BE host"
+ assert_equal value, buffer.get_value(le_type, 0), "#{le_type} should read correctly on BE host (with swapping)"
+ end
+
+ # Verify that when we write with one endianness and read with the opposite,
+ # we get the expected swapped value
+ buffer.set_value(le_type, 0, value)
+ swapped_value_le_to_be = buffer.get_value(be_type, 0)
+ assert_equal expected_swapped, swapped_value_le_to_be, "#{le_type} written, read as #{be_type} should produce expected swapped value"
+
+ # Also verify the reverse direction
+ buffer.set_value(be_type, buffer_size, value)
+ swapped_value_be_to_le = buffer.get_value(le_type, buffer_size)
+ assert_equal expected_swapped, swapped_value_be_to_le, "#{be_type} written, read as #{le_type} should produce expected swapped value"
+
+ # Verify that writing the swapped value back and reading with original endianness
+ # gives us the original value (double-swap should restore original)
+ buffer.set_value(be_type, 0, swapped_value_le_to_be)
+ round_trip_value = buffer.get_value(le_type, 0)
+ assert_equal value, round_trip_value, "#{le_type}/#{be_type}: double-swap should restore original value"
+ end
+ end
end
diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb
index b01d627d92..83d4fb0c7b 100644
--- a/test/ruby/test_io_m17n.rb
+++ b/test/ruby/test_io_m17n.rb
@@ -1395,30 +1395,6 @@ EOT
}
end
- def test_open_pipe_r_enc
- EnvUtil.suppress_warning do # https://bugs.ruby-lang.org/issues/19630
- open("|#{EnvUtil.rubybin} -e 'putc 255'", "r:ascii-8bit") {|f|
- assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
- assert_equal(nil, f.internal_encoding)
- s = f.read
- assert_equal(Encoding::ASCII_8BIT, s.encoding)
- assert_equal("\xff".force_encoding("ascii-8bit"), s)
- }
- end
- end
-
- def test_open_pipe_r_enc2
- EnvUtil.suppress_warning do # https://bugs.ruby-lang.org/issues/19630
- open("|#{EnvUtil.rubybin} -e 'putc \"\\u3042\"'", "r:UTF-8") {|f|
- assert_equal(Encoding::UTF_8, f.external_encoding)
- assert_equal(nil, f.internal_encoding)
- s = f.read
- assert_equal(Encoding::UTF_8, s.encoding)
- assert_equal("\u3042", s)
- }
- end
- end
-
def test_s_foreach_enc
with_tmpdir {
generate_file("t", "\xff")
@@ -2748,8 +2724,8 @@ EOT
def test_pos_with_buffer_end_cr
bug6401 = '[ruby-core:44874]'
with_tmpdir {
- # Read buffer size is 8191. This generates '\r' at 8191.
- lines = ["X" * 8187, "X"]
+ # Read buffer size is 8192. This generates '\r' at 8192.
+ lines = ["X" * 8188, "X"]
generate_file("tmp", lines.join("\r\n") + "\r\n")
open("tmp", "r") do |f|
@@ -2830,4 +2806,17 @@ EOT
flunk failure.join("\n---\n")
end
end
+
+ def test_each_codepoint_encoding_with_ungetc
+ File.open(File::NULL, "rt:utf-8") do |f|
+ f.ungetc(%Q[\u{3042}\u{3044}\u{3046}])
+ assert_equal [0x3042, 0x3044, 0x3046], f.each_codepoint.to_a
+ end
+ File.open(File::NULL, "rt:us-ascii") do |f|
+ f.ungetc(%Q[\u{3042}\u{3044}\u{3046}])
+ assert_raise(ArgumentError) do
+ f.each_codepoint.to_a
+ end
+ end
+ end
end
diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb
index 86c1f51dde..fa716787fe 100644
--- a/test/ruby/test_iseq.rb
+++ b/test/ruby/test_iseq.rb
@@ -139,8 +139,7 @@ class TestISeq < Test::Unit::TestCase
def test_lambda_with_ractor_roundtrip
iseq = compile(<<~EOF, __LINE__+1)
x = 42
- y = nil.instance_eval{ lambda { x } }
- Ractor.make_shareable(y)
+ y = Ractor.shareable_lambda{x}
y.call
EOF
assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
@@ -158,22 +157,18 @@ class TestISeq < Test::Unit::TestCase
def test_ractor_unshareable_outer_variable
name = "\u{2603 26a1}"
- y = nil.instance_eval do
- eval("proc {#{name} = nil; proc {|x| #{name} = x}}").call
- end
assert_raise_with_message(ArgumentError, /\(#{name}\)/) do
- Ractor.make_shareable(y)
- end
- y = nil.instance_eval do
- eval("proc {#{name} = []; proc {|x| #{name}}}").call
+ eval("#{name} = nil; Ractor.shareable_proc{#{name} = nil}")
end
- assert_raise_with_message(Ractor::IsolationError, /'#{name}'/) do
- Ractor.make_shareable(y)
+
+ assert_raise_with_message(Ractor::IsolationError, /\'#{name}\'/) do
+ eval("#{name} = []; Ractor.shareable_proc{#{name}}")
end
+
obj = Object.new
- def obj.foo(*) nil.instance_eval{ ->{super} } end
- assert_raise_with_message(Ractor::IsolationError, /refer unshareable object \[\] from variable '\*'/) do
- Ractor.make_shareable(obj.foo(*[]))
+ def obj.foo(*) Ractor.shareable_proc{super} end
+ assert_raise_with_message(Ractor::IsolationError, /cannot make a shareable Proc because it can refer unshareable object \[\]/) do
+ obj.foo(*[])
end
end
@@ -297,6 +292,56 @@ class TestISeq < Test::Unit::TestCase
assert_raise(TypeError, bug11159) {compile(1)}
end
+ def test_invalid_source_no_memory_leak
+ # [Bug #21394]
+ assert_no_memory_leak(["-rtempfile"], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true)
+ code = proc do |t|
+ RubyVM::InstructionSequence.new(nil)
+ rescue TypeError
+ else
+ raise "TypeError was not raised during RubyVM::InstructionSequence.new"
+ end
+
+ 10.times(&code)
+ begin;
+ 1_000_000.times(&code)
+ end;
+
+ # [Bug #21394]
+ # RubyVM::InstructionSequence.new calls rb_io_path, which dups the string
+ # and can leak memory if the dup raises
+ assert_no_memory_leak(["-rtempfile"], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true)
+ MyError = Class.new(StandardError)
+ String.prepend(Module.new do
+ def initialize_dup(_)
+ if $raise_on_dup
+ raise MyError
+ else
+ super
+ end
+ end
+ end)
+
+ code = proc do |t|
+ Tempfile.create do |f|
+ $raise_on_dup = true
+ t.times do
+ RubyVM::InstructionSequence.new(f)
+ rescue MyError
+ else
+ raise "MyError was not raised during RubyVM::InstructionSequence.new"
+ end
+ ensure
+ $raise_on_dup = false
+ end
+ end
+
+ code.call(100)
+ begin;
+ code.call(1_000_000)
+ end;
+ end
+
def test_frozen_string_literal_compile_option
$f = 'f'
line = __LINE__ + 2
@@ -808,7 +853,7 @@ class TestISeq < Test::Unit::TestCase
GC.start
Float(30)
}
- assert_equal :new, r.take
+ assert_equal :new, r.value
RUBY
end
@@ -859,9 +904,28 @@ class TestISeq < Test::Unit::TestCase
end
end
+ def test_serialize_anonymous_outer_variables
+ iseq = RubyVM::InstructionSequence.compile(<<~'RUBY')
+ obj = Object.new
+ def obj.test
+ [1].each do
+ raise "Oops"
+ rescue
+ return it
+ end
+ end
+ obj
+ RUBY
+
+ binary = iseq.to_binary # [Bug # 21370]
+ roundtripped_iseq = RubyVM::InstructionSequence.load_from_binary(binary)
+ object = roundtripped_iseq.eval
+ assert_equal 1, object.test
+ end
+
def test_loading_kwargs_memory_leak
assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", rss: true)
- a = iseq_to_binary(RubyVM::InstructionSequence.compile("foo(bar: :baz)"))
+ a = RubyVM::InstructionSequence.compile("foo(bar: :baz)").to_binary
begin;
1_000_000.times do
RubyVM::InstructionSequence.load_from_binary(a)
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index 4563308fa2..c836abd0c6 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -2424,6 +2424,21 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_raise(ArgumentError) { m.call(42, a: 1, **h2) }
end
+ def test_ruby2_keywords_post_arg
+ def self.a(*c, **kw) [c, kw] end
+ def self.b(*a, b) a(*a, b) end
+ assert_warn(/Skipping set of ruby2_keywords flag for b \(method accepts keywords or post arguments or method does not accept argument splat\)/) do
+ assert_nil(singleton_class.send(:ruby2_keywords, :b))
+ end
+ assert_equal([[{foo: 1}, {bar: 1}], {}], b({foo: 1}, bar: 1))
+
+ b = ->(*a, b){a(*a, b)}
+ assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc accepts keywords or post arguments or proc does not accept argument splat\)/) do
+ b.ruby2_keywords
+ end
+ assert_equal([[{foo: 1}, {bar: 1}], {}], b.({foo: 1}, bar: 1))
+ end
+
def test_proc_ruby2_keywords
h1 = {:a=>1}
foo = ->(*args, &block){block.call(*args)}
@@ -2436,8 +2451,8 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_raise(ArgumentError) { foo.call(:a=>1, &->(arg, **kw){[arg, kw]}) }
assert_equal(h1, foo.call(:a=>1, &->(arg){arg}))
- [->(){}, ->(arg){}, ->(*args, **kw){}, ->(*args, k: 1){}, ->(*args, k: ){}].each do |pr|
- assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc accepts keywords or proc does not accept argument splat\)/) do
+ [->(){}, ->(arg){}, ->(*args, x){}, ->(*args, **kw){}, ->(*args, k: 1){}, ->(*args, k: ){}].each do |pr|
+ assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc accepts keywords or post arguments or proc does not accept argument splat\)/) do
pr.ruby2_keywords
end
end
@@ -2790,10 +2805,21 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal(:opt, o.clear_last_opt(a: 1))
assert_nothing_raised(ArgumentError) { o.clear_last_empty_method(a: 1) }
- assert_warn(/Skipping set of ruby2_keywords flag for bar \(method accepts keywords or method does not accept argument splat\)/) do
+ assert_warn(/Skipping set of ruby2_keywords flag for bar \(method accepts keywords or post arguments or method does not accept argument splat\)/) do
assert_nil(c.send(:ruby2_keywords, :bar))
end
+ c.class_eval do
+ def bar_post(*a, x) = nil
+ define_method(:bar_post_bmethod) { |*a, x| }
+ end
+ assert_warn(/Skipping set of ruby2_keywords flag for bar_post \(method accepts keywords or post arguments or method does not accept argument splat\)/) do
+ assert_nil(c.send(:ruby2_keywords, :bar_post))
+ end
+ assert_warn(/Skipping set of ruby2_keywords flag for bar_post_bmethod \(method accepts keywords or post arguments or method does not accept argument splat\)/) do
+ assert_nil(c.send(:ruby2_keywords, :bar_post_bmethod))
+ end
+
utf16_sym = "abcdef".encode("UTF-16LE").to_sym
c.send(:define_method, utf16_sym, c.instance_method(:itself))
assert_warn(/abcdef/) do
@@ -4033,7 +4059,7 @@ class TestKeywordArguments < Test::Unit::TestCase
tap { m }
GC.start
tap { m }
- }, bug8964
+ }, bug8964, timeout: 30
assert_normal_exit %q{
prc = Proc.new {|a: []|}
GC.stress = true
diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb
index 3cbb54306c..c1858a36dd 100644
--- a/test/ruby/test_lambda.rb
+++ b/test/ruby/test_lambda.rb
@@ -163,7 +163,7 @@ class TestLambdaParameters < Test::Unit::TestCase
end
def test_proc_inside_lambda_toplevel
- assert_separately [], <<~RUBY
+ assert_ruby_status [], <<~RUBY
lambda{
$g = proc{ return :pr }
}.call
@@ -276,7 +276,7 @@ class TestLambdaParameters < Test::Unit::TestCase
end
def test_do_lambda_source_location
- exp = [__LINE__ + 1, 12, __LINE__ + 5, 7]
+ exp = [__LINE__ + 1, 10, __LINE__ + 5, 7]
lmd = ->(x,
y,
z) do
@@ -288,7 +288,7 @@ class TestLambdaParameters < Test::Unit::TestCase
end
def test_brace_lambda_source_location
- exp = [__LINE__ + 1, 12, __LINE__ + 5, 5]
+ exp = [__LINE__ + 1, 10, __LINE__ + 5, 5]
lmd = ->(x,
y,
z) {
diff --git a/test/ruby/test_literal.rb b/test/ruby/test_literal.rb
index dbff3c4734..cff888d4b3 100644
--- a/test/ruby/test_literal.rb
+++ b/test/ruby/test_literal.rb
@@ -682,6 +682,11 @@ class TestRubyLiteral < Test::Unit::TestCase
$VERBOSE = verbose_bak
end
+ def test_rational_float
+ assert_equal(12, 0.12r * 100)
+ assert_equal(12, 0.1_2r * 100)
+ end
+
def test_symbol_list
assert_equal([:foo, :bar], %i[foo bar])
assert_equal([:"\"foo"], %i["foo])
diff --git a/test/ruby/test_m17n.rb b/test/ruby/test_m17n.rb
index b0e2e9f849..9f7a3c7f4b 100644
--- a/test/ruby/test_m17n.rb
+++ b/test/ruby/test_m17n.rb
@@ -186,33 +186,35 @@ class TestM17N < Test::Unit::TestCase
end
def test_string_inspect_encoding
- EnvUtil.suppress_warning do
- begin
- orig_int = Encoding.default_internal
- orig_ext = Encoding.default_external
- Encoding.default_internal = nil
- [Encoding::UTF_8, Encoding::EUC_JP, Encoding::Windows_31J, Encoding::GB18030].
- each do |e|
- Encoding.default_external = e
- str = "\x81\x30\x81\x30".force_encoding('GB18030')
- assert_equal(Encoding::GB18030 == e ? %{"#{str}"} : '"\x{81308130}"', str.inspect)
- str = e("\xa1\x8f\xa1\xa1")
- expected = "\"\\xA1\x8F\xA1\xA1\"".force_encoding("EUC-JP")
- assert_equal(Encoding::EUC_JP == e ? expected : "\"\\xA1\\x{8FA1A1}\"", str.inspect)
- str = s("\x81@")
- assert_equal(Encoding::Windows_31J == e ? %{"#{str}"} : '"\x{8140}"', str.inspect)
- str = "\u3042\u{10FFFD}"
- assert_equal(Encoding::UTF_8 == e ? %{"#{str}"} : '"\u3042\u{10FFFD}"', str.inspect)
- end
- Encoding.default_external = Encoding::UTF_8
- [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE,
- Encoding::UTF8_SOFTBANK].each do |e|
- str = "abc".encode(e)
- assert_equal('"abc"', str.inspect)
- end
- ensure
- Encoding.default_internal = orig_int
- Encoding.default_external = orig_ext
+ [
+ Encoding::UTF_8,
+ Encoding::EUC_JP,
+ Encoding::Windows_31J,
+ Encoding::GB18030,
+ ].each do |e|
+ EnvUtil.with_default_external(e) do
+ str = "\x81\x30\x81\x30".force_encoding('GB18030')
+ assert_equal(Encoding::GB18030 == e ? %{"#{str}"} : '"\x{81308130}"', str.inspect)
+ str = e("\xa1\x8f\xa1\xa1")
+ expected = "\"\\xA1\x8F\xA1\xA1\"".force_encoding("EUC-JP")
+ assert_equal(Encoding::EUC_JP == e ? expected : "\"\\xA1\\x{8FA1A1}\"", str.inspect)
+ str = s("\x81@")
+ assert_equal(Encoding::Windows_31J == e ? %{"#{str}"} : '"\x{8140}"', str.inspect)
+ str = "\u3042\u{10FFFD}"
+ assert_equal(Encoding::UTF_8 == e ? %{"#{str}"} : '"\u3042\u{10FFFD}"', str.inspect)
+ end
+ end
+
+ EnvUtil.with_default_external(Encoding::UTF_8) do
+ [
+ Encoding::UTF_16BE,
+ Encoding::UTF_16LE,
+ Encoding::UTF_32BE,
+ Encoding::UTF_32LE,
+ Encoding::UTF8_SOFTBANK
+ ].each do |e|
+ str = "abc".encode(e)
+ assert_equal('"abc"', str.inspect)
end
end
end
@@ -246,59 +248,43 @@ class TestM17N < Test::Unit::TestCase
end
def test_object_utf16_32_inspect
- EnvUtil.suppress_warning do
- begin
- orig_int = Encoding.default_internal
- orig_ext = Encoding.default_external
- Encoding.default_internal = nil
- Encoding.default_external = Encoding::UTF_8
- o = Object.new
- [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE].each do |e|
- o.instance_eval "undef inspect;def inspect;'abc'.encode('#{e}');end"
- assert_equal '[abc]', [o].inspect
- end
- ensure
- Encoding.default_internal = orig_int
- Encoding.default_external = orig_ext
+ EnvUtil.with_default_external(Encoding::UTF_8) do
+ o = Object.new
+ [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE].each do |e|
+ o.instance_eval "undef inspect;def inspect;'abc'.encode('#{e}');end"
+ assert_equal '[abc]', [o].inspect
end
end
end
def test_object_inspect_external
- orig_v, $VERBOSE = $VERBOSE, false
- orig_int, Encoding.default_internal = Encoding.default_internal, nil
- orig_ext = Encoding.default_external
-
omit "https://bugs.ruby-lang.org/issues/18338"
o = Object.new
- Encoding.default_external = Encoding::UTF_16BE
- def o.inspect
- "abc"
- end
- assert_nothing_raised(Encoding::CompatibilityError) { [o].inspect }
+ EnvUtil.with_default_external(Encoding::UTF_16BE) do
+ def o.inspect
+ "abc"
+ end
+ assert_nothing_raised(Encoding::CompatibilityError) { [o].inspect }
- def o.inspect
- "abc".encode(Encoding.default_external)
+ def o.inspect
+ "abc".encode(Encoding.default_external)
+ end
+ assert_equal '[abc]', [o].inspect
end
- assert_equal '[abc]', [o].inspect
-
- Encoding.default_external = Encoding::US_ASCII
- def o.inspect
- "\u3042"
- end
- assert_equal '[\u3042]', [o].inspect
+ EnvUtil.with_default_external(Encoding::US_ASCII) do
+ def o.inspect
+ "\u3042"
+ end
+ assert_equal '[\u3042]', [o].inspect
- def o.inspect
- "\x82\xa0".force_encoding(Encoding::Windows_31J)
+ def o.inspect
+ "\x82\xa0".force_encoding(Encoding::Windows_31J)
+ end
+ assert_equal '[\x{82A0}]', [o].inspect
end
- assert_equal '[\x{82A0}]', [o].inspect
- ensure
- Encoding.default_internal = orig_int
- Encoding.default_external = orig_ext
- $VERBOSE = orig_v
end
def test_str_dump
diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb
index 2aa2a38f80..eb66994801 100644
--- a/test/ruby/test_marshal.rb
+++ b/test/ruby/test_marshal.rb
@@ -268,7 +268,11 @@ class TestMarshal < Test::Unit::TestCase
classISO8859_1.name
ClassISO8859_1 = classISO8859_1
- def test_class_nonascii
+ moduleUTF8 = const_set("C\u{30af 30e9 30b9}", Module.new)
+ moduleUTF8.name
+ ModuleUTF8 = moduleUTF8
+
+ def test_nonascii_class_instance
a = ClassUTF8.new
assert_instance_of(ClassUTF8, Marshal.load(Marshal.dump(a)), '[ruby-core:24790]')
@@ -301,6 +305,12 @@ class TestMarshal < Test::Unit::TestCase
end
end
+ def test_nonascii_class_module
+ assert_same(ClassUTF8, Marshal.load(Marshal.dump(ClassUTF8)))
+ assert_same(ClassISO8859_1, Marshal.load(Marshal.dump(ClassISO8859_1)))
+ assert_same(ModuleUTF8, Marshal.load(Marshal.dump(ModuleUTF8)))
+ end
+
def test_regexp2
assert_equal(/\\u/, Marshal.load("\004\b/\b\\\\u\000"))
assert_equal(/u/, Marshal.load("\004\b/\a\\u\000"))
@@ -459,6 +469,30 @@ class TestMarshal < Test::Unit::TestCase
assert_equal(o1.foo, o2.foo)
end
+ class TooComplex
+ def initialize
+ @marshal_too_complex = 1
+ end
+ end
+
+ def test_complex_shape_object_id_not_dumped
+ if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
+ assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+ 8.times do |i|
+ TooComplex.new.instance_variable_set("@TestObjectIdTooComplex#{i}", 1)
+ end
+ obj = TooComplex.new
+ ivar = "@a#{rand(10_000).to_s.rjust(5, '0')}"
+ obj.instance_variable_set(ivar, 1)
+
+ if defined?(RubyVM::Shape)
+ assert_predicate(RubyVM::Shape.of(obj), :too_complex?)
+ end
+ obj.object_id
+ assert_equal "\x04\bo:\x1CTestMarshal::TooComplex\a:\x19@marshal_too_complexi\x06:\f#{ivar}i\x06".b, Marshal.dump(obj)
+ end
+
def test_marshal_complex
assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\x05")}
assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\x06i\x00")}
diff --git a/test/ruby/test_math.rb b/test/ruby/test_math.rb
index 6e67099c6b..e134600cc4 100644
--- a/test/ruby/test_math.rb
+++ b/test/ruby/test_math.rb
@@ -147,6 +147,13 @@ class TestMath < Test::Unit::TestCase
check(Math::E ** 2, Math.exp(2))
end
+ def test_expm1
+ check(0, Math.expm1(0))
+ check(Math.sqrt(Math::E) - 1, Math.expm1(0.5))
+ check(Math::E - 1, Math.expm1(1))
+ check(Math::E ** 2 - 1, Math.expm1(2))
+ end
+
def test_log
check(0, Math.log(1))
check(1, Math.log(Math::E))
@@ -201,6 +208,19 @@ class TestMath < Test::Unit::TestCase
assert_nothing_raised { assert_infinity(-Math.log10(0)) }
end
+ def test_log1p
+ check(0, Math.log1p(0))
+ check(1, Math.log1p(Math::E - 1))
+ check(Math.log(2.0 ** 64 + 1), Math.log1p(1 << 64))
+ check(Math.log(2) * 1024.0, Math.log1p(2 ** 1024))
+ assert_nothing_raised { assert_infinity(Math.log1p(1.0/0)) }
+ assert_nothing_raised { assert_infinity(-Math.log1p(-1.0)) }
+ assert_raise_with_message(Math::DomainError, /\blog1p\b/) { Math.log1p(-1.1) }
+ assert_raise_with_message(Math::DomainError, /\blog1p\b/) { Math.log1p(-Float::EPSILON-1) }
+ assert_nothing_raised { assert_nan(Math.log1p(Float::NAN)) }
+ assert_nothing_raised { assert_infinity(-Math.log1p(-1)) }
+ end
+
def test_sqrt
check(0, Math.sqrt(0))
check(1, Math.sqrt(1))
@@ -301,11 +321,21 @@ class TestMath < Test::Unit::TestCase
assert_float_and_int([Math.log(6), 1], Math.lgamma(4))
assert_raise_with_message(Math::DomainError, /\blgamma\b/) { Math.lgamma(-Float::INFINITY) }
+
+ x, sign = Math.lgamma(+0.0)
+ mesg = "Math.lgamma(+0.0) should be [INF, +1]"
+ assert_infinity(x, mesg)
+ assert_equal(+1, sign, mesg)
+
x, sign = Math.lgamma(-0.0)
mesg = "Math.lgamma(-0.0) should be [INF, -1]"
assert_infinity(x, mesg)
assert_equal(-1, sign, mesg)
- x, sign = Math.lgamma(Float::NAN)
+
+ x, = Math.lgamma(-1)
+ assert_infinity(x, "Math.lgamma(-1) should be +INF")
+
+ x, = Math.lgamma(Float::NAN)
assert_nan(x)
end
diff --git a/test/ruby/test_memory_view.rb b/test/ruby/test_memory_view.rb
index 5a39084d18..d0122ddd59 100644
--- a/test/ruby/test_memory_view.rb
+++ b/test/ruby/test_memory_view.rb
@@ -335,7 +335,7 @@ class TestMemoryView < Test::Unit::TestCase
p mv[[0, 2]]
mv[[1, 3]]
end
- p r.take
+ p r.value
end;
end
end
diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb
index a865f6100b..8561f841a8 100644
--- a/test/ruby/test_method.rb
+++ b/test/ruby/test_method.rb
@@ -284,8 +284,10 @@ class TestMethod < Test::Unit::TestCase
assert_raise(TypeError) { m.bind(Object.new) }
cx = EnvUtil.labeled_class("X\u{1f431}")
- assert_raise_with_message(TypeError, /X\u{1f431}/) do
- o.method(cx)
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /X\u{1f431}/) do
+ o.method(cx)
+ end
end
end
@@ -315,9 +317,12 @@ class TestMethod < Test::Unit::TestCase
assert_raise(TypeError) do
Class.new.class_eval { define_method(:bar, o.method(:bar)) }
end
+
cx = EnvUtil.labeled_class("X\u{1f431}")
- assert_raise_with_message(TypeError, /X\u{1F431}/) do
- Class.new {define_method(cx) {}}
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /X\u{1F431}/) do
+ Class.new {define_method(cx) {}}
+ end
end
end
@@ -1612,7 +1617,7 @@ class TestMethod < Test::Unit::TestCase
begin
foo(1)
rescue ArgumentError => e
- assert_equal "main.rb:#{$line_method}:in 'foo'", e.backtrace.first
+ assert_equal "main.rb:#{$line_method}:in 'Object#foo'", e.backtrace.first
end
EOS
END_OF_BODY
diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb
index 9a21113fe0..9ed6c1e321 100644
--- a/test/ruby/test_module.rb
+++ b/test/ruby/test_module.rb
@@ -9,18 +9,18 @@ class TestModule < Test::Unit::TestCase
yield
end
- def assert_method_defined?(klass, mid, message="")
+ def assert_method_defined?(klass, (mid, *args), message="")
message = build_message(message, "#{klass}\##{mid} expected to be defined.")
_wrap_assertion do
- klass.method_defined?(mid) or
+ klass.method_defined?(mid, *args) or
raise Test::Unit::AssertionFailedError, message, caller(3)
end
end
- def assert_method_not_defined?(klass, mid, message="")
+ def assert_method_not_defined?(klass, (mid, *args), message="")
message = build_message(message, "#{klass}\##{mid} expected to not be defined.")
_wrap_assertion do
- klass.method_defined?(mid) and
+ klass.method_defined?(mid, *args) and
raise Test::Unit::AssertionFailedError, message, caller(3)
end
end
@@ -412,19 +412,6 @@ class TestModule < Test::Unit::TestCase
assert_equal([:MIXIN, :USER], User.constants.sort)
end
- def test_initialize_copy
- mod = Module.new { define_method(:foo) {:first} }
- klass = Class.new { include mod }
- instance = klass.new
- assert_equal(:first, instance.foo)
- new_mod = Module.new { define_method(:foo) { :second } }
- assert_raise(TypeError) do
- mod.send(:initialize_copy, new_mod)
- end
- 4.times { GC.start }
- assert_equal(:first, instance.foo) # [BUG] unreachable
- end
-
def test_initialize_copy_empty
m = Module.new do
def x
@@ -435,11 +422,6 @@ class TestModule < Test::Unit::TestCase
assert_equal([:x], m.instance_methods)
assert_equal([:@x], m.instance_variables)
assert_equal([:X], m.constants)
- assert_raise(TypeError) do
- m.module_eval do
- initialize_copy(Module.new)
- end
- end
m = Class.new(Module) do
def initialize_copy(other)
@@ -601,7 +583,7 @@ class TestModule < Test::Unit::TestCase
end
def test_gc_prepend_chain
- assert_separately([], <<-EOS)
+ assert_ruby_status([], <<-EOS)
10000.times { |i|
m1 = Module.new do
def foo; end
@@ -831,40 +813,40 @@ class TestModule < Test::Unit::TestCase
def test_method_defined?
[User, Class.new{include User}, Class.new{prepend User}].each do |klass|
[[], [true]].each do |args|
- assert !klass.method_defined?(:wombat, *args)
- assert klass.method_defined?(:mixin, *args)
- assert klass.method_defined?(:user, *args)
- assert klass.method_defined?(:user2, *args)
- assert !klass.method_defined?(:user3, *args)
+ assert_method_not_defined?(klass, [:wombat, *args])
+ assert_method_defined?(klass, [:mixin, *args])
+ assert_method_defined?(klass, [:user, *args])
+ assert_method_defined?(klass, [:user2, *args])
+ assert_method_not_defined?(klass, [:user3, *args])
- assert !klass.method_defined?("wombat", *args)
- assert klass.method_defined?("mixin", *args)
- assert klass.method_defined?("user", *args)
- assert klass.method_defined?("user2", *args)
- assert !klass.method_defined?("user3", *args)
+ assert_method_not_defined?(klass, ["wombat", *args])
+ assert_method_defined?(klass, ["mixin", *args])
+ assert_method_defined?(klass, ["user", *args])
+ assert_method_defined?(klass, ["user2", *args])
+ assert_method_not_defined?(klass, ["user3", *args])
end
end
end
def test_method_defined_without_include_super
- assert User.method_defined?(:user, false)
- assert !User.method_defined?(:mixin, false)
- assert Mixin.method_defined?(:mixin, false)
+ assert_method_defined?(User, [:user, false])
+ assert_method_not_defined?(User, [:mixin, false])
+ assert_method_defined?(Mixin, [:mixin, false])
User.const_set(:FOO, c = Class.new)
c.prepend(User)
- assert !c.method_defined?(:user, false)
+ assert_method_not_defined?(c, [:user, false])
c.define_method(:user){}
- assert c.method_defined?(:user, false)
+ assert_method_defined?(c, [:user, false])
- assert !c.method_defined?(:mixin, false)
+ assert_method_not_defined?(c, [:mixin, false])
c.define_method(:mixin){}
- assert c.method_defined?(:mixin, false)
+ assert_method_defined?(c, [:mixin, false])
- assert !c.method_defined?(:userx, false)
+ assert_method_not_defined?(c, [:userx, false])
c.define_method(:userx){}
- assert c.method_defined?(:userx, false)
+ assert_method_defined?(c, [:userx, false])
# cleanup
User.class_eval do
@@ -1291,8 +1273,11 @@ class TestModule < Test::Unit::TestCase
assert_raise(NameError) { c1.const_set("X\u{3042}".encode("utf-16le"), :foo) }
assert_raise(NameError) { c1.const_set("X\u{3042}".encode("utf-32be"), :foo) }
assert_raise(NameError) { c1.const_set("X\u{3042}".encode("utf-32le"), :foo) }
+
cx = EnvUtil.labeled_class("X\u{3042}")
- assert_raise_with_message(TypeError, /X\u{3042}/) { c1.const_set(cx, :foo) }
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /X\u{3042}/) { c1.const_set(cx, :foo) }
+ end
end
def test_const_get_invalid_name
@@ -1449,6 +1434,7 @@ class TestModule < Test::Unit::TestCase
c.instance_eval { attr_reader :"." }
end
+ c = Class.new
assert_equal([:a], c.class_eval { attr :a })
assert_equal([:b, :c], c.class_eval { attr :b, :c })
assert_equal([:d], c.class_eval { attr_reader :d })
@@ -1457,6 +1443,16 @@ class TestModule < Test::Unit::TestCase
assert_equal([:h=, :i=], c.class_eval { attr_writer :h, :i })
assert_equal([:j, :j=], c.class_eval { attr_accessor :j })
assert_equal([:k, :k=, :l, :l=], c.class_eval { attr_accessor :k, :l })
+
+ c = Class.new
+ assert_equal([:a], c.class_eval { attr "a" })
+ assert_equal([:b, :c], c.class_eval { attr "b", "c" })
+ assert_equal([:d], c.class_eval { attr_reader "d" })
+ assert_equal([:e, :f], c.class_eval { attr_reader "e", "f" })
+ assert_equal([:g=], c.class_eval { attr_writer "g" })
+ assert_equal([:h=, :i=], c.class_eval { attr_writer "h", "i" })
+ assert_equal([:j, :j=], c.class_eval { attr_accessor "j" })
+ assert_equal([:k, :k=, :l, :l=], c.class_eval { attr_accessor "k", "l" })
end
def test_alias_method
@@ -3020,17 +3016,17 @@ class TestModule < Test::Unit::TestCase
bug11532 = '[ruby-core:70828] [Bug #11532]'
c = Class.new {const_set(:A, 1)}.freeze
- assert_raise_with_message(FrozenError, /frozen class/, bug11532) {
+ assert_raise_with_message(FrozenError, /frozen Class/, bug11532) {
c.class_eval {private_constant :A}
}
c = Class.new {const_set(:A, 1); private_constant :A}.freeze
- assert_raise_with_message(FrozenError, /frozen class/, bug11532) {
+ assert_raise_with_message(FrozenError, /frozen Class/, bug11532) {
c.class_eval {public_constant :A}
}
c = Class.new {const_set(:A, 1)}.freeze
- assert_raise_with_message(FrozenError, /frozen class/, bug11532) {
+ assert_raise_with_message(FrozenError, /frozen Class/, bug11532) {
c.class_eval {deprecate_constant :A}
}
end
@@ -3077,7 +3073,7 @@ class TestModule < Test::Unit::TestCase
end
def test_prepend_gc
- assert_separately [], %{
+ assert_ruby_status [], %{
module Foo
end
class Object
@@ -3371,11 +3367,11 @@ class TestModule < Test::Unit::TestCase
m.const_set(:N, Module.new)
assert_match(/\A#<Module:0x\h+>::N\z/, m::N.name)
- m::N.set_temporary_name(name = "fake_name_under_M")
+ assert_same m::N, m::N.set_temporary_name(name = "fake_name_under_M")
name.upcase!
assert_equal("fake_name_under_M", m::N.name)
assert_raise(FrozenError) {m::N.name.upcase!}
- m::N.set_temporary_name(nil)
+ assert_same m::N, m::N.set_temporary_name(nil)
assert_nil(m::N.name)
m::N.const_set(:O, Module.new)
@@ -3383,14 +3379,14 @@ class TestModule < Test::Unit::TestCase
m::N.const_set(:Recursive, m)
m.const_set(:A, 42)
- m.set_temporary_name(name = "fake_name")
+ assert_same m, m.set_temporary_name(name = "fake_name")
name.upcase!
assert_equal("fake_name", m.name)
assert_raise(FrozenError) {m.name.upcase!}
assert_equal("fake_name::N", m::N.name)
assert_equal("fake_name::N::O", m::N::O.name)
- m.set_temporary_name(nil)
+ assert_same m, m.set_temporary_name(nil)
assert_nil m.name
assert_nil m::N.name
assert_nil m::N::O.name
diff --git a/test/ruby/test_nomethod_error.rb b/test/ruby/test_nomethod_error.rb
index 6d413e6391..6abd20cc81 100644
--- a/test/ruby/test_nomethod_error.rb
+++ b/test/ruby/test_nomethod_error.rb
@@ -78,7 +78,7 @@ class TestNoMethodError < Test::Unit::TestCase
assert_equal :foo, error.name
assert_equal [1, 2], error.args
assert_equal receiver, error.receiver
- assert error.private_call?, "private_call? was false."
+ assert_predicate error, :private_call?
end
def test_message_encoding
@@ -106,4 +106,32 @@ class TestNoMethodError < Test::Unit::TestCase
assert_match(/undefined method.+this_method_does_not_exist.+for.+Module/, err.to_s)
end
+
+ def test_send_forward_raises
+ t = EnvUtil.labeled_class("Test") do
+ def foo(...)
+ forward(...)
+ end
+ end
+ obj = t.new
+ assert_raise(NoMethodError) do
+ obj.foo
+ end
+ end
+
+ # [Bug #21535]
+ def test_send_forward_raises_when_called_through_vcall
+ t = EnvUtil.labeled_class("Test") do
+ def foo(...)
+ forward(...)
+ end
+ def foo_indirect
+ foo # vcall
+ end
+ end
+ obj = t.new
+ assert_raise(NoMethodError) do
+ obj.foo_indirect
+ end
+ end
end
diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb
index ab492743f6..35496ac875 100644
--- a/test/ruby/test_numeric.rb
+++ b/test/ruby/test_numeric.rb
@@ -18,18 +18,24 @@ class TestNumeric < Test::Unit::TestCase
assert_raise_with_message(TypeError, /can't be coerced into /) {1|:foo}
assert_raise_with_message(TypeError, /can't be coerced into /) {1^:foo}
- assert_raise_with_message(TypeError, /:\u{3042}/) {1+:"\u{3042}"}
- assert_raise_with_message(TypeError, /:\u{3042}/) {1&:"\u{3042}"}
- assert_raise_with_message(TypeError, /:\u{3042}/) {1|:"\u{3042}"}
- assert_raise_with_message(TypeError, /:\u{3042}/) {1^:"\u{3042}"}
- assert_raise_with_message(TypeError, /:"\\u3042"/) {1+:"\u{3042}"}
- assert_raise_with_message(TypeError, /:"\\u3042"/) {1&:"\u{3042}"}
- assert_raise_with_message(TypeError, /:"\\u3042"/) {1|:"\u{3042}"}
- assert_raise_with_message(TypeError, /:"\\u3042"/) {1^:"\u{3042}"}
- assert_raise_with_message(TypeError, /:\u{3044}/) {1+"\u{3044}".to_sym}
- assert_raise_with_message(TypeError, /:\u{3044}/) {1&"\u{3044}".to_sym}
- assert_raise_with_message(TypeError, /:\u{3044}/) {1|"\u{3044}".to_sym}
- assert_raise_with_message(TypeError, /:\u{3044}/) {1^"\u{3044}".to_sym}
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /:\u{3042}/) {1+:"\u{3042}"}
+ assert_raise_with_message(TypeError, /:\u{3042}/) {1&:"\u{3042}"}
+ assert_raise_with_message(TypeError, /:\u{3042}/) {1|:"\u{3042}"}
+ assert_raise_with_message(TypeError, /:\u{3042}/) {1^:"\u{3042}"}
+
+ assert_raise_with_message(TypeError, /:\u{3044}/) {1+"\u{3044}".to_sym}
+ assert_raise_with_message(TypeError, /:\u{3044}/) {1&"\u{3044}".to_sym}
+ assert_raise_with_message(TypeError, /:\u{3044}/) {1|"\u{3044}".to_sym}
+ assert_raise_with_message(TypeError, /:\u{3044}/) {1^"\u{3044}".to_sym}
+ end
+
+ EnvUtil.with_default_internal(Encoding::US_ASCII) do
+ assert_raise_with_message(TypeError, /:"\\u3042"/) {1+:"\u{3042}"}
+ assert_raise_with_message(TypeError, /:"\\u3042"/) {1&:"\u{3042}"}
+ assert_raise_with_message(TypeError, /:"\\u3042"/) {1|:"\u{3042}"}
+ assert_raise_with_message(TypeError, /:"\\u3042"/) {1^:"\u{3042}"}
+ end
bug10711 = '[ruby-core:67405] [Bug #10711]'
exp = "1.2 can't be coerced into Integer"
@@ -483,6 +489,10 @@ class TestNumeric < Test::Unit::TestCase
assert_equal(0, 0.pow(3, 1))
assert_equal(0, 2.pow(3, 1))
assert_equal(0, -2.pow(3, 1))
+
+ min, max = RbConfig::LIMITS.values_at("FIXNUM_MIN", "FIXNUM_MAX")
+ assert_equal(0, 0.pow(2, min))
+ assert_equal(0, Integer.sqrt(max+1).pow(2, min))
end
end
diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb
index 7d00422629..f4dfe2251b 100644
--- a/test/ruby/test_object.rb
+++ b/test/ruby/test_object.rb
@@ -280,6 +280,12 @@ class TestObject < Test::Unit::TestCase
assert_equal([:foo], k.private_methods(false))
end
+ class ToStrCounter
+ def initialize(str = "@foo") @str = str; @count = 0; end
+ def to_str; @count += 1; @str; end
+ def count; @count; end
+ end
+
def test_instance_variable_get
o = Object.new
o.instance_eval { @foo = :foo }
@@ -291,9 +297,7 @@ class TestObject < Test::Unit::TestCase
assert_raise(NameError) { o.instance_variable_get("bar") }
assert_raise(TypeError) { o.instance_variable_get(1) }
- n = Object.new
- def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end
- def n.count; @count; end
+ n = ToStrCounter.new
assert_equal(:foo, o.instance_variable_get(n))
assert_equal(1, n.count)
end
@@ -308,9 +312,7 @@ class TestObject < Test::Unit::TestCase
assert_raise(NameError) { o.instance_variable_set("bar", 1) }
assert_raise(TypeError) { o.instance_variable_set(1, 1) }
- n = Object.new
- def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end
- def n.count; @count; end
+ n = ToStrCounter.new
o.instance_variable_set(n, :bar)
assert_equal(:bar, o.instance_eval { @foo })
assert_equal(1, n.count)
@@ -327,9 +329,7 @@ class TestObject < Test::Unit::TestCase
assert_raise(NameError) { o.instance_variable_defined?("bar") }
assert_raise(TypeError) { o.instance_variable_defined?(1) }
- n = Object.new
- def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end
- def n.count; @count; end
+ n = ToStrCounter.new
assert_equal(true, o.instance_variable_defined?(n))
assert_equal(1, n.count)
end
@@ -356,38 +356,41 @@ class TestObject < Test::Unit::TestCase
end
def test_remove_instance_variable_re_embed
- require "objspace"
-
- c = Class.new do
- def a = @a
-
- def b = @b
-
- def c = @c
- end
-
- o1 = c.new
- o2 = c.new
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ c = Class.new do
+ attr_reader :a, :b, :c
- o1.instance_variable_set(:@foo, 5)
- o1.instance_variable_set(:@a, 0)
- o1.instance_variable_set(:@b, 1)
- o1.instance_variable_set(:@c, 2)
- refute_includes ObjectSpace.dump(o1), '"embedded":true'
- o1.remove_instance_variable(:@foo)
- assert_includes ObjectSpace.dump(o1), '"embedded":true'
-
- o2.instance_variable_set(:@a, 0)
- o2.instance_variable_set(:@b, 1)
- o2.instance_variable_set(:@c, 2)
- assert_includes ObjectSpace.dump(o2), '"embedded":true'
+ def initialize
+ @a = nil
+ @b = nil
+ @c = nil
+ end
+ end
- assert_equal(0, o1.a)
- assert_equal(1, o1.b)
- assert_equal(2, o1.c)
- assert_equal(0, o2.a)
- assert_equal(1, o2.b)
- assert_equal(2, o2.c)
+ o1 = c.new
+ o2 = c.new
+
+ o1.instance_variable_set(:@foo, 5)
+ o1.instance_variable_set(:@a, 0)
+ o1.instance_variable_set(:@b, 1)
+ o1.instance_variable_set(:@c, 2)
+ refute_includes ObjectSpace.dump(o1), '"embedded":true'
+ o1.remove_instance_variable(:@foo)
+ assert_includes ObjectSpace.dump(o1), '"embedded":true'
+
+ o2.instance_variable_set(:@a, 0)
+ o2.instance_variable_set(:@b, 1)
+ o2.instance_variable_set(:@c, 2)
+ assert_includes ObjectSpace.dump(o2), '"embedded":true'
+
+ assert_equal(0, o1.a)
+ assert_equal(1, o1.b)
+ assert_equal(2, o1.c)
+ assert_equal(0, o2.a)
+ assert_equal(1, o2.b)
+ assert_equal(2, o2.c)
+ end;
end
def test_convert_string
@@ -950,6 +953,19 @@ class TestObject < Test::Unit::TestCase
assert_match(/\bInspect\u{3042}:.* @\u{3044}=42\b/, x.inspect)
x.instance_variable_set("@\u{3046}".encode(Encoding::EUC_JP), 6)
assert_match(/@\u{3046}=6\b/, x.inspect)
+
+ x = Object.new
+ x.singleton_class.class_eval do
+ private def instance_variables_to_inspect = [:@host, :@user]
+ end
+
+ x.instance_variable_set(:@host, "localhost")
+ x.instance_variable_set(:@user, "root")
+ x.instance_variable_set(:@password, "hunter2")
+ s = x.inspect
+ assert_include(s, "@host=\"localhost\"")
+ assert_include(s, "@user=\"root\"")
+ assert_not_include(s, "@password=")
end
def test_singleton_methods
diff --git a/test/ruby/test_object_id.rb b/test/ruby/test_object_id.rb
new file mode 100644
index 0000000000..adb819febc
--- /dev/null
+++ b/test/ruby/test_object_id.rb
@@ -0,0 +1,303 @@
+require 'test/unit'
+require "securerandom"
+
+class TestObjectId < Test::Unit::TestCase
+ def setup
+ @obj = Object.new
+ end
+
+ def test_dup_new_id
+ id = @obj.object_id
+ refute_equal id, @obj.dup.object_id
+ end
+
+ def test_dup_with_ivar_and_id
+ id = @obj.object_id
+ @obj.instance_variable_set(:@foo, 42)
+
+ copy = @obj.dup
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ end
+
+ def test_dup_with_id_and_ivar
+ @obj.instance_variable_set(:@foo, 42)
+ id = @obj.object_id
+
+ copy = @obj.dup
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ end
+
+ def test_dup_with_id_and_ivar_and_frozen
+ @obj.instance_variable_set(:@foo, 42)
+ @obj.freeze
+ id = @obj.object_id
+
+ copy = @obj.dup
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ refute_predicate copy, :frozen?
+ end
+
+ def test_clone_new_id
+ id = @obj.object_id
+ refute_equal id, @obj.clone.object_id
+ end
+
+ def test_clone_with_ivar_and_id
+ id = @obj.object_id
+ @obj.instance_variable_set(:@foo, 42)
+
+ copy = @obj.clone
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ end
+
+ def test_clone_with_id_and_ivar
+ @obj.instance_variable_set(:@foo, 42)
+ id = @obj.object_id
+
+ copy = @obj.clone
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ end
+
+ def test_clone_with_id_and_ivar_and_frozen
+ @obj.instance_variable_set(:@foo, 42)
+ @obj.freeze
+ id = @obj.object_id
+
+ copy = @obj.clone
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ assert_predicate copy, :frozen?
+ end
+
+ def test_marshal_new_id
+ return pass if @obj.is_a?(Module)
+
+ id = @obj.object_id
+ refute_equal id, Marshal.load(Marshal.dump(@obj)).object_id
+ end
+
+ def test_marshal_with_ivar_and_id
+ return pass if @obj.is_a?(Module)
+
+ id = @obj.object_id
+ @obj.instance_variable_set(:@foo, 42)
+
+ copy = Marshal.load(Marshal.dump(@obj))
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ end
+
+ def test_marshal_with_id_and_ivar
+ return pass if @obj.is_a?(Module)
+
+ @obj.instance_variable_set(:@foo, 42)
+ id = @obj.object_id
+
+ copy = Marshal.load(Marshal.dump(@obj))
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ end
+
+ def test_marshal_with_id_and_ivar_and_frozen
+ return pass if @obj.is_a?(Module)
+
+ @obj.instance_variable_set(:@foo, 42)
+ @obj.freeze
+ id = @obj.object_id
+
+ copy = Marshal.load(Marshal.dump(@obj))
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ refute_predicate copy, :frozen?
+ end
+
+ def test_object_id_need_resize
+ (3 - @obj.instance_variables.size).times do |i|
+ @obj.instance_variable_set("@a_#{i}", "[Bug #21445]")
+ end
+ @obj.object_id
+ GC.start
+ end
+end
+
+class TestObjectIdClass < TestObjectId
+ def setup
+ @obj = Class.new
+ end
+end
+
+class TestObjectIdGeneric < TestObjectId
+ def setup
+ @obj = Array.new
+ end
+end
+
+class TestObjectIdTooComplex < TestObjectId
+ class TooComplex
+ def initialize
+ @too_complex_obj_id_test = 1
+ end
+ end
+
+ def setup
+ if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
+ assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+ 8.times do |i|
+ TooComplex.new.instance_variable_set("@TestObjectIdTooComplex#{i}", 1)
+ end
+ @obj = TooComplex.new
+ @obj.instance_variable_set("@a#{rand(10_000)}", 1)
+
+ if defined?(RubyVM::Shape)
+ assert_predicate(RubyVM::Shape.of(@obj), :too_complex?)
+ end
+ end
+end
+
+class TestObjectIdTooComplexClass < TestObjectId
+ class TooComplex < Module
+ end
+
+ def setup
+ if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
+ assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+
+ @obj = TooComplex.new
+
+ @obj.instance_variable_set("@___#{SecureRandom.hex}", 1)
+
+ 8.times do |i|
+ @obj.instance_variable_set("@TestObjectIdTooComplexClass#{i}", 1)
+ @obj.remove_instance_variable("@TestObjectIdTooComplexClass#{i}")
+ end
+
+ @obj.instance_variable_set("@test", 1)
+
+ if defined?(RubyVM::Shape)
+ assert_predicate(RubyVM::Shape.of(@obj), :too_complex?)
+ end
+ end
+end
+
+class TestObjectIdTooComplexGeneric < TestObjectId
+ class TooComplex < Array
+ end
+
+ def setup
+ if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
+ assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+ 8.times do |i|
+ TooComplex.new.instance_variable_set("@TestObjectIdTooComplexGeneric#{i}", 1)
+ end
+ @obj = TooComplex.new
+ @obj.instance_variable_set("@a#{rand(10_000)}", 1)
+ @obj.instance_variable_set("@a#{rand(10_000)}", 1)
+
+ if defined?(RubyVM::Shape)
+ assert_predicate(RubyVM::Shape.of(@obj), :too_complex?)
+ end
+ end
+end
+
+class TestObjectIdRactor < Test::Unit::TestCase
+ def test_object_id_race_free
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ Warning[:experimental] = false
+ class MyClass
+ attr_reader :a, :b, :c
+ def initialize
+ @a = @b = @c = nil
+ end
+ end
+ N = 10_000
+ objs = Ractor.make_shareable(N.times.map { MyClass.new })
+ results = 4.times.map{
+ Ractor.new(objs) { |objs|
+ vars = []
+ ids = []
+ objs.each do |obj|
+ vars << obj.a << obj.b << obj.c
+ ids << obj.object_id
+ end
+ [vars, ids]
+ }
+ }.map(&:value)
+ assert_equal 1, results.uniq.size
+ end;
+ end
+
+ def test_external_object_id_ractor_move
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ Warning[:experimental] = false
+ class MyClass
+ attr_reader :a, :b, :c
+ def initialize
+ @a = @b = @c = nil
+ end
+ end
+ obj = Ractor.make_shareable(MyClass.new)
+ object_id = obj.object_id
+ obj = Ractor.new { Ractor.receive }.send(obj, move: true).value
+ assert_equal object_id, obj.object_id
+ end;
+ end
+end
+
+class TestObjectIdStruct < TestObjectId
+ EmbeddedStruct = Struct.new(:embedded_field)
+
+ def setup
+ @obj = EmbeddedStruct.new
+ end
+end
+
+class TestObjectIdStructGenIvar < TestObjectId
+ GenIvarStruct = Struct.new(:a, :b, :c)
+
+ def setup
+ @obj = GenIvarStruct.new
+ end
+end
+
+class TestObjectIdStructNotEmbed < TestObjectId
+ MANY_IVS = 80
+
+ StructNotEmbed = Struct.new(*MANY_IVS.times.map { |i| :"field_#{i}" })
+
+ def setup
+ @obj = StructNotEmbed.new
+ end
+end
+
+class TestObjectIdStructTooComplex < TestObjectId
+ StructTooComplex = Struct.new(:a) do
+ def initialize
+ @too_complex_obj_id_test = 1
+ end
+ end
+
+ def setup
+ if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
+ assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+ 8.times do |i|
+ StructTooComplex.new.instance_variable_set("@TestObjectIdStructTooComplex#{i}", 1)
+ end
+ @obj = StructTooComplex.new
+ @obj.instance_variable_set("@a#{rand(10_000)}", 1)
+
+ if defined?(RubyVM::Shape)
+ assert_predicate(RubyVM::Shape.of(@obj), :too_complex?)
+ end
+ end
+end
diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb
index d161d8079a..a479547599 100644
--- a/test/ruby/test_objectspace.rb
+++ b/test/ruby/test_objectspace.rb
@@ -8,7 +8,7 @@ class TestObjectSpace < Test::Unit::TestCase
line = $1.to_i
code = <<"End"
define_method("test_id2ref_#{line}") {\
- o = ObjectSpace._id2ref(obj.object_id);\
+ o = EnvUtil.suppress_warning { ObjectSpace._id2ref(obj.object_id) }
assert_same(obj, o, "didn't round trip: \#{obj.inspect}");\
}
End
@@ -57,12 +57,12 @@ End
def test_id2ref_invalid_argument
msg = /no implicit conversion/
- assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(nil)}
- assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(false)}
- assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(true)}
- assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(:a)}
- assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref("0")}
- assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(Object.new)}
+ assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(nil) } }
+ assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(false) } }
+ assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(true) } }
+ assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(:a) } }
+ assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref("0") } }
+ assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(Object.new) } }
end
def test_id2ref_invalid_symbol_id
@@ -70,7 +70,7 @@ End
# 8 bits of the object is equal to RUBY_SYMBOL_FLAG, so we need to make
# sure that the bottom 8 bits remain unchanged.
msg = /is not a symbol id value/
- assert_raise_with_message(RangeError, msg) { ObjectSpace._id2ref(:a.object_id + 256) }
+ assert_raise_with_message(RangeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(:a.object_id + 256) } }
end
def test_count_objects
@@ -94,7 +94,7 @@ End
end
def test_finalizer
- assert_in_out_err(["-e", <<-END], "", %w(:ok :ok :ok :ok), [])
+ assert_in_out_err(["-e", <<-END], "", %w(:ok :ok :ok), [])
a = []
ObjectSpace.define_finalizer(a) { p :ok }
b = a.dup
@@ -137,6 +137,25 @@ End
}
end
+ def test_finalizer_copy
+ assert_in_out_err(["-e", <<~'RUBY'], "", %w(:ok), [])
+ def fin
+ ids = Set.new
+ ->(id) { puts "object_id (#{id}) reused" unless ids.add?(id) }
+ end
+
+ OBJ = Object.new
+ ObjectSpace.define_finalizer(OBJ, fin)
+ OBJ.freeze
+
+ 10.times do
+ OBJ.clone
+ end
+
+ p :ok
+ RUBY
+ end
+
def test_finalizer_with_super
assert_in_out_err(["-e", <<-END], "", %w(:ok), [])
class A
@@ -265,6 +284,21 @@ End
end;
end
+ def test_id2ref_table_build
+ assert_separately([], <<-End)
+ 10.times do
+ Object.new.object_id
+ end
+
+ GC.start(immediate_mark: false)
+
+ obj = Object.new
+ EnvUtil.suppress_warning do
+ assert_equal obj, ObjectSpace._id2ref(obj.object_id)
+ end
+ End
+ end
+
def test_each_object_singleton_class
assert_separately([], <<-End)
class C
diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb
index e39eafa5e5..5d16984eef 100644
--- a/test/ruby/test_optimization.rb
+++ b/test/ruby/test_optimization.rb
@@ -606,11 +606,11 @@ class TestRubyOptimization < Test::Unit::TestCase
end
class Bug10557
- def [](_)
+ def [](_, &)
block_given?
end
- def []=(_, _)
+ def []=(_, _, &)
block_given?
end
end
@@ -946,14 +946,14 @@ class TestRubyOptimization < Test::Unit::TestCase
end
def test_peephole_optimization_without_trace
- assert_separately [], <<-END
+ assert_ruby_status [], <<-END
RubyVM::InstructionSequence.compile_option = {trace_instruction: false}
eval "def foo; 1.times{|(a), &b| nil && a}; end"
END
end
def test_clear_unreachable_keyword_args
- assert_separately [], <<-END, timeout: 60
+ assert_ruby_status [], <<-END, timeout: 60
script = <<-EOS
if true
else
@@ -1080,7 +1080,7 @@ class TestRubyOptimization < Test::Unit::TestCase
class Objtostring
end
- def test_objtostring
+ def test_objtostring_immediate
assert_raise(NoMethodError){"#{BasicObject.new}"}
assert_redefine_method('Symbol', 'to_s', <<-'end')
assert_match %r{\A#<Symbol:0x[0-9a-f]+>\z}, "#{:foo}"
@@ -1094,11 +1094,17 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_redefine_method('FalseClass', 'to_s', <<-'end')
assert_match %r{\A#<FalseClass:0x[0-9a-f]+>\z}, "#{false}"
end
+ end
+
+ def test_objtostring_fixnum
assert_redefine_method('Integer', 'to_s', <<-'end')
(-1..10).each { |i|
assert_match %r{\A#<Integer:0x[0-9a-f]+>\z}, "#{i}"
}
end
+ end
+
+ def test_objtostring
assert_equal "TestRubyOptimization::Objtostring", "#{Objtostring}"
assert_match %r{\A#<Class:0x[0-9a-f]+>\z}, "#{Class.new}"
assert_match %r{\A#<Module:0x[0-9a-f]+>\z}, "#{Module.new}"
@@ -1256,6 +1262,9 @@ class TestRubyOptimization < Test::Unit::TestCase
insn = iseq.disasm
assert_match(/opt_new/, insn)
assert_match(/OptNewFoo:.+@a=1, @b=2/, iseq.eval.inspect)
+ # clean up to avoid warnings
+ Object.send :remove_const, :OptNewFoo
+ Object.remove_method :optnew_foo if defined?(optnew_foo)
end
[
'def optnew_foo(&) = OptNewFoo.new(&)',
diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb
index 98e95b98af..def41d6017 100644
--- a/test/ruby/test_parse.rb
+++ b/test/ruby/test_parse.rb
@@ -352,6 +352,21 @@ class TestParse < Test::Unit::TestCase
assert_equal("foobar", b)
end
+ def test_call_command
+ a = b = nil
+ o = Object.new
+ def o.m(*arg); proc {|a| arg.join + a }; end
+
+ assert_nothing_raised do
+ o.instance_eval <<-END, __FILE__, __LINE__+1
+ a = o.m "foo", "bar" do end.("buz")
+ b = o.m "foo", "bar" do end::("buz")
+ END
+ end
+ assert_equal("foobarbuz", a)
+ assert_equal("foobarbuz", b)
+ end
+
def test_xstring
assert_raise(Errno::ENOENT) do
eval("``")
@@ -1544,7 +1559,7 @@ x = __ENCODING__
end
def test_shareable_constant_value_simple
- obj = [['unsharable_value']]
+ obj = [['unshareable_value']]
a, b, c = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}")
begin;
# shareable_constant_value: experimental_everything
@@ -1573,7 +1588,7 @@ x = __ENCODING__
assert_ractor_shareable(a)
assert_not_ractor_shareable(obj)
assert_equal obj, a
- assert !obj.equal?(a)
+ assert_not_same obj, a
bug_20339 = '[ruby-core:117186] [Bug #20339]'
bug_20341 = '[ruby-core:117197] [Bug #20341]'
diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb
index 92a3244fc2..96aa2a7fd6 100644
--- a/test/ruby/test_pattern_matching.rb
+++ b/test/ruby/test_pattern_matching.rb
@@ -197,11 +197,49 @@ class TestPatternMatching < Test::Unit::TestCase
end
end
- assert_syntax_error(%q{
+ assert_valid_syntax(%{
+ case 0
+ in [ :a | :b, x]
+ true
+ end
+ })
+
+ assert_in_out_err(['-c'], %q{
case 0
in a | 0
end
- }, /illegal variable in alternative pattern/)
+ }, [], /alternative pattern/,
+ success: false)
+
+ assert_in_out_err(['-c'], %q{
+ case 0
+ in 0 | a
+ end
+ }, [], /alternative pattern/,
+ success: false)
+ end
+
+ def test_alternative_pattern_nested
+ assert_in_out_err(['-c'], %q{
+ case 0
+ in [a] | 1
+ end
+ }, [], /alternative pattern/,
+ success: false)
+
+ assert_in_out_err(['-c'], %q{
+ case 0
+ in { a: b } | 1
+ end
+ }, [], /alternative pattern/,
+ success: false)
+
+ assert_in_out_err(['-c'], %q{
+ case 0
+ in [{ a: [{ b: [{ c: }] }] }] | 1
+ end
+ }, [], /alternative pattern/,
+ success: false)
end
def test_var_pattern
diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb
index 35aa16063d..959ea87f25 100644
--- a/test/ruby/test_proc.rb
+++ b/test/ruby/test_proc.rb
@@ -1637,6 +1637,10 @@ class TestProc < Test::Unit::TestCase
assert_equal(3, b.local_variable_get(:when))
assert_equal(4, b.local_variable_get(:begin))
assert_equal(5, b.local_variable_get(:end))
+
+ assert_raise_with_message(NameError, /local variable \Wdefault\W/) {
+ binding.local_variable_get(:default)
+ }
end
def test_local_variable_set
@@ -1651,33 +1655,103 @@ class TestProc < Test::Unit::TestCase
def test_numparam_is_not_local_variables
"foo".tap do
- _9
+ _9 and flunk
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:_9) }
assert_raise(NameError) { binding.local_variable_set(:_9, 1) }
+ assert_raise(NameError) { binding.local_variable_defined?(:_9) }
"bar".tap do
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:_9) }
assert_raise(NameError) { binding.local_variable_set(:_9, 1) }
+ assert_raise(NameError) { binding.local_variable_defined?(:_9) }
end
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:_9) }
assert_raise(NameError) { binding.local_variable_set(:_9, 1) }
+ assert_raise(NameError) { binding.local_variable_defined?(:_9) }
end
"foo".tap do
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:_9) }
assert_raise(NameError) { binding.local_variable_set(:_9, 1) }
+ assert_raise(NameError) { binding.local_variable_defined?(:_9) }
"bar".tap do
- _9
+ _9 and flunk
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:_9) }
assert_raise(NameError) { binding.local_variable_set(:_9, 1) }
+ assert_raise(NameError) { binding.local_variable_defined?(:_9) }
end
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:_9) }
assert_raise(NameError) { binding.local_variable_set(:_9, 1) }
+ assert_raise(NameError) { binding.local_variable_defined?(:_9) }
+ end
+ end
+
+ def test_implicit_parameters_for_numparams
+ x = x = 1
+ assert_raise(NameError) { binding.implicit_parameter_get(:x) }
+ assert_raise(NameError) { binding.implicit_parameter_defined?(:x) }
+
+ "foo".tap do
+ _5 and flunk
+ assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters)
+ assert_equal("foo", binding.implicit_parameter_get(:_1))
+ assert_equal(nil, binding.implicit_parameter_get(:_5))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_equal(true, binding.implicit_parameter_defined?(:_1))
+ assert_equal(true, binding.implicit_parameter_defined?(:_5))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ "bar".tap do
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ end
+ assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters)
+ assert_equal("foo", binding.implicit_parameter_get(:_1))
+ assert_equal(nil, binding.implicit_parameter_get(:_5))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_equal(true, binding.implicit_parameter_defined?(:_1))
+ assert_equal(true, binding.implicit_parameter_defined?(:_5))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ end
+
+ "foo".tap do
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ "bar".tap do
+ _5 and flunk
+ assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters)
+ assert_equal("bar", binding.implicit_parameter_get(:_1))
+ assert_equal(nil, binding.implicit_parameter_get(:_5))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_equal(true, binding.implicit_parameter_defined?(:_1))
+ assert_equal(true, binding.implicit_parameter_defined?(:_5))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ end
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
end
end
@@ -1686,32 +1760,165 @@ class TestProc < Test::Unit::TestCase
it
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
"bar".tap do
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
end
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
"bar".tap do
it
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
end
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
end
"foo".tap do
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
"bar".tap do
it
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
end
assert_equal([], binding.local_variables)
assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
+ end
+ end
+
+ def test_implicit_parameters_for_it
+ "foo".tap do
+ it or flunk
+ assert_equal([:it], binding.implicit_parameters)
+ assert_equal("foo", binding.implicit_parameter_get(:it))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_equal(true, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ "bar".tap do
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ end
+ assert_equal([:it], binding.implicit_parameters)
+ assert_equal("foo", binding.implicit_parameter_get(:it))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_equal(true, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ end
+
+ "foo".tap do
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ "bar".tap do
+ it or flunk
+ assert_equal([:it], binding.implicit_parameters)
+ assert_equal("bar", binding.implicit_parameter_get(:it))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_equal(true, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ end
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ end
+ end
+
+ def test_implicit_parameters_for_it_complex
+ "foo".tap do
+ it = it = "bar"
+
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+
+ assert_equal([:it], binding.local_variables)
+ assert_equal("bar", binding.local_variable_get(:it))
+ assert_equal(true, binding.local_variable_defined?(:it))
+ end
+
+ "foo".tap do
+ it or flunk
+
+ assert_equal([:it], binding.implicit_parameters)
+ assert_equal("foo", binding.implicit_parameter_get(:it))
+ assert_equal(true, binding.implicit_parameter_defined?(:it))
+
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
+ end
+
+ "foo".tap do
+ it or flunk
+ it = it = "bar"
+
+ assert_equal([:it], binding.implicit_parameters)
+ assert_equal("foo", binding.implicit_parameter_get(:it))
+ assert_equal(true, binding.implicit_parameter_defined?(:it))
+
+ assert_equal([:it], binding.local_variables)
+ assert_equal("bar", binding.local_variable_get(:it))
+ assert_equal(true, binding.local_variable_defined?(:it))
+ end
+ end
+
+ def test_implicit_parameters_for_it_and_numparams
+ "foo".tap do
+ it or flunk
+ "bar".tap do
+ _5 and flunk
+ assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_equal("bar", binding.implicit_parameter_get(:_1))
+ assert_equal(nil, binding.implicit_parameter_get(:_5))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ assert_equal(true, binding.implicit_parameter_defined?(:_1))
+ assert_equal(true, binding.implicit_parameter_defined?(:_5))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ end
end
+
+ "foo".tap do
+ _5 and flunk
+ "bar".tap do
+ it or flunk
+ assert_equal([:it], binding.implicit_parameters)
+ assert_equal("bar", binding.implicit_parameter_get(:it))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_5) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_equal(true, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ assert_equal(false, binding.implicit_parameter_defined?(:_5))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ end
+ end
+ end
+
+ def test_implicit_parameter_invalid_name
+ message_pattern = /is not an implicit parameter/
+ assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_defined?(:foo) }
+ assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_get(:foo) }
+ assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_defined?("wrong_implicit_parameter_name_#{rand(10000)}") }
+ assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_get("wrong_implicit_parameter_name_#{rand(10000)}") }
end
def test_local_variable_set_wb
diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb
index 2d9d1416aa..b3a88b664c 100644
--- a/test/ruby/test_process.rb
+++ b/test/ruby/test_process.rb
@@ -58,6 +58,8 @@ class TestProcess < Test::Unit::TestCase
def test_rlimit_nofile
return unless rlimit_exist?
+ omit "LSAN needs to open proc file" if Test::Sanitizers.lsan_enabled?
+
with_tmpchdir {
File.write 's', <<-"End"
# Too small RLIMIT_NOFILE, such as zero, causes problems.
@@ -114,14 +116,19 @@ class TestProcess < Test::Unit::TestCase
}
assert_raise(ArgumentError) { Process.getrlimit(:FOO) }
assert_raise(ArgumentError) { Process.getrlimit("FOO") }
- assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.getrlimit("\u{30eb 30d3 30fc}") }
+
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.getrlimit("\u{30eb 30d3 30fc}") }
+ end
end
def test_rlimit_value
return unless rlimit_exist?
assert_raise(ArgumentError) { Process.setrlimit(:FOO, 0) }
assert_raise(ArgumentError) { Process.setrlimit(:CORE, :FOO) }
- assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit("\u{30eb 30d3 30fc}", 0) }
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit("\u{30eb 30d3 30fc}", 0) }
+ end
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit(:CORE, "\u{30eb 30d3 30fc}") }
with_tmpchdir do
s = run_in_child(<<-'End')
@@ -275,21 +282,22 @@ class TestProcess < Test::Unit::TestCase
end;
end
- MANDATORY_ENVS = %w[RUBYLIB GEM_HOME GEM_PATH]
- case RbConfig::CONFIG['target_os']
- when /linux/
- MANDATORY_ENVS << 'LD_PRELOAD'
- when /mswin|mingw/
- MANDATORY_ENVS.concat(%w[HOME USER TMPDIR PROCESSOR_ARCHITECTURE])
- when /darwin/
- MANDATORY_ENVS.concat(ENV.keys.grep(/\A__CF_/))
- end
+ MANDATORY_ENVS = %w[RUBYLIB GEM_HOME GEM_PATH RUBY_FREE_AT_EXIT]
if e = RbConfig::CONFIG['LIBPATHENV']
MANDATORY_ENVS << e
end
if e = RbConfig::CONFIG['PRELOADENV'] and !e.empty?
MANDATORY_ENVS << e
end
+ case RbConfig::CONFIG['target_os']
+ when /mswin|mingw/
+ MANDATORY_ENVS.concat(%w[HOME USER TMPDIR PROCESSOR_ARCHITECTURE])
+ when /darwin/
+ MANDATORY_ENVS.concat(%w[TMPDIR], ENV.keys.grep(/\A__CF_/))
+ # IO.popen([ENV.keys.to_h {|e| [e, nil]},
+ # RUBY, "-e", %q[print ENV.keys.join(?\0)]],
+ # &:read).split(?\0)
+ end
PREENVARG = ['-e', "%w[#{MANDATORY_ENVS.join(' ')}].each{|e|ENV.delete(e)}"]
ENVARG = ['-e', 'ENV.each {|k,v| puts "#{k}=#{v}" }']
ENVCOMMAND = [RUBY].concat(PREENVARG).concat(ENVARG)
@@ -1682,9 +1690,10 @@ class TestProcess < Test::Unit::TestCase
if u = Etc.getpwuid(Process.uid)
assert_equal(Process.uid, Process::UID.from_name(u.name), u.name)
end
- assert_raise_with_message(ArgumentError, /\u{4e0d 5b58 5728}/) {
+ exc = assert_raise_kind_of(ArgumentError, SystemCallError) {
Process::UID.from_name("\u{4e0d 5b58 5728}")
}
+ assert_match(/\u{4e0d 5b58 5728}/, exc.message) if exc.is_a?(ArgumentError)
end
end
@@ -1987,7 +1996,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_popen_reopen
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
io = File.open(IO::NULL)
io2 = io.dup
@@ -2378,7 +2387,7 @@ EOS
end
def test_deadlock_by_signal_at_forking
- assert_separately(%W(- #{RUBY}), <<-INPUT, timeout: 100)
+ assert_ruby_status(%W(- #{RUBY}), <<-INPUT, timeout: 100)
ruby = ARGV.shift
GC.start # reduce garbage
GC.disable # avoid triggering CoW after forks
@@ -2769,7 +2778,9 @@ EOS
Process.warmup
- assert_equal(total_slots_before, GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_slots))
+ # TODO: flaky
+ # assert_equal(total_slots_before, GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_slots))
+
assert_equal(0, GC.stat(:heap_empty_pages))
assert_operator(GC.stat(:total_freed_pages), :>, 0)
end;
diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb
index ec94df361f..6ae511217a 100644
--- a/test/ruby/test_ractor.rb
+++ b/test/ruby/test_ractor.rb
@@ -3,38 +3,19 @@ require 'test/unit'
class TestRactor < Test::Unit::TestCase
def test_shareability_of_iseq_proc
- y = nil.instance_eval do
+ assert_raise Ractor::IsolationError do
foo = []
- proc { foo }
+ Ractor.shareable_proc{ foo }
end
- assert_unshareable(y, /unshareable object \[\] from variable 'foo'/)
-
- y = [].instance_eval { proc { self } }
- assert_unshareable(y, /Proc's self is not shareable/)
-
- y = [].freeze.instance_eval { proc { self } }
- assert_make_shareable(y)
- end
-
- def test_shareability_of_curried_proc
- x = nil.instance_eval do
- foo = []
- proc { foo }.curry
- end
- assert_unshareable(x, /unshareable object \[\] from variable 'foo'/)
-
- x = nil.instance_eval do
- foo = 123
- proc { foo }.curry
- end
- assert_make_shareable(x)
end
def test_shareability_of_method_proc
+ # TODO: fix with Ractor.shareable_proc/lambda
+=begin
str = +""
x = str.instance_exec { proc { to_s } }
- assert_unshareable(x, /Proc's self is not shareable/)
+ assert_unshareable(x, /Proc\'s self is not shareable/)
x = str.instance_exec { method(:to_s) }
assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
@@ -58,6 +39,15 @@ class TestRactor < Test::Unit::TestCase
x = str.instance_exec { method(:itself).to_proc }
assert_unshareable(x, "can not make shareable object for #<Method: String(Kernel)#itself()>", exception: Ractor::Error)
+=end
+ end
+
+ def test_shareability_error_uses_inspect
+ x = (+"").instance_exec { method(:to_s) }
+ def x.to_s
+ raise "this should not be called"
+ end
+ assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()> because it refers unshareable objects", exception: Ractor::Error)
end
def test_default_thread_group
@@ -66,11 +56,163 @@ class TestRactor < Test::Unit::TestCase
Warning[:experimental] = false
main_ractor_id = Thread.current.group.object_id
- ractor_id = Ractor.new { Thread.current.group.object_id }.take
+ ractor_id = Ractor.new { Thread.current.group.object_id }.value
refute_equal main_ractor_id, ractor_id
end;
end
+ def test_class_instance_variables
+ assert_ractor(<<~'RUBY')
+ # Once we're in multi-ractor mode, the codepaths
+ # for class instance variables are a bit different.
+ Ractor.new {}.value
+
+ class TestClass
+ @a = 1
+ @b = 2
+ @c = 3
+ @d = 4
+ end
+
+ assert_equal 4, TestClass.remove_instance_variable(:@d)
+ assert_nil TestClass.instance_variable_get(:@d)
+ assert_equal 4, TestClass.instance_variable_set(:@d, 4)
+ assert_equal 4, TestClass.instance_variable_get(:@d)
+ RUBY
+ end
+
+ def test_struct_instance_variables
+ assert_ractor(<<~'RUBY')
+ StructIvar = Struct.new(:member) do
+ def initialize(*)
+ super
+ @ivar = "ivar"
+ end
+ attr_reader :ivar
+ end
+ obj = StructIvar.new("member")
+ obj_copy = Ractor.new { Ractor.receive }.send(obj).value
+ assert_equal obj.ivar, obj_copy.ivar
+ refute_same obj.ivar, obj_copy.ivar
+ assert_equal obj.member, obj_copy.member
+ refute_same obj.member, obj_copy.member
+ RUBY
+ end
+
+ def test_move_nested_hash_during_gc_with_yjit
+ assert_ractor(<<~'RUBY', args: [{ "RUBY_YJIT_ENABLE" => "1" }])
+ GC.stress = true
+ hash = { foo: { bar: "hello" }, baz: { qux: "there" } }
+ result = Ractor.new { Ractor.receive }.send(hash, move: true).value
+ assert_equal "hello", result[:foo][:bar]
+ assert_equal "there", result[:baz][:qux]
+ RUBY
+ end
+
+ def test_fork_raise_isolation_error
+ assert_ractor(<<~'RUBY')
+ ractor = Ractor.new do
+ Process.fork
+ rescue Ractor::IsolationError => e
+ e
+ end
+ assert_equal Ractor::IsolationError, ractor.value.class
+ RUBY
+ end if Process.respond_to?(:fork)
+
+ def test_require_raises_and_no_ractor_belonging_issue
+ assert_ractor(<<~'RUBY')
+ require "tempfile"
+ f = Tempfile.new(["file_to_require_from_ractor", ".rb"])
+ f.write("raise 'uh oh'")
+ f.flush
+ err_msg = Ractor.new(f.path) do |path|
+ begin
+ require path
+ rescue RuntimeError => e
+ e.message # had confirm belonging issue here
+ else
+ nil
+ end
+ end.value
+ assert_equal "uh oh", err_msg
+ RUBY
+ end
+
+ def test_require_non_string
+ assert_ractor(<<~'RUBY')
+ require "tempfile"
+ require "pathname"
+ f = Tempfile.new(["file_to_require_from_ractor", ".rb"])
+ f.write("")
+ f.flush
+ result = Ractor.new(f.path) do |path|
+ require Pathname.new(path)
+ "success"
+ end.value
+ assert_equal "success", result
+ RUBY
+ end
+
+ # [Bug #21398]
+ def test_port_receive_dnt_with_port_send
+ omit 'unstable on windows and macos-14' if RUBY_PLATFORM =~ /mswin|mingw|darwin/
+ assert_ractor(<<~'RUBY', timeout: 90)
+ THREADS = 10
+ JOBS_PER_THREAD = 50
+ ARRAY_SIZE = 20_000
+ def ractor_job(job_count, array_size)
+ port = Ractor::Port.new
+ workers = (1..4).map do |i|
+ Ractor.new(port) do |job_port|
+ while job = Ractor.receive
+ result = job.map { |x| x * 2 }.sum
+ job_port.send result
+ end
+ end
+ end
+ jobs = Array.new(job_count) { Array.new(array_size) { rand(1000) } }
+ jobs.each_with_index do |job, i|
+ w_idx = i % 4
+ workers[w_idx].send(job)
+ end
+ results = []
+ jobs.size.times do
+ result = port.receive # dnt receive
+ results << result
+ end
+ results
+ end
+ threads = []
+ # creates 40 ractors (THREADSx4)
+ THREADS.times do
+ threads << Thread.new do
+ ractor_job(JOBS_PER_THREAD, ARRAY_SIZE)
+ end
+ end
+ threads.each(&:join)
+ RUBY
+ end
+
+ # [Bug #20146]
+ def test_max_cpu_1
+ assert_ractor(<<~'RUBY', args: [{ "RUBY_MAX_CPU" => "1" }])
+ assert_equal :ok, Ractor.new { :ok }.value
+ RUBY
+ end
+
+ def test_symbol_proc_is_shareable
+ pr = :symbol.to_proc
+ assert_make_shareable(pr)
+ end
+
+ # [Bug #21775]
+ def test_ifunc_proc_not_shareable
+ h = Hash.new { self }
+ pr = h.to_proc
+ assert_unshareable(pr, /not supported yet/, exception: RuntimeError)
+ end
+
def assert_make_shareable(obj)
refute Ractor.shareable?(obj), "object was already shareable"
Ractor.make_shareable(obj)
diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb
index f875c0ab40..ff17dca69e 100644
--- a/test/ruby/test_range.rb
+++ b/test/ruby/test_range.rb
@@ -36,6 +36,7 @@ class TestRange < Test::Unit::TestCase
assert_equal(["a"], ("a" ... "b").to_a)
assert_equal(["a", "b"], ("a" .. "b").to_a)
assert_equal([*"a".."z", "aa"], ("a"..).take(27))
+ assert_equal([*"a".."z"], eval("('a' || 'b')..'z'").to_a)
end
def test_range_numeric_string
@@ -1457,6 +1458,12 @@ class TestRange < Test::Unit::TestCase
assert_raise(RangeError) { (1..).to_a }
end
+ def test_to_set
+ assert_equal(Set[1,2,3,4,5], (1..5).to_set)
+ assert_equal(Set[1,2,3,4], (1...5).to_set)
+ assert_raise(RangeError) { (1..).to_set }
+ end
+
def test_beginless_range_iteration
assert_raise(TypeError) { (..1).each { } }
end
diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb
index 89bb7b20a8..e0edbde463 100644
--- a/test/ruby/test_rational.rb
+++ b/test/ruby/test_rational.rb
@@ -117,9 +117,13 @@ class Rational_Test < Test::Unit::TestCase
assert_equal(Rational(111, 1000), Rational('1.11e-1'))
assert_raise(TypeError){Rational(nil)}
assert_raise(ArgumentError){Rational('')}
- assert_raise_with_message(ArgumentError, /\u{221a 2668}/) {
- Rational("\u{221a 2668}")
- }
+
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(ArgumentError, /\u{221a 2668}/) {
+ Rational("\u{221a 2668}")
+ }
+ end
+
assert_warning('') {
assert_predicate(Rational('1e-99999999999999999999'), :zero?)
}
diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb
index 6ce434790b..209e55294b 100644
--- a/test/ruby/test_refinement.rb
+++ b/test/ruby/test_refinement.rb
@@ -1933,6 +1933,29 @@ class TestRefinement < Test::Unit::TestCase
end;
end
+ def test_public_in_refine_for_method_in_superclass
+ assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ bug21446 = '[ruby-core:122558] [Bug #21446]'
+
+ class CowSuper
+ private
+ def moo() "Moo"; end
+ end
+ class Cow < CowSuper
+ end
+
+ module PublicCows
+ refine(Cow) {
+ public :moo
+ }
+ end
+
+ using PublicCows
+ assert_equal("Moo", Cow.new.moo, bug21446)
+ end;
+ end
+
module SuperToModule
class Parent
end
@@ -2712,6 +2735,30 @@ class TestRefinement < Test::Unit::TestCase
INPUT
end
+ def test_refined_module_method
+ m = Module.new {
+ x = Module.new {def qux;end}
+ refine(x) {def qux;end}
+ break x
+ }
+ extend m
+ meth = method(:qux)
+ assert_equal m, meth.owner
+ assert_equal :qux, meth.name
+ end
+
+ def test_symbol_proc_from_using_scope
+ # assert_separately to contain the side effects of refining Kernel
+ assert_separately([], <<~RUBY)
+ class RefinedScope
+ using(Module.new { refine(Kernel) { def itself = 0 } })
+ ITSELF = :itself.to_proc
+ end
+
+ assert_equal(1, RefinedScope::ITSELF[1], "[Bug #21265]")
+ RUBY
+ end
+
private
def eval_using(mod, s)
diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb
index 78269f8e9a..9feababa53 100644
--- a/test/ruby/test_regexp.rb
+++ b/test/ruby/test_regexp.rb
@@ -1036,10 +1036,12 @@ class TestRegexp < Test::Unit::TestCase
[Encoding::UTF_8, Encoding::Shift_JIS, Encoding::EUC_JP].each do |enc|
idx = key.encode(enc)
pat = /#{idx}/
- test.call {|m| assert_raise_with_message(IndexError, pat, bug10877) {m[idx]} }
- test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.offset(idx)} }
- test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.begin(idx)} }
- test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.end(idx)} }
+ EnvUtil.with_default_internal(enc) do
+ test.call {|m| assert_raise_with_message(IndexError, pat, bug10877) {m[idx]} }
+ test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.offset(idx)} }
+ test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.begin(idx)} }
+ test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.end(idx)} }
+ end
end
test.call {|m| assert_equal(/a/, m.regexp) }
test.call {|m| assert_equal("abc", m.string) }
@@ -1308,6 +1310,9 @@ class TestRegexp < Test::Unit::TestCase
assert_match(/\A[[:space:]]+\z/, "\r\n\v\f\r\s\u0085")
assert_match(/\A[[:ascii:]]+\z/, "\x00\x7F")
assert_no_match(/[[:ascii:]]/, "\x80\xFF")
+
+ assert_match(/[[:word:]]/, "\u{200C}")
+ assert_match(/[[:word:]]/, "\u{200D}")
end
def test_cclass_R
@@ -1664,6 +1669,30 @@ class TestRegexp < Test::Unit::TestCase
assert_equal("hoge fuga", h["body"])
end
+ def test_matchdata_large_capture_groups_stack
+ env = {"RUBY_THREAD_MACHINE_STACK_SIZE" => (256 * 1024).to_s}
+ assert_separately([env], <<~'RUBY')
+ n = 20000
+ require "rbconfig/sizeof"
+ stack = RubyVM::DEFAULT_PARAMS[:thread_machine_stack_size]
+ size = RbConfig::SIZEOF["long"]
+ required = (n + 1) * 4 * size
+ if !stack || stack == 0 || stack >= required
+ omit "thread machine stack size not reduced (#{stack}:#{required})"
+ end
+
+ inspect = Thread.new do
+ str = "\u{3042}" * n
+ m = Regexp.new("(.)" * n).match(str)
+ assert_not_nil(m)
+ assert_equal([n - 1, n], m.offset(n))
+ m.inspect
+ end.value
+
+ assert_include(inspect, "MatchData")
+ RUBY
+ end
+
def test_regexp_popped
EnvUtil.suppress_warning do
assert_nothing_raised { eval("a = 1; /\#{ a }/; a") }
@@ -1738,6 +1767,33 @@ class TestRegexp < Test::Unit::TestCase
assert_raise(RegexpError, bug12418){ Regexp.new('(0?0|(?(5)||)|(?(5)||))?') }
end
+ def test_quick_search
+ assert_match_at('(?i) *TOOKY', 'Mozilla/5.0 (Linux; Android 4.0.3; TOOKY', [[34, 40]]) # Issue #120
+ end
+
+ def test_ss_in_look_behind
+ assert_match_at("(?i:ss)", "ss", [[0, 2]])
+ assert_match_at("(?i:ss)", "Ss", [[0, 2]])
+ assert_match_at("(?i:ss)", "SS", [[0, 2]])
+ assert_match_at("(?i:ss)", "\u017fS", [[0, 2]]) # LATIN SMALL LETTER LONG S
+ assert_match_at("(?i:ss)", "s\u017f", [[0, 2]])
+ assert_match_at("(?i:ss)", "\u00df", [[0, 1]]) # LATIN SMALL LETTER SHARP S
+ assert_match_at("(?i:ss)", "\u1e9e", [[0, 1]]) # LATIN CAPITAL LETTER SHARP S
+ assert_match_at("(?i:xssy)", "xssy", [[0, 4]])
+ assert_match_at("(?i:xssy)", "xSsy", [[0, 4]])
+ assert_match_at("(?i:xssy)", "xSSy", [[0, 4]])
+ assert_match_at("(?i:xssy)", "x\u017fSy", [[0, 4]])
+ assert_match_at("(?i:xssy)", "xs\u017fy", [[0, 4]])
+ assert_match_at("(?i:xssy)", "x\u00dfy", [[0, 3]])
+ assert_match_at("(?i:xssy)", "x\u1e9ey", [[0, 3]])
+ assert_match_at("(?i:\u00df)", "ss", [[0, 2]])
+ assert_match_at("(?i:\u00df)", "SS", [[0, 2]])
+ assert_match_at("(?i:[\u00df])", "ss", [[0, 2]])
+ assert_match_at("(?i:[\u00df])", "SS", [[0, 2]])
+ assert_match_at("(?i)(?<!ss)\u2728", "qq\u2728", [[2, 3]]) # Issue #92
+ assert_match_at("(?i)(?<!xss)\u2728", "qq\u2728", [[2, 3]])
+ end
+
def test_options_in_look_behind
assert_nothing_raised {
assert_match_at("(?<=(?i)ab)cd", "ABcd", [[2,4]])
@@ -1875,6 +1931,12 @@ class TestRegexp < Test::Unit::TestCase
end;
end
+ def test_too_big_number_for_repeat_range
+ assert_raise_with_message(SyntaxError, /too big number for repeat range/) do
+ eval(%[/|{1000000}/])
+ end
+ end
+
# This assertion is for porting x2() tests in testpy.py of Onigmo.
def assert_match_at(re, str, positions, msg = nil)
re = Regexp.new(re) unless re.is_a?(Regexp)
@@ -1979,7 +2041,7 @@ class TestRegexp < Test::Unit::TestCase
end
def per_instance_redos_test(global_timeout, per_instance_timeout, expected_timeout)
- assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", timeout: 60)
global_timeout = #{ EnvUtil.apply_timeout_scale(global_timeout).inspect }
per_instance_timeout = #{ (per_instance_timeout ? EnvUtil.apply_timeout_scale(per_instance_timeout) : nil).inspect }
expected_timeout = #{ EnvUtil.apply_timeout_scale(expected_timeout).inspect }
diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb
index 13e7076391..0067a49700 100644
--- a/test/ruby/test_require.rb
+++ b/test/ruby/test_require.rb
@@ -840,6 +840,36 @@ class TestRequire < Test::Unit::TestCase
p :ok
end;
}
+
+ # [Bug #21567]
+ assert_ruby_status(%w[-rtempfile], "#{<<~"begin;"}\n#{<<~"end;"}")
+ begin;
+ class MyString
+ def initialize(path)
+ @path = path
+ end
+
+ def to_str
+ $LOADED_FEATURES.clear
+ @path
+ end
+
+ def to_path = @path
+ end
+
+ FILES = []
+
+ def create_ruby_file
+ file = Tempfile.open(["test", ".rb"])
+ FILES << file
+ file.path
+ end
+
+ require MyString.new(create_ruby_file)
+ $LOADED_FEATURES.unshift(create_ruby_file)
+ $LOADED_FEATURES << MyString.new(create_ruby_file)
+ require create_ruby_file
+ end;
end
def test_loading_fifo_threading_raise
@@ -999,7 +1029,7 @@ class TestRequire < Test::Unit::TestCase
def test_require_with_public_method_missing
# [Bug #19793]
- assert_separately(["-W0", "-rtempfile"], __FILE__, __LINE__, <<~RUBY, timeout: 60)
+ assert_ruby_status(["-W0", "-rtempfile"], <<~RUBY, timeout: 60)
GC.stress = true
class Object
@@ -1011,4 +1041,18 @@ class TestRequire < Test::Unit::TestCase
end
RUBY
end
+
+ def test_bug_21568
+ load_path = $LOAD_PATH.dup
+ loaded_featrures = $LOADED_FEATURES.dup
+
+ $LOAD_PATH.clear
+ $LOADED_FEATURES.replace(["foo.so", "a/foo.rb", "b/foo.rb"])
+
+ assert_nothing_raised(LoadError) { require "foo" }
+
+ ensure
+ $LOAD_PATH.replace(load_path) if load_path
+ $LOADED_FEATURES.replace loaded_featrures
+ end
end
diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb
index d7184f1057..cd2dd5d3ff 100644
--- a/test/ruby/test_rubyoptions.rb
+++ b/test/ruby/test_rubyoptions.rb
@@ -8,8 +8,6 @@ require_relative '../lib/jit_support'
require_relative '../lib/parser_support'
class TestRubyOptions < Test::Unit::TestCase
- def self.yjit_enabled? = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
-
# Here we're defining our own RUBY_DESCRIPTION without "+PRISM". We do this
# here so that the various tests that reference RUBY_DESCRIPTION don't have to
# worry about it. The flag itself is tested in its own test.
@@ -22,8 +20,10 @@ class TestRubyOptions < Test::Unit::TestCase
NO_JIT_DESCRIPTION =
case
- when yjit_enabled?
- RUBY_DESCRIPTION.sub(/\+YJIT( (dev|dev_nodebug|stats))? /, '')
+ when JITSupport.yjit_enabled?
+ RUBY_DESCRIPTION.sub(/\+YJIT( \w+)? /, '')
+ when JITSupport.zjit_enabled?
+ RUBY_DESCRIPTION.sub(/\+ZJIT( \w+)? /, '')
else
RUBY_DESCRIPTION
end
@@ -47,10 +47,15 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err([], "", [], [])
end
+ # This constant enforces the traditional 80x25 terminal size standard
+ TRADITIONAL_TERM_COLS = 80 # DO NOT MODIFY!
+ TRADITIONAL_TERM_ROWS = 25 # DO NOT MODIFY!
+
def test_usage
+ # This test checks if the output of `ruby -h` fits in 80x25
assert_in_out_err(%w(-h)) do |r, e|
- assert_operator(r.size, :<=, 25)
- longer = r[1..-1].select {|x| x.size >= 80}
+ assert_operator(r.size, :<=, TRADITIONAL_TERM_ROWS)
+ longer = r[1..-1].select {|x| x.size >= TRADITIONAL_TERM_COLS}
assert_equal([], longer)
assert_equal([], e)
end
@@ -174,7 +179,7 @@ class TestRubyOptions < Test::Unit::TestCase
def test_verbose
assert_in_out_err([{'RUBY_YJIT_ENABLE' => nil}, "-vve", ""]) do |r, e|
assert_match(VERSION_PATTERN, r[0])
- if self.class.yjit_enabled? && !JITSupport.yjit_force_enabled?
+ if (JITSupport.yjit_enabled? && !JITSupport.yjit_force_enabled?) || JITSupport.zjit_enabled?
assert_equal(NO_JIT_DESCRIPTION, r[0])
else
assert_equal(RUBY_DESCRIPTION, r[0])
@@ -203,6 +208,8 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%w(--enable foobarbazqux -e) + [""], "", [],
/unknown argument for --enable: 'foobarbazqux'/)
assert_in_out_err(%w(--enable), "", [], /missing argument for --enable/)
+ assert_in_out_err(%w(-e) + ['p defined? Gem'], "", %w["constant"], [], gems: true)
+ assert_in_out_err(%w(-e) + ['p defined? Gem'], "", %w["constant"], [], gems: nil)
end
def test_disable
@@ -212,7 +219,7 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%w(--disable foobarbazqux -e) + [""], "", [],
/unknown argument for --disable: 'foobarbazqux'/)
assert_in_out_err(%w(--disable), "", [], /missing argument for --disable/)
- assert_in_out_err(%w(-e) + ['p defined? Gem'], "", ["nil"], [])
+ assert_in_out_err(%w(-e) + ['p defined? Gem'], "", ["nil"], [], gems: false)
assert_in_out_err(%w(--disable-did_you_mean -e) + ['p defined? DidYouMean'], "", ["nil"], [])
assert_in_out_err(%w(-e) + ['p defined? DidYouMean'], "", ["nil"], [])
end
@@ -240,7 +247,7 @@ class TestRubyOptions < Test::Unit::TestCase
assert_match(VERSION_PATTERN, r[0])
if ENV['RUBY_YJIT_ENABLE'] == '1'
assert_equal(NO_JIT_DESCRIPTION, r[0])
- elsif self.class.yjit_enabled? # checking -DYJIT_FORCE_ENABLE
+ elsif JITSupport.yjit_enabled? || JITSupport.zjit_enabled? # checking -DYJIT_FORCE_ENABLE
assert_equal(EnvUtil.invoke_ruby(['-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0])
else
assert_equal(RUBY_DESCRIPTION, r[0])
@@ -260,6 +267,8 @@ class TestRubyOptions < Test::Unit::TestCase
end
def test_parser_flag
+ omit if ENV["RUBYOPT"]&.include?("--parser=")
+
assert_in_out_err(%w(--parser=prism -e) + ["puts :hi"], "", %w(hi), [])
assert_in_out_err(%w(--parser=prism --dump=parsetree -e _=:hi), "", /"hi"/, [])
@@ -519,6 +528,8 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%w(- -#=foo), "#!ruby -s\n", [],
/invalid name for global variable - -# \(NameError\)/)
+
+ assert_in_out_err(['-s', '-e', 'GC.start; p $DEBUG', '--', '-DEBUG=x'], "", ['"x"'])
end
def test_option_missing_argument
@@ -785,6 +796,12 @@ class TestRubyOptions < Test::Unit::TestCase
unless /mswin|mingw/ =~ RUBY_PLATFORM
opts[:rlimit_core] = 0
end
+ opts[:failed] = proc do |status, message = "", out = ""|
+ if (sig = status.termsig) && Signal.list["SEGV"] == sig
+ out = ""
+ end
+ Test::Unit::CoreAssertions::FailDesc[status, message]
+ end
ExecOptions = opts.freeze
# The regexp list that should match the entire stderr output.
@@ -834,20 +851,24 @@ class TestRubyOptions < Test::Unit::TestCase
end
def assert_segv(args, message=nil, list: SEGVTest::ExpectedStderrList, **opt, &block)
- pend "macOS 15 is not working with this assertion" if macos?(15)
-
# We want YJIT to be enabled in the subprocess if it's enabled for us
# so that the Ruby description matches.
env = Hash === args.first ? args.shift : {}
- args.unshift("--yjit") if self.class.yjit_enabled?
+ args.unshift("--yjit") if JITSupport.yjit_enabled?
+ args.unshift("--zjit") if JITSupport.zjit_enabled?
env.update({'RUBY_ON_BUG' => nil})
+ env['RUBY_CRASH_REPORT'] ||= nil # default to not passing down parent setting
# ASAN registers a segv handler which prints out "AddressSanitizer: DEADLYSIGNAL" when
# catching sigsegv; we don't expect that output, so suppress it.
- env.update({'ASAN_OPTIONS' => 'handle_segv=0'})
+ env.update({'ASAN_OPTIONS' => 'handle_segv=0', 'LSAN_OPTIONS' => 'handle_segv=0'})
args.unshift(env)
test_stdin = ""
- tests = [//, list] unless block
+ if !block
+ tests = [//, list, message]
+ elsif message
+ tests = [[], [], message]
+ end
assert_in_out_err(args, test_stdin, *tests, encoding: "ASCII-8BIT",
**SEGVTest::ExecOptions, **opt, &block)
@@ -860,13 +881,12 @@ class TestRubyOptions < Test::Unit::TestCase
def test_segv_loaded_features
bug7402 = '[ruby-core:49573]'
- status = assert_segv(['-e', "END {#{SEGVTest::KILL_SELF}}",
- '-e', 'class Bogus; def to_str; exit true; end; end',
- '-e', '$".clear',
- '-e', '$".unshift Bogus.new',
- '-e', '(p $"; abort) unless $".size == 1',
- ])
- assert_not_predicate(status, :success?, "segv but success #{bug7402}")
+ assert_segv(['-e', "END {#{SEGVTest::KILL_SELF}}",
+ '-e', 'class Bogus; def to_str; exit true; end; end',
+ '-e', '$".clear',
+ '-e', '$".unshift Bogus.new',
+ '-e', '(p $"; abort) unless $".size == 1',
+ ], bug7402, success: false)
end
def test_segv_setproctitle
@@ -879,8 +899,6 @@ class TestRubyOptions < Test::Unit::TestCase
end
def assert_crash_report(path, cmd = nil, &block)
- pend "macOS 15 is not working with this assertion" if macos?(15)
-
Dir.mktmpdir("ruby_crash_report") do |dir|
list = SEGVTest::ExpectedStderrList
if cmd
@@ -934,6 +952,27 @@ class TestRubyOptions < Test::Unit::TestCase
end
end
+ def test_crash_report_pipe_script
+ omit "only runs on Linux" unless RUBY_PLATFORM.include?("linux")
+
+ Tempfile.create(["script", ".sh"]) do |script|
+ Tempfile.create("crash_report") do |crash_report|
+ script.write(<<~BASH)
+ #!/usr/bin/env bash
+
+ cat > #{crash_report.path}
+ BASH
+ script.close
+
+ FileUtils.chmod("+x", script)
+
+ assert_crash_report("| #{script.path}") do
+ assert_include(File.read(crash_report.path), "[BUG] Segmentation fault at")
+ end
+ end
+ end
+ end
+
def test_DATA
Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t|
t.puts "puts DATA.read.inspect"
@@ -980,7 +1019,7 @@ class TestRubyOptions < Test::Unit::TestCase
pid = spawn(EnvUtil.rubybin, :in => s, :out => w)
w.close
assert_nothing_raised('[ruby-dev:37798]') do
- result = EnvUtil.timeout(3) {r.read}
+ result = EnvUtil.timeout(10) {r.read}
end
Process.wait pid
}
@@ -1278,4 +1317,10 @@ class TestRubyOptions < Test::Unit::TestCase
def test_toplevel_ruby
assert_instance_of Module, ::Ruby
end
+
+ def test_ruby_patchlevel
+ # We stopped bumping RUBY_PATCHLEVEL at Ruby 4.0.0.
+ # Released versions have RUBY_PATCHLEVEL 0, and un-released versions have -1.
+ assert_include [-1, 0], RUBY_PATCHLEVEL
+ end
end
diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb
index 2bb7858eb2..70a61aa3b5 100644
--- a/test/ruby/test_set.rb
+++ b/test/ruby/test_set.rb
@@ -3,8 +3,11 @@ require 'test/unit'
require 'set'
class TC_Set < Test::Unit::TestCase
- class Set2 < Set
+ class SetSubclass < Set
end
+ class CoreSetSubclass < Set::CoreSet
+ end
+ ALL_SET_CLASSES = [Set, SetSubclass, CoreSetSubclass].freeze
def test_marshal
set = Set[1, 2, 3]
@@ -130,6 +133,12 @@ class TC_Set < Test::Unit::TestCase
assert_equal(Set['a','b','c'], set)
set = Set[1,2]
+ ret = set.replace(Set.new('a'..'c'))
+
+ assert_same(set, ret)
+ assert_equal(Set['a','b','c'], set)
+
+ set = Set[1,2]
assert_raise(ArgumentError) {
set.replace(3)
}
@@ -258,7 +267,7 @@ class TC_Set < Test::Unit::TestCase
set.superset?([2])
}
- [Set, Set2].each { |klass|
+ ALL_SET_CLASSES.each { |klass|
assert_equal(true, set.superset?(klass[]), klass.name)
assert_equal(true, set.superset?(klass[1,2]), klass.name)
assert_equal(true, set.superset?(klass[1,2,3]), klass.name)
@@ -287,7 +296,7 @@ class TC_Set < Test::Unit::TestCase
set.proper_superset?([2])
}
- [Set, Set2].each { |klass|
+ ALL_SET_CLASSES.each { |klass|
assert_equal(true, set.proper_superset?(klass[]), klass.name)
assert_equal(true, set.proper_superset?(klass[1,2]), klass.name)
assert_equal(false, set.proper_superset?(klass[1,2,3]), klass.name)
@@ -316,7 +325,7 @@ class TC_Set < Test::Unit::TestCase
set.subset?([2])
}
- [Set, Set2].each { |klass|
+ ALL_SET_CLASSES.each { |klass|
assert_equal(true, set.subset?(klass[1,2,3,4]), klass.name)
assert_equal(true, set.subset?(klass[1,2,3]), klass.name)
assert_equal(false, set.subset?(klass[1,2]), klass.name)
@@ -345,7 +354,7 @@ class TC_Set < Test::Unit::TestCase
set.proper_subset?([2])
}
- [Set, Set2].each { |klass|
+ ALL_SET_CLASSES.each { |klass|
assert_equal(true, set.proper_subset?(klass[1,2,3,4]), klass.name)
assert_equal(false, set.proper_subset?(klass[1,2,3]), klass.name)
assert_equal(false, set.proper_subset?(klass[1,2]), klass.name)
@@ -365,7 +374,7 @@ class TC_Set < Test::Unit::TestCase
assert_nil(set <=> set.to_a)
- [Set, Set2].each { |klass|
+ ALL_SET_CLASSES.each { |klass|
assert_equal(-1, set <=> klass[1,2,3,4], klass.name)
assert_equal( 0, set <=> klass[3,2,1] , klass.name)
assert_equal(nil, set <=> klass[1,2,4] , klass.name)
@@ -681,15 +690,28 @@ class TC_Set < Test::Unit::TestCase
end
def test_xor
- set = Set[1,2,3,4]
- ret = set ^ [2,4,5,5]
- assert_not_same(set, ret)
- assert_equal(Set[1,3,5], ret)
+ ALL_SET_CLASSES.each { |klass|
+ set = klass[1,2,3,4]
+ ret = set ^ [2,4,5,5]
+ assert_not_same(set, ret)
+ assert_equal(klass[1,3,5], ret)
+
+ set2 = klass[1,2,3,4]
+ ret2 = set2 ^ [2,4,5,5]
+ assert_instance_of(klass, ret2)
+ assert_equal(klass[1,3,5], ret2)
+ }
+ end
+
+ def test_xor_does_not_mutate_other_set
+ a = Set[1]
+ b = Set[1, 2]
+ original_b = b.dup
+
+ result = a ^ b
- set2 = Set2[1,2,3,4]
- ret2 = set2 ^ [2,4,5,5]
- assert_instance_of(Set2, ret2)
- assert_equal(Set2[1,3,5], ret2)
+ assert_equal(original_b, b)
+ assert_equal(Set[2], result)
end
def test_eq
@@ -775,6 +797,10 @@ class TC_Set < Test::Unit::TestCase
ret.each { |s| n += s.size }
assert_equal(set.size, n)
assert_equal(set, ret.flatten)
+
+ set = Set[2,12,9,11,13,4,10,15,3,8,5,0,1,7,14]
+ ret = set.divide { |a,b| (a - b).abs == 1 }
+ assert_equal(2, ret.size)
end
def test_freeze
@@ -829,24 +855,32 @@ class TC_Set < Test::Unit::TestCase
def test_inspect
set1 = Set[1, 2]
- assert_equal('#<Set: {1, 2}>', set1.inspect)
+ assert_equal('Set[1, 2]', set1.inspect)
set2 = Set[Set[0], 1, 2, set1]
- assert_equal('#<Set: {#<Set: {0}>, 1, 2, #<Set: {1, 2}>}>', set2.inspect)
+ assert_equal('Set[Set[0], 1, 2, Set[1, 2]]', set2.inspect)
set1.add(set2)
- assert_equal('#<Set: {#<Set: {0}>, 1, 2, #<Set: {1, 2, #<Set: {...}>}>}>', set2.inspect)
+ assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.inspect)
+
+ c = Class.new(Set::CoreSet)
+ c.set_temporary_name("_MySet")
+ assert_equal('_MySet[1, 2]', c[1, 2].inspect)
+
+ c = Class.new(Set)
+ c.set_temporary_name("_MySet")
+ assert_equal('#<_MySet: {1, 2}>', c[1, 2].inspect)
end
def test_to_s
set1 = Set[1, 2]
- assert_equal('#<Set: {1, 2}>', set1.to_s)
+ assert_equal('Set[1, 2]', set1.to_s)
set2 = Set[Set[0], 1, 2, set1]
- assert_equal('#<Set: {#<Set: {0}>, 1, 2, #<Set: {1, 2}>}>', set2.to_s)
+ assert_equal('Set[Set[0], 1, 2, Set[1, 2]]', set2.to_s)
set1.add(set2)
- assert_equal('#<Set: {#<Set: {0}>, 1, 2, #<Set: {1, 2, #<Set: {...}>}>}>', set2.to_s)
+ assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.to_s)
end
def test_compare_by_identity
@@ -896,6 +930,39 @@ class TC_Set < Test::Unit::TestCase
end
end;
end
+
+ def test_larger_sets
+ set = Set.new
+ 10_000.times do |i|
+ set << i
+ end
+ set = set.dup
+
+ 10_000.times do |i|
+ assert_includes set, i
+ end
+ end
+
+ def test_subclass_new_calls_add
+ c = Class.new(Set) do
+ def add(o)
+ super
+ super(o+1)
+ end
+ end
+ assert_equal([1, 2], c.new([1]).to_a)
+ end
+
+ def test_subclass_aref_calls_initialize
+ c = Class.new(Set) do
+ def initialize(enum)
+ super
+ add(1)
+ end
+ end
+ assert_equal([2, 1], c[2].to_a)
+ end
+
end
class TC_Enumerable < Test::Unit::TestCase
@@ -913,6 +980,36 @@ class TC_Enumerable < Test::Unit::TestCase
assert_same set, set.to_set
assert_not_same set, set.to_set { |o| o }
end
+
+ class MyEnum
+ include Enumerable
+
+ def initialize(array)
+ @array = array
+ end
+
+ def each(&block)
+ @array.each(&block)
+ end
+
+ def size
+ raise "should not be called"
+ end
+ end
+
+ def test_to_set_not_calling_size
+ enum = MyEnum.new([1,2,3])
+
+ set = assert_nothing_raised { enum.to_set }
+ assert(set.is_a?(Set))
+ assert_equal(Set[1,2,3], set)
+
+ enumerator = enum.to_enum
+
+ set = assert_nothing_raised { enumerator.to_set }
+ assert(set.is_a?(Set))
+ assert_equal(Set[1,2,3], set)
+ end
end
class TC_Set_Builtin < Test::Unit::TestCase
diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb
index 55c07abbea..d3b2441e21 100644
--- a/test/ruby/test_settracefunc.rb
+++ b/test/ruby/test_settracefunc.rb
@@ -1999,7 +1999,7 @@ CODE
TracePoint.new(:c_call, &capture_events).enable{
c.new
}
- assert_equal [:c_call, :itself, :initialize], events[1]
+ assert_equal [:c_call, :itself, :initialize], events[0]
events.clear
o = Class.new{
@@ -2226,7 +2226,7 @@ CODE
def test_thread_add_trace_func
events = []
base_line = __LINE__
- q = Thread::Queue.new
+ q = []
t = Thread.new{
Thread.current.add_trace_func proc{|ev, file, line, *args|
events << [ev, line] if file == __FILE__
@@ -2724,7 +2724,7 @@ CODE
end
def test_disable_local_tracepoint_in_trace
- assert_normal_exit <<-EOS
+ assert_normal_exit(<<-EOS, timeout: 60)
def foo
trace = TracePoint.new(:b_return){|tp|
tp.disable
@@ -2957,4 +2957,210 @@ CODE
assert_kind_of(Thread, target_thread)
end
+
+ def test_tracepoint_garbage_collected_when_disable
+ before_count_stat = 0
+ before_count_objspace = 0
+ TracePoint.stat.each do
+ before_count_stat += 1
+ end
+ ObjectSpace.each_object(TracePoint) do
+ before_count_objspace += 1
+ end
+ tp = TracePoint.new(:c_call, :c_return) do
+ end
+ tp.enable
+ Class.inspect # c_call, c_return invoked
+ tp.disable
+ tp_id = tp.object_id
+ tp = nil
+
+ gc_times = 0
+ gc_max_retries = 10
+ EnvUtil.suppress_warning do
+ until (ObjectSpace._id2ref(tp_id) rescue nil).nil?
+ GC.start
+ gc_times += 1
+ if gc_times == gc_max_retries
+ break
+ end
+ end
+ end
+ return if gc_times == gc_max_retries
+
+ after_count_stat = 0
+ TracePoint.stat.each do |v|
+ after_count_stat += 1
+ end
+ assert after_count_stat <= before_count_stat
+ after_count_objspace = 0
+ ObjectSpace.each_object(TracePoint) do
+ after_count_objspace += 1
+ end
+ assert after_count_objspace <= before_count_objspace
+ end
+
+ def test_tp_ractor_local_untargeted
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ r = Ractor.new do
+ results = []
+ tp = TracePoint.new(:line) { |tp| results << tp.path }
+ tp.enable
+ Ractor.main << :continue
+ Ractor.receive
+ tp.disable
+ results
+ end
+ outer_results = []
+ outer_tp = TracePoint.new(:line) { |tp| outer_results << tp.path }
+ outer_tp.enable
+ Ractor.receive
+ GC.start # so I can check <internal:gc> path
+ r << :continue
+ inner_results = r.value
+ outer_tp.disable
+ assert_equal 1, outer_results.select { |path| path.match?(/internal:gc/) }.size
+ assert_equal 0, inner_results.select { |path| path.match?(/internal:gc/) }.size
+ end;
+ end
+
+ def test_tp_targeted_ractor_local_bmethod
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ mname = :foo
+ prok = Ractor.shareable_proc do
+ end
+ klass = EnvUtil.labeled_class(:Klass) do
+ define_method(mname, &prok)
+ end
+ outer_results = 0
+ _outer_tp = TracePoint.new(:call) do
+ outer_results += 1
+ end # not enabled
+ rs = 10.times.map do
+ Ractor.new(mname, klass) do |mname, klass0|
+ inner_results = 0
+ tp = TracePoint.new(:call) { |tp| inner_results += 1 }
+ target = klass0.instance_method(mname)
+ tp.enable(target: target)
+ obj = klass0.new
+ 10.times { obj.send(mname) }
+ tp.disable
+ inner_results
+ end
+ end
+ inner_results = rs.map(&:value).sum
+ obj = klass.new
+ 10.times { obj.send(mname) }
+ assert_equal 100, inner_results
+ assert_equal 0, outer_results
+ end;
+ end
+
+ def test_tp_targeted_ractor_local_method
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ def foo
+ end
+ outer_results = 0
+ _outer_tp = TracePoint.new(:call) do
+ outer_results += 1
+ end # not enabled
+
+ rs = 10.times.map do
+ Ractor.new do
+ inner_results = 0
+ tp = TracePoint.new(:call) do
+ inner_results += 1
+ end
+ tp.enable(target: method(:foo))
+ 10.times { foo }
+ tp.disable
+ inner_results
+ end
+ end
+
+ inner_results = rs.map(&:value).sum
+ 10.times { foo }
+ assert_equal 100, inner_results
+ assert_equal 0, outer_results
+ end;
+ end
+
+ def test_tracepoints_not_disabled_by_ractor_gc
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $-w = nil # uses ObjectSpace._id2ref
+ def hi = "hi"
+ greetings = 0
+ tp_target = TracePoint.new(:call) do |tp|
+ greetings += 1
+ end
+ tp_target.enable(target: method(:hi))
+
+ raises = 0
+ tp_global = TracePoint.new(:raise) do |tp|
+ raises += 1
+ end
+ tp_global.enable
+
+ r = Ractor.new { 10 }
+ r.join
+ ractor_id = r.object_id
+ r = nil # allow gc for ractor
+ gc_max_retries = 15
+ gc_times = 0
+ # force GC of ractor (or try, because we have a conservative GC)
+ until (ObjectSpace._id2ref(ractor_id) rescue nil).nil?
+ GC.start
+ gc_times += 1
+ if gc_times == gc_max_retries
+ break
+ end
+ end
+
+ # tracepoints should still be enabled after GC of `r`
+ 5.times {
+ hi
+ }
+ 6.times {
+ raise "uh oh" rescue nil
+ }
+ tp_target.disable
+ tp_global.disable
+ assert_equal 5, greetings
+ if gc_times == gc_max_retries # _id2ref never raised
+ assert_equal 6, raises
+ else
+ assert_equal 7, raises
+ end
+ end;
+ end
+
+ def test_lots_of_enabled_tracepoints_ractor_gc
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ def foo; end
+ sum = 8.times.map do
+ Ractor.new do
+ called = 0
+ TracePoint.new(:call) do |tp|
+ next if tp.callee_id != :foo
+ called += 1
+ end.enable
+ 200.times do
+ TracePoint.new(:line) {
+ # all these allocations shouldn't GC these tracepoints while the ractor is alive.
+ Object.new
+ }.enable
+ end
+ 100.times { foo }
+ called
+ end
+ end.map(&:value).sum
+ assert_equal 800, sum
+ 4.times { GC.start } # Now the tracepoints can be GC'd because the ractors can be GC'd
+ end;
+ end
end
diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb
index 0c1d8d424e..67e2c543a3 100644
--- a/test/ruby/test_shapes.rb
+++ b/test/ruby/test_shapes.rb
@@ -2,6 +2,7 @@
require 'test/unit'
require 'objspace'
require 'json'
+require 'securerandom'
# These test the functionality of object shapes
class TestShapes < Test::Unit::TestCase
@@ -92,15 +93,18 @@ class TestShapes < Test::Unit::TestCase
# RubyVM::Shape.of returns new instances of shape objects for
# each call. This helper method allows us to define equality for
# shapes
- def assert_shape_equal(shape1, shape2)
- assert_equal(shape1.id, shape2.id)
- assert_equal(shape1.parent_id, shape2.parent_id)
- assert_equal(shape1.depth, shape2.depth)
- assert_equal(shape1.type, shape2.type)
+ def assert_shape_equal(e, a)
+ assert_equal(
+ {id: e.id, parent_id: e.parent_id, depth: e.depth, type: e.type},
+ {id: a.id, parent_id: a.parent_id, depth: a.depth, type: a.type},
+ )
end
- def refute_shape_equal(shape1, shape2)
- refute_equal(shape1.id, shape2.id)
+ def refute_shape_equal(e, a)
+ refute_equal(
+ {id: e.id, parent_id: e.parent_id, depth: e.depth, type: e.type},
+ {id: a.id, parent_id: a.parent_id, depth: a.depth, type: a.type},
+ )
end
def test_iv_order_correct_on_complex_objects
@@ -146,11 +150,14 @@ class TestShapes < Test::Unit::TestCase
def test_too_many_ivs_on_class
obj = Class.new
- (MANY_IVS + 1).times do
+ obj.instance_variable_set(:@test_too_many_ivs_on_class, 1)
+ refute_predicate RubyVM::Shape.of(obj), :too_complex?
+
+ MANY_IVS.times do
obj.instance_variable_set(:"@a#{_1}", 1)
end
- assert_false RubyVM::Shape.of(obj).too_complex?
+ refute_predicate RubyVM::Shape.of(obj), :too_complex?
end
def test_removing_when_too_many_ivs_on_class
@@ -221,7 +228,7 @@ class TestShapes < Test::Unit::TestCase
end
def test_run_out_of_shape_for_object
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
class A
def initialize
@@ -332,7 +339,7 @@ class TestShapes < Test::Unit::TestCase
end
def test_gc_stress_during_evacuate_generic_ivar
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
[].instance_variable_set(:@a, 1)
@@ -500,7 +507,7 @@ class TestShapes < Test::Unit::TestCase
end
def test_run_out_of_shape_rb_obj_copy_ivar
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
class A
def initialize
@@ -596,8 +603,8 @@ class TestShapes < Test::Unit::TestCase
assert_predicate RubyVM::Shape.of(tc), :too_complex?
assert_equal 3, tc.very_unique
- assert_equal 3, Ractor.new(tc) { |x| Ractor.yield(x.very_unique) }.take
- assert_equal tc.instance_variables.sort, Ractor.new(tc) { |x| Ractor.yield(x.instance_variables) }.take.sort
+ assert_equal 3, Ractor.new(tc) { |x| x.very_unique }.value
+ assert_equal tc.instance_variables.sort, Ractor.new(tc) { |x| x.instance_variables }.value.sort
end;
end
@@ -622,6 +629,97 @@ class TestShapes < Test::Unit::TestCase
end;
end
+ def test_too_complex_and_frozen
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $VERBOSE = nil
+ class TooComplex
+ attr_reader :very_unique
+ end
+
+ RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
+ TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new)
+ end
+
+ tc = TooComplex.new
+ tc.instance_variable_set(:"@very_unique", 3)
+
+ shape = RubyVM::Shape.of(tc)
+ assert_predicate shape, :too_complex?
+ refute_predicate shape, :shape_frozen?
+ tc.freeze
+ frozen_shape = RubyVM::Shape.of(tc)
+ refute_equal shape.id, frozen_shape.id
+ assert_predicate frozen_shape, :too_complex?
+ assert_predicate frozen_shape, :shape_frozen?
+
+ assert_equal 3, tc.very_unique
+ assert_equal 3, Ractor.make_shareable(tc).very_unique
+ end;
+ end
+
+ def test_object_id_transition_too_complex
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ RubyVM::Shape.exhaust_shapes
+ assert_equal obj.object_id, obj.object_id
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Hi; end
+ obj = Hi.new
+ obj.instance_variable_set(:@a, 1)
+ obj.instance_variable_set(:@b, 2)
+ old_id = obj.object_id
+
+ RubyVM::Shape.exhaust_shapes
+ obj.remove_instance_variable(:@a)
+
+ assert_equal old_id, obj.object_id
+ end;
+ end
+
+ def test_too_complex_and_frozen_and_object_id
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $VERBOSE = nil
+ class TooComplex
+ attr_reader :very_unique
+ end
+
+ RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
+ TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new)
+ end
+
+ tc = TooComplex.new
+ tc.instance_variable_set(:"@very_unique", 3)
+
+ shape = RubyVM::Shape.of(tc)
+ assert_predicate shape, :too_complex?
+ refute_predicate shape, :shape_frozen?
+ tc.freeze
+ frozen_shape = RubyVM::Shape.of(tc)
+ refute_equal shape.id, frozen_shape.id
+ assert_predicate frozen_shape, :too_complex?
+ assert_predicate frozen_shape, :shape_frozen?
+ refute_predicate frozen_shape, :has_object_id?
+
+ assert_equal tc.object_id, tc.object_id
+
+ id_shape = RubyVM::Shape.of(tc)
+ refute_equal frozen_shape.id, id_shape.id
+ assert_predicate id_shape, :too_complex?
+ assert_predicate id_shape, :has_object_id?
+ assert_predicate id_shape, :shape_frozen?
+
+ assert_equal 3, tc.very_unique
+ assert_equal 3, Ractor.make_shareable(tc).very_unique
+ end;
+ end
+
def test_too_complex_obj_ivar_ractor_share
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
@@ -632,10 +730,10 @@ class TestShapes < Test::Unit::TestCase
r = Ractor.new do
o = Object.new
o.instance_variable_set(:@a, "hello")
- Ractor.yield(o)
+ o
end
- o = r.take
+ o = r.value
assert_equal "hello", o.instance_variable_get(:@a)
end;
end
@@ -650,10 +748,10 @@ class TestShapes < Test::Unit::TestCase
r = Ractor.new do
o = []
o.instance_variable_set(:@a, "hello")
- Ractor.yield(o)
+ o
end
- o = r.take
+ o = r.value
assert_equal "hello", o.instance_variable_get(:@a)
end;
end
@@ -721,9 +819,42 @@ class TestShapes < Test::Unit::TestCase
assert_equal 3, tc.a3_m # make sure IV is initialized
assert tc.instance_variable_defined?(:@a3)
tc.remove_instance_variable(:@a3)
+ refute tc.instance_variable_defined?(:@a3)
+ assert_nil tc.a3
+ end
+
+ def test_delete_iv_after_complex_and_object_id
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+
+ assert_equal 3, tc.a3_m # make sure IV is initialized
+ assert tc.instance_variable_defined?(:@a3)
+ tc.object_id
+ tc.remove_instance_variable(:@a3)
+ refute tc.instance_variable_defined?(:@a3)
assert_nil tc.a3
end
+ def test_delete_iv_after_complex_and_freeze
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+
+ assert_equal 3, tc.a3_m # make sure IV is initialized
+ assert tc.instance_variable_defined?(:@a3)
+ tc.freeze
+ assert_raise FrozenError do
+ tc.remove_instance_variable(:@a3)
+ end
+ assert tc.instance_variable_defined?(:@a3)
+ assert_equal 3, tc.a3
+ end
+
def test_delete_undefined_after_complex
ensure_complex
@@ -786,13 +917,15 @@ class TestShapes < Test::Unit::TestCase
def test_remove_instance_variable_capacity_transition
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
- t_object_shape = RubyVM::Shape.find_by_id(RubyVM::Shape::FIRST_T_OBJECT_SHAPE_ID)
- assert_equal(RubyVM::Shape::SHAPE_T_OBJECT, t_object_shape.type)
-
- initial_capacity = t_object_shape.capacity
# a does not transition in capacity
a = Class.new.new
+ root_shape = RubyVM::Shape.of(a)
+
+ assert_equal(RubyVM::Shape::SHAPE_ROOT, root_shape.type)
+ initial_capacity = root_shape.capacity
+ refute_equal(0, initial_capacity)
+
initial_capacity.times do |i|
a.instance_variable_set(:"@ivar#{i + 1}", i)
end
@@ -852,13 +985,13 @@ class TestShapes < Test::Unit::TestCase
def test_iv_index
example = RemoveAndAdd.new
initial_shape = RubyVM::Shape.of(example)
- assert_equal 0, initial_shape.next_iv_index
+ assert_equal 0, initial_shape.next_field_index
example.add_foo # makes a transition
add_foo_shape = RubyVM::Shape.of(example)
assert_equal([:@foo], example.instance_variables)
- assert_equal(initial_shape.id, add_foo_shape.parent.id)
- assert_equal(1, add_foo_shape.next_iv_index)
+ assert_equal(initial_shape.raw_id, add_foo_shape.parent.raw_id)
+ assert_equal(1, add_foo_shape.next_field_index)
example.remove_foo # makes a transition
remove_foo_shape = RubyVM::Shape.of(example)
@@ -868,8 +1001,8 @@ class TestShapes < Test::Unit::TestCase
example.add_bar # makes a transition
bar_shape = RubyVM::Shape.of(example)
assert_equal([:@bar], example.instance_variables)
- assert_equal(initial_shape.id, bar_shape.parent_id)
- assert_equal(1, bar_shape.next_iv_index)
+ assert_equal(initial_shape.raw_id, bar_shape.parent_id)
+ assert_equal(1, bar_shape.next_field_index)
end
def test_remove_then_add_again
@@ -888,7 +1021,7 @@ class TestShapes < Test::Unit::TestCase
def test_new_obj_has_t_object_shape
obj = TestObject.new
shape = RubyVM::Shape.of(obj)
- assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type
+ assert_equal RubyVM::Shape::SHAPE_ROOT, shape.type
assert_nil shape.parent
end
@@ -900,16 +1033,29 @@ class TestShapes < Test::Unit::TestCase
assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of([]))
end
- def test_true_has_special_const_shape_id
- assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(true).id)
- end
-
- def test_nil_has_special_const_shape_id
- assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(nil).id)
+ def test_raise_on_special_consts
+ assert_raise ArgumentError do
+ RubyVM::Shape.of(true)
+ end
+ assert_raise ArgumentError do
+ RubyVM::Shape.of(false)
+ end
+ assert_raise ArgumentError do
+ RubyVM::Shape.of(nil)
+ end
+ assert_raise ArgumentError do
+ RubyVM::Shape.of(0)
+ end
+ # 32-bit platforms don't have flonums or static symbols as special
+ # constants
+ # TODO(max): Add ArgumentError tests for symbol and flonum, skipping if
+ # RUBY_PLATFORM =~ /i686/
end
- def test_root_shape_transition_to_special_const_on_frozen
- assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of([].freeze).id)
+ def test_root_shape_frozen
+ frozen_root_shape = RubyVM::Shape.of([].freeze)
+ assert_predicate(frozen_root_shape, :frozen?)
+ assert_equal(RubyVM::Shape.root_shape.id, frozen_root_shape.raw_id)
end
def test_basic_shape_transition
@@ -920,7 +1066,7 @@ class TestShapes < Test::Unit::TestCase
assert_equal RubyVM::Shape::SHAPE_IVAR, shape.type
shape = shape.parent
- assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type
+ assert_equal RubyVM::Shape::SHAPE_ROOT, shape.type
assert_nil shape.parent
assert_equal(1, obj.instance_variable_get(:@a))
@@ -955,11 +1101,12 @@ class TestShapes < Test::Unit::TestCase
def test_freezing_and_duplicating_object
obj = Object.new.freeze
+ assert_predicate(RubyVM::Shape.of(obj), :shape_frozen?)
+
+ # dup'd objects shouldn't be frozen
obj2 = obj.dup
refute_predicate(obj2, :frozen?)
- # dup'd objects shouldn't be frozen, and the shape should be the
- # parent shape of the copied object
- assert_equal(RubyVM::Shape.of(obj).parent.id, RubyVM::Shape.of(obj2).id)
+ refute_predicate(RubyVM::Shape.of(obj2), :shape_frozen?)
end
def test_freezing_and_duplicating_object_with_ivars
@@ -976,6 +1123,7 @@ class TestShapes < Test::Unit::TestCase
str.freeze
str2 = str.dup
refute_predicate(str2, :frozen?)
+
refute_equal(RubyVM::Shape.of(str).id, RubyVM::Shape.of(str2).id)
assert_equal(str2.instance_variable_get(:@a), 1)
end
@@ -992,8 +1140,7 @@ class TestShapes < Test::Unit::TestCase
obj2 = obj.clone(freeze: true)
assert_predicate(obj2, :frozen?)
refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
- assert_equal(RubyVM::Shape::SHAPE_FROZEN, RubyVM::Shape.of(obj2).type)
- assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2).parent)
+ assert_predicate(RubyVM::Shape.of(obj2), :shape_frozen?)
end
def test_freezing_and_cloning_object_with_ivars
@@ -1036,4 +1183,30 @@ class TestShapes < Test::Unit::TestCase
tc.send("a#{_1}_m")
end
end
+
+ def assert_too_complex_during_delete(obj)
+ obj.instance_variable_set("@___#{SecureRandom.hex}", 1)
+
+ (RubyVM::Shape::SHAPE_MAX_VARIATIONS * 2).times do |i|
+ obj.instance_variable_set("@ivar#{i}", i)
+ end
+
+ refute_predicate RubyVM::Shape.of(obj), :too_complex?
+ (RubyVM::Shape::SHAPE_MAX_VARIATIONS * 2).times do |i|
+ obj.remove_instance_variable("@ivar#{i}")
+ end
+ assert_predicate RubyVM::Shape.of(obj), :too_complex?
+ end
+
+ def test_object_too_complex_during_delete
+ assert_too_complex_during_delete(Class.new.new)
+ end
+
+ def test_class_too_complex_during_delete
+ assert_too_complex_during_delete(Module.new)
+ end
+
+ def test_generic_too_complex_during_delete
+ assert_too_complex_during_delete(Class.new(Array).new)
+ end
end if defined?(RubyVM::Shape)
diff --git a/test/ruby/test_signal.rb b/test/ruby/test_signal.rb
index a2bdf02b88..091a66d5da 100644
--- a/test/ruby/test_signal.rb
+++ b/test/ruby/test_signal.rb
@@ -320,20 +320,20 @@ class TestSignal < Test::Unit::TestCase
# The parent should be notified about the stop
_, status = Process.waitpid2(child_pid, Process::WUNTRACED)
- assert status.stopped?
+ assert_predicate status, :stopped?
# It can be continued
Process.kill(:CONT, child_pid)
# And the child then runs to completion
_, status = Process.waitpid2(child_pid)
- assert status.exited?
- assert status.success?
+ assert_predicate status, :exited?
+ assert_predicate status, :success?
end
def test_sigwait_fd_unused
t = EnvUtil.apply_timeout_scale(0.1)
- assert_separately([], <<-End)
+ assert_ruby_status([], <<-End)
tgt = $$
trap(:TERM) { exit(0) }
e = "Process.daemon; sleep #{t * 2}; Process.kill(:TERM,\#{tgt})"
diff --git a/test/ruby/test_sleep.rb b/test/ruby/test_sleep.rb
index 991b73ebd5..7ef962db4a 100644
--- a/test/ruby/test_sleep.rb
+++ b/test/ruby/test_sleep.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: false
require 'test/unit'
require 'etc'
+require 'timeout'
class TestSleep < Test::Unit::TestCase
def test_sleep_5sec
@@ -13,4 +14,21 @@ class TestSleep < Test::Unit::TestCase
assert_operator(slept, :<=, 6.0, "[ruby-core:18015]: longer than expected")
end
end
+
+ def test_sleep_forever_not_woken_by_sigchld
+ begin
+ t = Thread.new do
+ sleep 0.5
+ `echo hello`
+ end
+
+ assert_raise Timeout::Error do
+ Timeout.timeout 2 do
+ sleep # Should block forever
+ end
+ end
+ ensure
+ t.join
+ end
+ end
end
diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb
index 1011967fe9..2458d38ef4 100644
--- a/test/ruby/test_string.rb
+++ b/test/ruby/test_string.rb
@@ -872,6 +872,10 @@ CODE
assert_equal('\#', S('"\\\\#"').undump)
assert_equal('\#{', S('"\\\\\#{"').undump)
+ assert_undump("\0\u{ABCD}")
+ assert_undump(S('"\x00\u3042"'.force_encoding("SJIS")))
+ assert_undump(S('"\u3042\x7E"'.force_encoding("SJIS")))
+
assert_raise(RuntimeError) { S('\u3042').undump }
assert_raise(RuntimeError) { S('"\x82\xA0\u3042"'.force_encoding("SJIS")).undump }
assert_raise(RuntimeError) { S('"\u3042\x82\xA0"'.force_encoding("SJIS")).undump }
@@ -1869,6 +1873,13 @@ CODE
result = []; S("aaa,bbb,ccc,ddd").split(/,/) {|s| result << s.gsub(/./, "A")}
assert_equal(["AAA"]*4, result)
+
+ s = S("abc ") * 20
+ assert_raise(RuntimeError) {
+ 10.times do
+ s.split {s.prepend("xxx" * 100)}
+ end
+ }
ensure
EnvUtil.suppress_warning {$; = fs}
end
@@ -1876,9 +1887,24 @@ CODE
def test_fs
return unless @cls == String
- assert_raise_with_message(TypeError, /\$;/) {
- $; = []
- }
+ begin
+ fs = $;
+ assert_deprecated_warning(/non-nil '\$;'/) {$; = "x"}
+ assert_raise_with_message(TypeError, /\$;/) {$; = []}
+ ensure
+ EnvUtil.suppress_warning {$; = fs}
+ end
+ name = "\u{5206 5217}"
+ assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}")
+ do;
+ alias $#{name} $;
+ assert_deprecated_warning(/\\$#{name}/) { $#{name} = "" }
+ assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 }
+ end;
+ end
+
+ def test_fs_gc
+ return unless @cls == String
assert_separately(%W[-W0], "#{<<~"begin;"}\n#{<<~'end;'}")
bug = '[ruby-core:79582] $; must not be GCed'
@@ -2027,6 +2053,117 @@ CODE
assert_equal(S("x") ,a)
end
+ def test_strip_with_selectors
+ assert_equal(S("abc"), S("---abc+++").strip("-+"))
+ assert_equal(S("abc"), S("+++abc---").strip("-+"))
+ assert_equal(S("abc"), S("+-+abc-+-").strip("-+"))
+ assert_equal(S(""), S("---+++").strip("-+"))
+ assert_equal(S("abc "), S("---abc ").strip("-"))
+ assert_equal(S(" abc"), S(" abc+++").strip("+"))
+
+ # Test with multibyte characters
+ assert_equal(S("abc"), S("あああabcいいい").strip("あい"))
+ assert_equal(S("abc"), S("いいいabcあああ").strip("あい"))
+
+ # Test with NUL characters
+ assert_equal(S("abc\0"), S("---abc\0--").strip("-"))
+ assert_equal(S("\0abc"), S("--\0abc---").strip("-"))
+
+ # Test without modification
+ assert_equal(S("abc"), S("abc").strip("-+"))
+ assert_equal(S("abc"), S("abc").strip(""))
+
+ # Test with range
+ assert_equal(S("abc"), S("012abc345").strip("0-9"))
+ assert_equal(S("abc"), S("012abc345").strip("^a-z"))
+
+ # Test with multiple selectors
+ assert_equal(S("4abc56"), S("01234abc56789").strip("0-9", "^4-6"))
+ end
+
+ def test_strip_bang_with_chars
+ a = S("---abc+++")
+ assert_equal(S("abc"), a.strip!("-+"))
+ assert_equal(S("abc"), a)
+
+ a = S("+++abc---")
+ assert_equal(S("abc"), a.strip!("-+"))
+ assert_equal(S("abc"), a)
+
+ a = S("abc")
+ assert_nil(a.strip!("-+"))
+ assert_equal(S("abc"), a)
+
+ # Test with multibyte characters
+ a = S("あああabcいいい")
+ assert_equal(S("abc"), a.strip!("あい"))
+ assert_equal(S("abc"), a)
+ end
+
+ def test_lstrip_with_selectors
+ assert_equal(S("abc+++"), S("---abc+++").lstrip("-"))
+ assert_equal(S("abc---"), S("+++abc---").lstrip("+"))
+ assert_equal(S("abc"), S("---abc").lstrip("-"))
+ assert_equal(S(""), S("---").lstrip("-"))
+
+ # Test with multibyte characters
+ assert_equal(S("abcいいい"), S("あああabcいいい").lstrip("あ"))
+
+ # Test with NUL characters
+ assert_equal(S("\0abc+++"), S("--\0abc+++").lstrip("-"))
+
+ # Test without modification
+ assert_equal(S("abc"), S("abc").lstrip("-"))
+
+ # Test with range
+ assert_equal(S("abc345"), S("012abc345").lstrip("0-9"))
+
+ # Test with multiple selectors
+ assert_equal(S("4abc56789"), S("01234abc56789").lstrip("0-9", "^4-6"))
+ end
+
+ def test_lstrip_bang_with_chars
+ a = S("---abc+++")
+ assert_equal(S("abc+++"), a.lstrip!("-"))
+ assert_equal(S("abc+++"), a)
+
+ a = S("abc")
+ assert_nil(a.lstrip!("-"))
+ assert_equal(S("abc"), a)
+ end
+
+ def test_rstrip_with_selectors
+ assert_equal(S("---abc"), S("---abc+++").rstrip("+"))
+ assert_equal(S("+++abc"), S("+++abc---").rstrip("-"))
+ assert_equal(S("abc"), S("abc+++").rstrip("+"))
+ assert_equal(S(""), S("+++").rstrip("+"))
+
+ # Test with multibyte characters
+ assert_equal(S("あああabc"), S("あああabcいいい").rstrip("い"))
+
+ # Test with NUL characters
+ assert_equal(S("---abc\0"), S("---abc\0++").rstrip("+"))
+
+ # Test without modification
+ assert_equal(S("abc"), S("abc").rstrip("-"))
+
+ # Test with range
+ assert_equal(S("012abc"), S("012abc345").rstrip("0-9"))
+
+ # Test with multiple selectors
+ assert_equal(S("01234abc56"), S("01234abc56789").rstrip("0-9", "^4-6"))
+ end
+
+ def test_rstrip_bang_with_chars
+ a = S("---abc+++")
+ assert_equal(S("---abc"), a.rstrip!("+"))
+ assert_equal(S("---abc"), a)
+
+ a = S("abc")
+ assert_nil(a.rstrip!("+"))
+ assert_equal(S("abc"), a)
+ end
+
def test_sub
assert_equal(S("h*llo"), S("hello").sub(/[aeiou]/, S('*')))
assert_equal(S("h<e>llo"), S("hello").sub(/([aeiou])/, S('<\1>')))
@@ -2462,33 +2599,7 @@ CODE
assert_equal([0xa9, 0x42, 0x2260], S("\xc2\xa9B\xe2\x89\xa0").unpack(S("U*")))
-=begin
- skipping "Not tested:
- D,d & double-precision float, native format\\
- E & double-precision float, little-endian byte order\\
- e & single-precision float, little-endian byte order\\
- F,f & single-precision float, native format\\
- G & double-precision float, network (big-endian) byte order\\
- g & single-precision float, network (big-endian) byte order\\
- I & unsigned integer\\
- i & integer\\
- L & unsigned long\\
- l & long\\
-
- m & string encoded in base64 (uuencoded)\\
- N & long, network (big-endian) byte order\\
- n & short, network (big-endian) byte-order\\
- P & pointer to a structure (fixed-length string)\\
- p & pointer to a null-terminated string\\
- S & unsigned short\\
- s & short\\
- V & long, little-endian byte order\\
- v & short, little-endian byte order\\
- X & back up a byte\\
- x & null byte\\
- Z & ASCII string (null padded, count is width)\\
-"
-=end
+ # more comprehensive tests are in test_pack.rb
end
def test_upcase
@@ -2780,14 +2891,21 @@ CODE
assert_equal([S("abcdb"), S("c"), S("e")], S("abcdbce").rpartition(/b\Kc/))
end
- def test_fs_setter
+ def test_rs
return unless @cls == String
- assert_raise(TypeError) { $/ = 1 }
+ begin
+ rs = $/
+ assert_deprecated_warning(/non-nil '\$\/'/) { $/ = "" }
+ assert_raise(TypeError) { $/ = 1 }
+ ensure
+ EnvUtil.suppress_warning { $/ = rs }
+ end
name = "\u{5206 884c}"
assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}")
do;
alias $#{name} $/
+ assert_deprecated_warning(/\\$#{name}/) { $#{name} = "" }
assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 }
end;
end
@@ -2838,27 +2956,45 @@ CODE
assert_equal("\u3042", ("\u3042" * 100)[-1])
end
-=begin
def test_compare_different_encoding_string
s1 = S("\xff".force_encoding("UTF-8"))
s2 = S("\xff".force_encoding("ISO-2022-JP"))
assert_equal([-1, 1], [s1 <=> s2, s2 <=> s1].sort)
+
+ s3 = S("あ".force_encoding("UTF-16LE"))
+ s4 = S("a".force_encoding("IBM437"))
+ assert_equal([-1, 1], [s3 <=> s4, s4 <=> s3].sort)
end
-=end
def test_casecmp
assert_equal(0, S("FoO").casecmp("fOO"))
assert_equal(1, S("FoO").casecmp("BaR"))
+ assert_equal(-1, S("foo").casecmp("FOOBAR"))
assert_equal(-1, S("baR").casecmp("FoO"))
assert_equal(1, S("\u3042B").casecmp("\u3042a"))
assert_equal(-1, S("foo").casecmp("foo\0"))
+ assert_equal(1, S("FOOBAR").casecmp("foo"))
+ assert_equal(0, S("foo\0bar").casecmp("FOO\0BAR"))
assert_nil(S("foo").casecmp(:foo))
assert_nil(S("foo").casecmp(Object.new))
+ assert_nil(S("foo").casecmp(0))
+ assert_nil(S("foo").casecmp(5.00))
+
o = Object.new
def o.to_str; "fOO"; end
assert_equal(0, S("FoO").casecmp(o))
+
+ assert_equal(0, S("#" * 128 + "A" * 256 + "b").casecmp("#" * 128 + "a" * 256 + "B"))
+ assert_equal(0, S("a" * 256 + "B").casecmp("A" * 256 + "b"))
+
+ assert_equal(-1, S("@").casecmp("`"))
+ assert_equal(0, S("hello\u00E9X").casecmp("HELLO\u00E9x"))
+
+ s1 = S("\xff".force_encoding("UTF-8"))
+ s2 = S("\xff".force_encoding("ISO-2022-JP"))
+ assert_nil(s1.casecmp(s2))
end
def test_casecmp?
@@ -2871,9 +3007,16 @@ CODE
assert_nil(S("foo").casecmp?(:foo))
assert_nil(S("foo").casecmp?(Object.new))
+ assert_nil(S("foo").casecmp(0))
+ assert_nil(S("foo").casecmp(5.00))
+
o = Object.new
def o.to_str; "fOO"; end
assert_equal(true, S("FoO").casecmp?(o))
+
+ s1 = S("\xff".force_encoding("UTF-8"))
+ s2 = S("\xff".force_encoding("ISO-2022-JP"))
+ assert_nil(s1.casecmp?(s2))
end
def test_upcase2
@@ -2946,7 +3089,6 @@ CODE
s5 = S("\u0000\u3042")
assert_equal("\u3042", s5.lstrip!)
assert_equal("\u3042", s5)
-
end
def test_delete_prefix_type_error
@@ -3246,18 +3388,12 @@ CODE
assert_equal('"\\u3042\\u3044\\u3046"', S("\u3042\u3044\u3046".encode(e)).inspect)
assert_equal('"ab\\"c"', S("ab\"c".encode(e)).inspect, bug4081)
end
- begin
- verbose, $VERBOSE = $VERBOSE, nil
- ext = Encoding.default_external
- Encoding.default_external = "us-ascii"
- $VERBOSE = verbose
+
+ EnvUtil.with_default_external(Encoding::US_ASCII) do
i = S("abc\"\\".force_encoding("utf-8")).inspect
- ensure
- $VERBOSE = nil
- Encoding.default_external = ext
- $VERBOSE = verbose
+
+ assert_equal('"abc\\"\\\\"', i, bug4081)
end
- assert_equal('"abc\\"\\\\"', i, bug4081)
end
def test_dummy_inspect
@@ -3314,6 +3450,11 @@ CODE
assert_equal(u("\x82")+("\u3042"*9), S("\u3042"*10).byteslice(2, 28))
+ assert_equal("\xE3", S("こんにちは").byteslice(0))
+ assert_equal("こんにちは", S("こんにちは").byteslice(0, 15))
+ assert_equal("こ", S("こんにちは").byteslice(0, 3))
+ assert_equal("は", S("こんにちは").byteslice(12, 15))
+
bug7954 = '[ruby-dev:47108]'
assert_equal(false, S("\u3042").byteslice(0, 2).valid_encoding?, bug7954)
assert_equal(false, ("\u3042"*10).byteslice(0, 20).valid_encoding?, bug7954)
@@ -3437,6 +3578,17 @@ CODE
assert_equal(false, str.frozen?)
end
+ def test_uminus_no_embed_gc
+ pad = "a"*2048
+ File.open(IO::NULL, "w") do |dev_null|
+ ("aa".."zz").each do |c|
+ fstr = -(c + pad).freeze
+ dev_null.write(fstr)
+ end
+ end
+ GC.start
+ end
+
def test_ord
assert_equal(97, S("a").ord)
assert_equal(97, S("abc").ord)
@@ -3743,6 +3895,96 @@ CODE
Warning[:deprecated] = deprecated
end
+ def test_encode_fallback_raise_memory_leak
+ {
+ "hash" => <<~RUBY,
+ fallback = Hash.new { raise MyError }
+ RUBY
+ "proc" => <<~RUBY,
+ fallback = proc { raise MyError }
+ RUBY
+ "method" => <<~RUBY,
+ def my_method(_str) = raise MyError
+ fallback = method(:my_method)
+ RUBY
+ "aref" => <<~RUBY,
+ fallback = Object.new
+ def fallback.[](_str) = raise MyError
+ RUBY
+ }.each do |type, code|
+ assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true)
+ class MyError < StandardError; end
+
+ #{code}
+
+ 100_000.times do |i|
+ "\\ufffd".encode(Encoding::US_ASCII, fallback:)
+ rescue MyError
+ end
+ RUBY
+ end
+ end
+
+ def test_encode_fallback_too_big_memory_leak
+ {
+ "hash" => <<~RUBY,
+ fallback = Hash.new { "\\uffee" }
+ RUBY
+ "proc" => <<~RUBY,
+ fallback = proc { "\\uffee" }
+ RUBY
+ "method" => <<~RUBY,
+ def my_method(_str) = "\\uffee"
+ fallback = method(:my_method)
+ RUBY
+ "aref" => <<~RUBY,
+ fallback = Object.new
+ def fallback.[](_str) = "\\uffee"
+ RUBY
+ }.each do |type, code|
+ assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true)
+ class MyError < StandardError; end
+
+ #{code}
+
+ 100_000.times do |i|
+ "\\ufffd".encode(Encoding::US_ASCII, fallback:)
+ rescue ArgumentError
+ end
+ RUBY
+ end
+ end
+
+ def test_encode_fallback_not_string_memory_leak
+ {
+ "hash" => <<~RUBY,
+ fallback = Hash.new { Object.new }
+ RUBY
+ "proc" => <<~RUBY,
+ fallback = proc { Object.new }
+ RUBY
+ "method" => <<~RUBY,
+ def my_method(_str) = Object.new
+ fallback = method(:my_method)
+ RUBY
+ "aref" => <<~RUBY,
+ fallback = Object.new
+ def fallback.[](_str) = Object.new
+ RUBY
+ }.each do |type, code|
+ assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true)
+ class MyError < StandardError; end
+
+ #{code}
+
+ 100_000.times do |i|
+ "\\ufffd".encode(Encoding::US_ASCII, fallback:)
+ rescue TypeError
+ end
+ RUBY
+ end
+ end
+
private
def assert_bytesplice_result(expected, s, *args)
@@ -3789,6 +4031,10 @@ CODE
def assert_byterindex(expected, string, match, *rest)
assert_index_like(:byterindex, expected, string, match, *rest)
end
+
+ def assert_undump(str, *rest)
+ assert_equal(str, str.dump.undump, *rest)
+ end
end
class TestString2 < TestString
diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb
index 3d727adf04..01e5cc68f6 100644
--- a/test/ruby/test_struct.rb
+++ b/test/ruby/test_struct.rb
@@ -535,6 +535,8 @@ module TestStruct
end
def test_named_structs_are_not_rooted
+ omit 'skip on riscv64-linux CI machine. See https://github.com/ruby/ruby/pull/13422' if ENV['RUBY_DEBUG'] == 'ci' && /riscv64-linux/ =~ RUBY_DESCRIPTION
+
# [Bug #20311]
assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
code = proc do
@@ -542,12 +544,18 @@ module TestStruct
Struct.send(:remove_const, :A)
end
- 1_000.times(&code)
+ 10_000.times(&code)
PREP
50_000.times(&code)
CODE
end
+ def test_frozen_subclass
+ test = Class.new(@Struct.new(:a)).freeze.new(a: 0)
+ assert_kind_of(@Struct, test)
+ assert_equal([:a], test.members)
+ end
+
class TopStruct < Test::Unit::TestCase
include TestStruct
diff --git a/test/ruby/test_super.rb b/test/ruby/test_super.rb
index 8e973b0f7f..25bad2242a 100644
--- a/test/ruby/test_super.rb
+++ b/test/ruby/test_super.rb
@@ -759,4 +759,19 @@ class TestSuper < Test::Unit::TestCase
inherited = inherited_class.new
assert_equal 2, inherited.test # it may read index=1 while it should be index=2
end
+
+ def test_super_in_basic_object
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class ::BasicObject
+ def no_super
+ super()
+ rescue ::NameError
+ :ok
+ end
+ end
+
+ assert_equal :ok, "[Bug #21694]".no_super
+ end;
+ end
end
diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb
index 62f1d99bdc..b355128a73 100644
--- a/test/ruby/test_syntax.rb
+++ b/test/ruby/test_syntax.rb
@@ -1259,6 +1259,52 @@ eom
assert_valid_syntax("a #\n#\n&.foo\n")
end
+ def test_fluent_and
+ assert_valid_syntax("a\n" "&& foo")
+ assert_valid_syntax("a\n" "and foo")
+
+ assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}"))
+ begin;
+ a = true
+ if a
+ && (a = :ok; true)
+ a
+ end
+ end;
+
+ assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}"))
+ begin;
+ a = true
+ if a
+ and (a = :ok; true)
+ a
+ end
+ end;
+ end
+
+ def test_fluent_or
+ assert_valid_syntax("a\n" "|| foo")
+ assert_valid_syntax("a\n" "or foo")
+
+ assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}"))
+ begin;
+ a = false
+ if a
+ || (a = :ok; true)
+ a
+ end
+ end;
+
+ assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}"))
+ begin;
+ a = false
+ if a
+ or (a = :ok; true)
+ a
+ end
+ end;
+ end
+
def test_safe_call_in_massign_lhs
assert_syntax_error("*a&.x=0", /multiple assignment destination/)
assert_syntax_error("a&.x,=0", /multiple assignment destination/)
@@ -1794,15 +1840,12 @@ eom
assert_equal("class ok", k.rescued("ok"))
assert_equal("instance ok", k.new.rescued("ok"))
- # Current technical limitation: cannot prepend "private" or something for command endless def
- error = /(syntax error,|\^~*) unexpected string literal/
- error2 = /(syntax error,|\^~*) unexpected local variable or method/
- assert_syntax_error('private def foo = puts "Hello"', error)
- assert_syntax_error('private def foo() = puts "Hello"', error)
- assert_syntax_error('private def foo(x) = puts x', error2)
- assert_syntax_error('private def obj.foo = puts "Hello"', error)
- assert_syntax_error('private def obj.foo() = puts "Hello"', error)
- assert_syntax_error('private def obj.foo(x) = puts x', error2)
+ assert_valid_syntax('private def foo = puts "Hello"')
+ assert_valid_syntax('private def foo() = puts "Hello"')
+ assert_valid_syntax('private def foo(x) = puts x')
+ assert_valid_syntax('private def obj.foo = puts "Hello"')
+ assert_valid_syntax('private def obj.foo() = puts "Hello"')
+ assert_valid_syntax('private def obj.foo(x) = puts x')
end
def test_methoddef_in_cond
@@ -1946,6 +1989,28 @@ eom
end
assert_valid_syntax('proc {def foo(_);end;it}')
assert_syntax_error('p { [it **2] }', /unexpected \*\*/)
+ assert_equal(1, eval('1.then { raise rescue it }'))
+ assert_equal(2, eval('1.then { 2.then { raise rescue it } }'))
+ assert_equal(3, eval('3.then { begin; raise; rescue; it; end }'))
+ assert_equal(4, eval('4.tap { begin; raise ; rescue; raise rescue it; end; }'))
+ assert_equal(5, eval('a = 0; 5.then { begin; nil; ensure; a = it; end }; a'))
+ assert_equal(6, eval('a = 0; 6.then { begin; nil; rescue; ensure; a = it; end }; a'))
+ assert_equal(7, eval('a = 0; 7.then { begin; raise; ensure; a = it; end } rescue a'))
+ assert_equal(8, eval('a = 0; 8.then { begin; raise; rescue; ensure; a = it; end }; a'))
+ assert_equal(/9/, eval('9.then { /#{it}/o }'))
+ end
+
+ def test_it_with_splat_super_method
+ bug21256 = '[ruby-core:121592] [Bug #21256]'
+
+ a = Class.new do
+ define_method(:foo) { it }
+ end
+ b = Class.new(a) do
+ def foo(*args) = super
+ end
+
+ assert_equal(1, b.new.foo(1), bug21256)
end
def test_value_expr_in_condition
@@ -2018,10 +2083,11 @@ eom
end
obj4 = obj1.clone
obj5 = obj1.clone
+ obj6 = obj1.clone
obj1.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__)
- obj1.instance_eval('def foo(...) eval("bar(...)") end', __FILE__, __LINE__)
obj4.instance_eval("def foo ...\n bar(...)\n""end", __FILE__, __LINE__)
obj5.instance_eval("def foo ...; bar(...); end", __FILE__, __LINE__)
+ obj6.instance_eval('def foo(...) eval("bar(...)") end', __FILE__, __LINE__)
klass = Class.new {
def foo(*args, **kws, &block)
@@ -2050,7 +2116,7 @@ eom
end
obj3.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__)
- [obj1, obj2, obj3, obj4, obj5].each do |obj|
+ [obj1, obj2, obj3, obj4, obj5, obj6].each do |obj|
assert_warning('') {
assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5) {|*x| x})
}
@@ -2230,13 +2296,13 @@ eom
end
def test_class_module_Object_ancestors
- assert_separately([], <<-RUBY)
+ assert_ruby_status([], <<-RUBY)
m = Module.new
m::Bug18832 = 1
include m
class Bug18832; end
RUBY
- assert_separately([], <<-RUBY)
+ assert_ruby_status([], <<-RUBY)
m = Module.new
m::Bug18832 = 1
include m
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb
index 12ba1165ed..2a61fc3450 100644
--- a/test/ruby/test_thread.rb
+++ b/test/ruby/test_thread.rb
@@ -1480,6 +1480,8 @@ q.pop
end
def test_thread_interrupt_for_killed_thread
+ pend "hang-up" if /mswin|mingw/ =~ RUBY_PLATFORM
+
opts = { timeout: 5, timeout_error: nil }
assert_normal_exit(<<-_end, '[Bug #8996]', **opts)
@@ -1589,4 +1591,65 @@ q.pop
frame_for_deadlock_test_2 { t.join }
INPUT
end
+
+ # [Bug #21342]
+ def test_unlock_locked_mutex_with_collected_fiber
+ bug21127 = '[ruby-core:120930] [Bug #21127]'
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ 5.times do
+ m = Mutex.new
+ Thread.new do
+ m.synchronize do
+ end
+ end.join
+ Fiber.new do
+ GC.start
+ m.lock
+ end.resume
+ end
+ end;
+ end
+
+ def test_unlock_locked_mutex_with_collected_fiber2
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ MUTEXES = []
+ 5.times do
+ m = Mutex.new
+ Fiber.new do
+ GC.start
+ m.lock
+ end.resume
+ MUTEXES << m
+ end
+ 10.times do
+ MUTEXES.clear
+ GC.start
+ end
+ end;
+ end
+
+ def test_mutexes_locked_in_fiber_dont_have_aba_issue_with_new_fibers
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ mutexes = 1000.times.map do
+ Mutex.new
+ end
+
+ mutexes.map do |m|
+ Fiber.new do
+ m.lock
+ end.resume
+ end
+
+ GC.start
+
+ 1000.times.map do
+ Fiber.new do
+ raise "FAILED!" if mutexes.any?(&:owned?)
+ end.resume
+ end
+ end;
+ end
end
diff --git a/test/ruby/test_thread_cv.rb b/test/ruby/test_thread_cv.rb
index eb88b9606c..81201f134f 100644
--- a/test/ruby/test_thread_cv.rb
+++ b/test/ruby/test_thread_cv.rb
@@ -76,7 +76,7 @@ class TestThreadConditionVariable < Test::Unit::TestCase
condvar.broadcast
result << "P2"
end
- Timeout.timeout(5) do
+ Timeout.timeout(60) do
nr_threads.times do |i|
threads[i].join
end
diff --git a/test/ruby/test_thread_queue.rb b/test/ruby/test_thread_queue.rb
index 545bf98888..9a41be8b1a 100644
--- a/test/ruby/test_thread_queue.rb
+++ b/test/ruby/test_thread_queue.rb
@@ -235,8 +235,14 @@ class TestThreadQueue < Test::Unit::TestCase
end
_eom
rescue Timeout::Error
+ # record load average:
+ uptime = `uptime` rescue nil
+ if uptime && /(load average: [\d.]+),/ =~ uptime
+ la = " (#{$1})"
+ end
+
count = File.read("#{d}/test_thr_kill_count").to_i
- flunk "only #{count}/#{total_count} done in #{timeout} seconds."
+ flunk "only #{count}/#{total_count} done in #{timeout} seconds.#{la}"
end
}
end
@@ -373,7 +379,7 @@ class TestThreadQueue < Test::Unit::TestCase
assert_equal false, q.closed?
q << :something
assert_equal q, q.close
- assert q.closed?
+ assert_predicate q, :closed?
assert_raise_with_message(ClosedQueueError, /closed/){q << :nothing}
assert_equal q.pop, :something
assert_nil q.pop
@@ -427,7 +433,7 @@ class TestThreadQueue < Test::Unit::TestCase
assert_equal 1, q.size
assert_equal :one, q.pop
- assert q.empty?, "queue not empty"
+ assert_empty q
end
# make sure that shutdown state is handled properly by empty? for the non-blocking case
@@ -561,7 +567,7 @@ class TestThreadQueue < Test::Unit::TestCase
assert_equal 0, q.size
assert_equal 3, ary.size
- ary.each{|e| assert [0,1,2,3,4,5].include?(e)}
+ ary.each{|e| assert_include [0,1,2,3,4,5], e}
assert_nil q.pop
prod_threads.each{|t|
diff --git a/test/ruby/test_time_tz.rb b/test/ruby/test_time_tz.rb
index f66cd9bec2..473c3cabcb 100644
--- a/test/ruby/test_time_tz.rb
+++ b/test/ruby/test_time_tz.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: false
require 'test/unit'
-require '-test-/time'
class TestTimeTZ < Test::Unit::TestCase
has_right_tz = true
diff --git a/test/ruby/test_transcode.rb b/test/ruby/test_transcode.rb
index 63d37f4ba4..99b5ee8d43 100644
--- a/test/ruby/test_transcode.rb
+++ b/test/ruby/test_transcode.rb
@@ -2320,6 +2320,93 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("A\nB\nC", s.encode(usascii, newline: :lf))
end
+ def test_ractor_lazy_load_encoding
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}", timeout: 60)
+ begin;
+ rs = []
+ autoload_encodings = Encoding.list.select { |e| e.inspect.include?("(autoload)") }.freeze
+ 7.times do
+ rs << Ractor.new(autoload_encodings) do |encodings|
+ str = "\u0300"
+ encodings.each do |enc|
+ str.encode(enc) rescue Encoding::UndefinedConversionError
+ end
+ end
+ end
+
+ while rs.any?
+ r, _obj = Ractor.select(*rs)
+ rs.delete(r)
+ end
+ assert_empty rs
+ end;
+ end
+
+ def test_ractor_lazy_load_encoding_random
+ omit 'unstable on s390x and windows' if RUBY_PLATFORM =~ /s390x|mswin/
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ rs = []
+ 100.times do
+ rs << Ractor.new do
+ "\u0300".encode(Encoding.list.sample) rescue Encoding::UndefinedConversionError
+ end
+ end
+
+ while rs.any?
+ r, _obj = Ractor.select(*rs)
+ rs.delete(r)
+ end
+ assert_empty rs
+ end;
+ end
+
+ def test_ractor_asciicompat_encoding_exists
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ rs = []
+ 7.times do
+ rs << Ractor.new do
+ string = "ISO-2022-JP"
+ encoding = Encoding.find(string)
+ 20_000.times do
+ Encoding::Converter.asciicompat_encoding(string)
+ Encoding::Converter.asciicompat_encoding(encoding)
+ end
+ end
+ end
+
+ while rs.any?
+ r, _obj = Ractor.select(*rs)
+ rs.delete(r)
+ end
+ assert_empty rs
+ end;
+ end
+
+ def test_ractor_asciicompat_encoding_doesnt_exist
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}", timeout: 60)
+ begin;
+ rs = []
+ NO_EXIST = "I".freeze
+ 7.times do
+ rs << Ractor.new do
+ 50.times do
+ if (val = Encoding::Converter.asciicompat_encoding(NO_EXIST))
+ raise "Got #{val}, expected nil"
+ end
+ end
+ end
+ end
+
+ while rs.any?
+ r, _obj = Ractor.select(*rs)
+ rs.delete(r)
+ end
+ assert_empty rs
+ end;
+ end
+
private
def assert_conversion_both_ways_utf8(utf8, raw, encoding)
diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb
index 49fec2d40e..13b8a7905f 100644
--- a/test/ruby/test_variable.rb
+++ b/test/ruby/test_variable.rb
@@ -388,6 +388,61 @@ class TestVariable < Test::Unit::TestCase
end
end
+ class RemoveIvar
+ class << self
+ attr_reader :ivar
+
+ def add_ivar
+ @ivar = 1
+ end
+ end
+
+ attr_reader :ivar
+
+ def add_ivar
+ @ivar = 1
+ end
+ end
+
+ def add_and_remove_ivar(obj)
+ assert_nil obj.ivar
+ assert_equal 1, obj.add_ivar
+ assert_equal 1, obj.instance_variable_get(:@ivar)
+ assert_equal 1, obj.ivar
+
+ obj.remove_instance_variable(:@ivar)
+ assert_nil obj.ivar
+
+ assert_raise NameError do
+ obj.remove_instance_variable(:@ivar)
+ end
+ end
+
+ def test_remove_instance_variables_object
+ obj = RemoveIvar.new
+ add_and_remove_ivar(obj)
+ add_and_remove_ivar(obj)
+ end
+
+ def test_remove_instance_variables_class
+ add_and_remove_ivar(RemoveIvar)
+ add_and_remove_ivar(RemoveIvar)
+ end
+
+ class RemoveIvarGeneric < Array
+ attr_reader :ivar
+
+ def add_ivar
+ @ivar = 1
+ end
+ end
+
+ def test_remove_instance_variables_generic
+ obj = RemoveIvarGeneric.new
+ add_and_remove_ivar(obj)
+ add_and_remove_ivar(obj)
+ end
+
class ExIvar < Hash
def initialize
@a = 1
@@ -407,6 +462,21 @@ class TestVariable < Test::Unit::TestCase
}
end
+ def test_exivar_resize_with_compaction_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ objs = 10_000.times.map do
+ ExIvar.new
+ end
+ EnvUtil.under_gc_compact_stress do
+ 10.times do
+ x = ExIvar.new
+ x.instance_variable_set(:@resize, 1)
+ x
+ end
+ end
+ objs or flunk
+ end
+
def test_local_variables_with_kwarg
bug11674 = '[ruby-core:71437] [Bug #11674]'
v = with_kwargs_11(v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8,v9:9,v10:10,v11:11)
@@ -426,12 +496,55 @@ class TestVariable < Test::Unit::TestCase
end
def test_local_variables_encoding
- α = 1
+ α = 1 or flunk
b = binding
b.eval("".encode("us-ascii"))
assert_equal(%i[α b], b.local_variables)
end
+ def test_genivar_cache
+ bug21547 = '[Bug #21547]'
+ klass = Class.new(Array)
+ instance = klass.new
+ instance.instance_variable_set(:@a1, 1)
+ instance.instance_variable_set(:@a2, 2)
+ Fiber.new do
+ instance.instance_variable_set(:@a3, 3)
+ instance.instance_variable_set(:@a4, 4)
+ end.resume
+ assert_equal 4, instance.instance_variable_get(:@a4), bug21547
+ end
+
+ def test_genivar_cache_free
+ str = +"hello"
+ str.instance_variable_set(:@x, :old_value)
+
+ str.instance_variable_get(:@x) # populate cache
+
+ Fiber.new {
+ str.remove_instance_variable(:@x)
+ str.instance_variable_set(:@x, :new_value)
+ }.resume
+
+ assert_equal :new_value, str.instance_variable_get(:@x)
+ end
+
+ def test_genivar_cache_invalidated_by_gc
+ str = +"hello"
+ str.instance_variable_set(:@x, :old_value)
+
+ str.instance_variable_get(:@x) # populate cache
+
+ Fiber.new {
+ str.remove_instance_variable(:@x)
+ str.instance_variable_set(:@x, :new_value)
+ }.resume
+
+ GC.start
+
+ assert_equal :new_value, str.instance_variable_get(:@x)
+ end
+
private
def with_kwargs_11(v1:, v2:, v3:, v4:, v5:, v6:, v7:, v8:, v9:, v10:, v11:)
local_variables
diff --git a/test/ruby/test_vm_dump.rb b/test/ruby/test_vm_dump.rb
index 709fd5eadf..d183e03391 100644
--- a/test/ruby/test_vm_dump.rb
+++ b/test/ruby/test_vm_dump.rb
@@ -5,8 +5,7 @@ return unless /darwin/ =~ RUBY_PLATFORM
class TestVMDump < Test::Unit::TestCase
def assert_darwin_vm_dump_works(args, timeout=nil)
- pend "macOS 15 is not working with this assertion" if macos?(15)
-
+ args.unshift({"RUBY_ON_BUG" => nil, "RUBY_CRASH_REPORT" => nil})
assert_in_out_err(args, "", [], /^\[IMPORTANT\]/, timeout: timeout || 300)
end
@@ -15,7 +14,7 @@ class TestVMDump < Test::Unit::TestCase
end
def test_darwin_segv_in_syscall
- assert_darwin_vm_dump_works('-e1.times{Process.kill :SEGV,$$}')
+ assert_darwin_vm_dump_works(['-e1.times{Process.kill :SEGV,$$}'])
end
def test_darwin_invalid_access
diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb
index a2904776bc..4f5823ecf4 100644
--- a/test/ruby/test_weakmap.rb
+++ b/test/ruby/test_weakmap.rb
@@ -203,7 +203,7 @@ class TestWeakMap < Test::Unit::TestCase
@wm[i] = obj
end
- assert_separately([], <<-'end;')
+ assert_ruby_status([], <<-'end;')
wm = ObjectSpace::WeakMap.new
obj = Object.new
100.times do
@@ -224,7 +224,7 @@ class TestWeakMap < Test::Unit::TestCase
assert_equal(val, wm[key])
end;
- assert_separately(["-W0"], <<-'end;')
+ assert_ruby_status(["-W0"], <<-'end;')
wm = ObjectSpace::WeakMap.new
ary = 10_000.times.map do
@@ -265,4 +265,27 @@ class TestWeakMap < Test::Unit::TestCase
10_000.times { weakmap[Object.new] = Object.new }
RUBY
end
+
+ def test_generational_gc
+ EnvUtil.without_gc do
+ wmap = ObjectSpace::WeakMap.new
+
+ (GC::INTERNAL_CONSTANTS[:RVALUE_OLD_AGE] - 1).times { GC.start }
+
+ retain = []
+ 50.times do
+ k = Object.new
+ wmap[k] = true
+ retain << k
+ end
+
+ GC.start # WeakMap promoted, other objects still young
+
+ retain.clear
+
+ GC.start(full_mark: false)
+
+ wmap.keys.each(&:itself) # call method on keys to cause crash
+ end
+ end
end
diff --git a/test/ruby/test_yield.rb b/test/ruby/test_yield.rb
index 9b2b2f37e0..e7e65fce9e 100644
--- a/test/ruby/test_yield.rb
+++ b/test/ruby/test_yield.rb
@@ -401,7 +401,7 @@ class TestRubyYieldGen < Test::Unit::TestCase
def test_block_cached_argc
# [Bug #11451]
- assert_separately([], <<-"end;")
+ assert_ruby_status([], <<-"end;")
class Yielder
def each
yield :x, :y, :z
diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb
index 7c0524354b..d6b9b75648 100644
--- a/test/ruby/test_yjit.rb
+++ b/test/ruby/test_yjit.rb
@@ -133,7 +133,7 @@ class TestYJIT < Test::Unit::TestCase
end
def test_yjit_enable_with_monkey_patch
- assert_separately(%w[--yjit-disable], <<~RUBY)
+ assert_ruby_status(%w[--yjit-disable], <<~RUBY)
# This lets rb_method_entry_at(rb_mKernel, ...) return NULL
Kernel.prepend(Module.new)
@@ -166,6 +166,11 @@ class TestYJIT < Test::Unit::TestCase
end
end
+ if JITSupport.zjit_supported?
+ def test_yjit_enable_with_zjit_enabled
+ assert_in_out_err(['--zjit'], 'puts RubyVM::YJIT.enable', ['false'], ['Only one JIT can be enabled at the same time.'])
+ end
+ end
def test_yjit_stats_and_v_no_error
_stdout, stderr, _status = invoke_ruby(%w(-v --yjit-stats), '', true, true)
@@ -652,6 +657,26 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_struct_aset_guards_recv_is_not_frozen
+ assert_compiles(<<~RUBY, result: :ok, exits: { opt_send_without_block: 1 })
+ def foo(obj)
+ obj.foo = 123
+ end
+
+ Foo = Struct.new(:foo)
+ obj = Foo.new(123)
+ 100.times do
+ foo(obj)
+ end
+ obj.freeze
+ begin
+ foo(obj)
+ rescue FrozenError
+ :ok
+ end
+ RUBY
+ end
+
def test_getblockparam
assert_compiles(<<~'RUBY', insns: [:getblockparam])
def foo &blk
@@ -1529,14 +1554,6 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
- def test_opt_aref_with
- assert_compiles(<<~RUBY, insns: %i[opt_aref_with], result: "bar", frozen_string_literal: false)
- h = {"foo" => "bar"}
-
- h["foo"]
- RUBY
- end
-
def test_proc_block_arg
assert_compiles(<<~RUBY, result: [:proc, :no_block])
def yield_if_given = block_given? ? yield : :no_block
@@ -1775,6 +1792,30 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_proc_block_with_kwrest
+ # When the bug was present this required --yjit-stats to trigger.
+ assert_compiles(<<~RUBY, result: {extra: 5})
+ def foo = bar(w: 1, x: 2, y: 3, z: 4, extra: 5, &proc { _1 })
+ def bar(w:, x:, y:, z:, **kwrest) = yield kwrest
+
+ GC.stress = true
+ foo
+ foo
+ RUBY
+ end
+
+ def test_yjit_dump_insns
+ # Testing that this undocumented debugging feature doesn't crash
+ args = [
+ '--yjit-call-threshold=1',
+ '--yjit-dump-insns',
+ '-e def foo(case:) = {case:}[:case]',
+ '-e foo(case:0)',
+ ]
+ _out, _err, status = invoke_ruby(args, '', true, true)
+ assert_not_predicate(status, :signaled?)
+ end
+
private
def code_gc_helpers
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
index 5336c6cc47..ad2df806d5 100644
--- a/test/ruby/test_zjit.rb
+++ b/test/ruby/test_zjit.rb
@@ -9,6 +9,109 @@ require_relative '../lib/jit_support'
return unless JITSupport.zjit_supported?
class TestZJIT < Test::Unit::TestCase
+ def test_enabled
+ assert_runs 'false', <<~RUBY, zjit: false
+ RubyVM::ZJIT.enabled?
+ RUBY
+ assert_runs 'true', <<~RUBY, zjit: true
+ RubyVM::ZJIT.enabled?
+ RUBY
+ end
+
+ def test_stats_enabled
+ assert_runs 'false', <<~RUBY, stats: false
+ RubyVM::ZJIT.stats_enabled?
+ RUBY
+ assert_runs 'true', <<~RUBY, stats: true
+ RubyVM::ZJIT.stats_enabled?
+ RUBY
+ end
+
+ def test_stats_quiet
+ # Test that --zjit-stats-quiet collects stats but doesn't print them
+ script = <<~RUBY
+ def test = 42
+ test
+ test
+ puts RubyVM::ZJIT.stats_enabled?
+ RUBY
+
+ stats_header = "***ZJIT: Printing ZJIT statistics on exit***"
+
+ # With --zjit-stats, stats should be printed to stderr
+ out, err, status = eval_with_jit(script, stats: true)
+ assert_success(out, err, status)
+ assert_includes(err, stats_header)
+ assert_equal("true\n", out)
+
+ # With --zjit-stats-quiet, stats should NOT be printed but still enabled
+ out, err, status = eval_with_jit(script, stats: :quiet)
+ assert_success(out, err, status)
+ refute_includes(err, stats_header)
+ assert_equal("true\n", out)
+
+ # With --zjit-stats=<path>, stats should be printed to the path
+ Tempfile.create("zjit-stats-") {|tmp|
+ stats_file = tmp.path
+ tmp.puts("Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...")
+ tmp.close
+
+ out, err, status = eval_with_jit(script, stats: stats_file)
+ assert_success(out, err, status)
+ refute_includes(err, stats_header)
+ assert_equal("true\n", out)
+ assert_equal stats_header, File.open(stats_file) {|f| f.gets(chomp: true)}, "should be overwritten"
+ }
+ end
+
+ def test_enable_through_env
+ child_env = {'RUBY_YJIT_ENABLE' => nil, 'RUBY_ZJIT_ENABLE' => '1'}
+ assert_in_out_err([child_env, '-v'], '') do |stdout, stderr|
+ assert_includes(stdout.first, '+ZJIT')
+ assert_equal([], stderr)
+ end
+ end
+
+ def test_zjit_enable
+ # --disable-all is important in case the build/environment has YJIT enabled by
+ # default through e.g. -DYJIT_FORCE_ENABLE. Can't enable ZJIT when YJIT is on.
+ assert_separately(["--disable-all"], <<~'RUBY')
+ refute_predicate RubyVM::ZJIT, :enabled?
+ refute_predicate RubyVM::ZJIT, :stats_enabled?
+ refute_includes RUBY_DESCRIPTION, "+ZJIT"
+
+ RubyVM::ZJIT.enable
+
+ assert_predicate RubyVM::ZJIT, :enabled?
+ refute_predicate RubyVM::ZJIT, :stats_enabled?
+ assert_includes RUBY_DESCRIPTION, "+ZJIT"
+ RUBY
+ end
+
+ def test_zjit_disable
+ assert_separately(["--zjit", "--zjit-disable"], <<~'RUBY')
+ refute_predicate RubyVM::ZJIT, :enabled?
+ refute_includes RUBY_DESCRIPTION, "+ZJIT"
+
+ RubyVM::ZJIT.enable
+
+ assert_predicate RubyVM::ZJIT, :enabled?
+ assert_includes RUBY_DESCRIPTION, "+ZJIT"
+ RUBY
+ end
+
+ def test_zjit_enable_respects_existing_options
+ assert_separately(['--zjit-disable', '--zjit-stats-quiet'], <<~RUBY)
+ refute_predicate RubyVM::ZJIT, :enabled?
+ assert_predicate RubyVM::ZJIT, :stats_enabled?
+
+ RubyVM::ZJIT.enable
+
+ assert_predicate RubyVM::ZJIT, :enabled?
+ assert_predicate RubyVM::ZJIT, :stats_enabled?
+ RUBY
+ end
+
def test_call_itself
assert_compiles '42', <<~RUBY, call_threshold: 2
def test = 42.itself
@@ -31,6 +134,20 @@ class TestZJIT < Test::Unit::TestCase
}
end
+ def test_putstring
+ assert_compiles '""', %q{
+ def test = "#{""}"
+ test
+ }, insns: [:putstring]
+ end
+
+ def test_putchilldedstring
+ assert_compiles '""', %q{
+ def test = ""
+ test
+ }, insns: [:putchilledstring]
+ end
+
def test_leave_param
assert_compiles '5', %q{
def test(n) = n
@@ -38,6 +155,194 @@ class TestZJIT < Test::Unit::TestCase
}
end
+ def test_getglobal_with_warning
+ assert_compiles('"rescued"', %q{
+ Warning[:deprecated] = true
+
+ module Warning
+ def warn(message)
+ raise
+ end
+ end
+
+ def test
+ $=
+ rescue
+ "rescued"
+ end
+
+ $VERBOSE = true
+ test
+ }, insns: [:getglobal])
+ end
+
+ def test_setglobal
+ assert_compiles '1', %q{
+ def test
+ $a = 1
+ $a
+ end
+
+ test
+ }, insns: [:setglobal]
+ end
+
+ def test_string_intern
+ assert_compiles ':foo123', %q{
+ def test
+ :"foo#{123}"
+ end
+
+ test
+ }, insns: [:intern]
+ end
+
+ def test_duphash
+ assert_compiles '{a: 1}', %q{
+ def test
+ {a: 1}
+ end
+
+ test
+ }, insns: [:duphash]
+ end
+
+ def test_pushtoarray
+ assert_compiles '[1, 2, 3]', %q{
+ def test
+ [*[], 1, 2, 3]
+ end
+ test
+ }, insns: [:pushtoarray]
+ end
+
+ def test_splatarray_new_array
+ assert_compiles '[1, 2, 3]', %q{
+ def test a
+ [*a, 3]
+ end
+ test [1, 2]
+ }, insns: [:splatarray]
+ end
+
+ def test_splatarray_existing_array
+ assert_compiles '[1, 2, 3]', %q{
+ def foo v
+ [1, 2, v]
+ end
+ def test a
+ foo(*a)
+ end
+ test [3]
+ }, insns: [:splatarray]
+ end
+
+ def test_concattoarray
+ assert_compiles '[1, 2, 3]', %q{
+ def test(*a)
+ [1, 2, *a]
+ end
+ test 3
+ }, insns: [:concattoarray]
+ end
+
+ def test_definedivar
+ assert_compiles '[nil, "instance-variable", nil]', %q{
+ def test
+ v0 = defined?(@a)
+ @a = nil
+ v1 = defined?(@a)
+ remove_instance_variable :@a
+ v2 = defined?(@a)
+ [v0, v1, v2]
+ end
+ test
+ }, insns: [:definedivar]
+ end
+
+ def test_setglobal_with_trace_var_exception
+ assert_compiles '"rescued"', %q{
+ def test
+ $a = 1
+ rescue
+ "rescued"
+ end
+
+ trace_var(:$a) { raise }
+ test
+ }, insns: [:setglobal]
+ end
+
+ def test_toplevel_binding
+ # Not using assert_compiles, which doesn't use the toplevel frame for `test_script`.
+ out, err, status = eval_with_jit(%q{
+ a = 1
+ b = 2
+ TOPLEVEL_BINDING.local_variable_set(:b, 3)
+ c = 4
+ print [a, b, c]
+ })
+ assert_success(out, err, status)
+ assert_equal "[1, 3, 4]", out
+ end
+
+ def test_toplevel_local_after_eval
+ # Not using assert_compiles, which doesn't use the toplevel frame for `test_script`.
+ out, err, status = eval_with_jit(%q{
+ a = 1
+ b = 2
+ eval('b = 3')
+ c = 4
+ print [a, b, c]
+ })
+ assert_success(out, err, status)
+ assert_equal "[1, 3, 4]", out
+ end
+
+ def test_getlocal_after_eval
+ assert_compiles '2', %q{
+ def test
+ a = 1
+ eval('a = 2')
+ a
+ end
+ test
+ }
+ end
+
+ def test_getlocal_after_instance_eval
+ assert_compiles '2', %q{
+ def test
+ a = 1
+ instance_eval('a = 2')
+ a
+ end
+ test
+ }
+ end
+
+ def test_getlocal_after_module_eval
+ assert_compiles '2', %q{
+ def test
+ a = 1
+ Kernel.module_eval('a = 2')
+ a
+ end
+ test
+ }
+ end
+
+ def test_getlocal_after_class_eval
+ assert_compiles '2', %q{
+ def test
+ a = 1
+ Kernel.class_eval('a = 2')
+ a
+ end
+ test
+ }
+ end
+
def test_setlocal
assert_compiles '3', %q{
def test(n)
@@ -48,6 +353,247 @@ class TestZJIT < Test::Unit::TestCase
}
end
+ def test_return_nonparam_local
+ # Use dead code (if false) to create a local without initialization instructions.
+ assert_compiles 'nil', %q{
+ def foo(a)
+ if false
+ x = nil
+ end
+ x
+ end
+ def test = foo(1)
+ test
+ test
+ }, call_threshold: 2
+ end
+
+ def test_nonparam_local_nil_in_jit_call
+ # Non-parameter locals must be initialized to nil in JIT-to-JIT calls.
+ # Use dead code (if false) to create locals without initialization instructions.
+ # Then eval a string that accesses the uninitialized locals.
+ assert_compiles '["x", "x", "x", "x"]', %q{
+ def f(a)
+ a ||= 1
+ if false; b = 1; end
+ eval("-> { p 'x#{b}' }")
+ end
+
+ 4.times.map { f(1).call }
+ }, call_threshold: 2
+ end
+
+ def test_kwargs_with_exit_and_local_invalidation
+ assert_compiles ':ok', %q{
+ def a(b:, c:)
+ if c == :b
+ return -> {}
+ end
+ Class # invalidate locals
+
+ raise "c is :b!" if c == :b
+ end
+
+ def test
+ # note opposite order of kwargs
+ a(c: :c, b: :b)
+ end
+
+ 4.times { test }
+ :ok
+ }, call_threshold: 2
+ end
+
+ def test_kwargs_with_max_direct_send_arg_count
+ # Ensure that we only reorder the args when we _can_ use direct send (< 6 args).
+ assert_compiles '[[1, 2, 3, 4, 5, 6, 7, 8]]', %q{
+ def kwargs(five, six, a:, b:, c:, d:, e:, f:)
+ [a, b, c, d, five, six, e, f]
+ end
+
+ 5.times.flat_map do
+ [
+ kwargs(5, 6, d: 4, c: 3, a: 1, b: 2, e: 7, f: 8),
+ kwargs(5, 6, d: 4, c: 3, b: 2, a: 1, e: 7, f: 8)
+ ]
+ end.uniq
+ }, call_threshold: 2
+ end
+
+ def test_setlocal_on_eval
+ assert_compiles '1', %q{
+ @b = binding
+ eval('a = 1', @b)
+ eval('a', @b)
+ }
+ end
+
+ def test_optional_arguments
+ assert_compiles '[[1, 2, 3], [10, 20, 3], [100, 200, 300]]', %q{
+ def test(a, b = 2, c = 3)
+ [a, b, c]
+ end
+ [test(1), test(10, 20), test(100, 200, 300)]
+ }
+ end
+
+ def test_optional_arguments_setlocal
+ assert_compiles '[[2, 2], [1, nil]]', %q{
+ def test(a = (b = 2))
+ [a, b]
+ end
+ [test, test(1)]
+ }
+ end
+
+ def test_optional_arguments_cyclic
+ assert_compiles '[nil, 1]', %q{
+ test = proc { |a=a| a }
+ [test.call, test.call(1)]
+ }
+ end
+
+ def test_optional_arguments_side_exit
+ # This leads to FailedOptionalArguments, so not using assert_compiles
+ assert_runs '[:foo, nil, 1]', %q{
+ def test(a = (def foo = nil)) = a
+ [test, (undef :foo), test(1)]
+ }
+ end
+
+ def test_getblockparamproxy
+ assert_compiles '1', %q{
+ def test(&block)
+ 0.then(&block)
+ end
+ test { 1 }
+ }, insns: [:getblockparamproxy]
+ end
+
+ def test_optimized_method_call_proc_call
+ assert_compiles '2', %q{
+ p = proc { |x| x * 2 }
+ def test(p)
+ p.call(1)
+ end
+ test(p)
+ test(p)
+ }, call_threshold: 2, insns: [:opt_send_without_block]
+ end
+
+ def test_optimized_method_call_proc_aref
+ assert_compiles '4', %q{
+ p = proc { |x| x * 2 }
+ def test(p)
+ p[2]
+ end
+ test(p)
+ test(p)
+ }, call_threshold: 2, insns: [:opt_aref]
+ end
+
+ def test_optimized_method_call_proc_yield
+ assert_compiles '6', %q{
+ p = proc { |x| x * 2 }
+ def test(p)
+ p.yield(3)
+ end
+ test(p)
+ test(p)
+ }, call_threshold: 2, insns: [:opt_send_without_block]
+ end
+
+ def test_optimized_method_call_proc_kw_splat
+ assert_compiles '3', %q{
+ p = proc { |**kw| kw[:a] + kw[:b] }
+ def test(p, h)
+ p.call(**h)
+ end
+ h = { a: 1, b: 2 }
+ test(p, h)
+ test(p, h)
+ }, call_threshold: 2, insns: [:opt_send_without_block]
+ end
+
+ def test_optimized_method_call_proc_call_splat
+ assert_compiles '43', %q{
+ p = proc { |x| x + 1 }
+ def test(p)
+ ary = [42]
+ p.call(*ary)
+ end
+ test(p)
+ test(p)
+ }, call_threshold: 2
+ end
+
+ def test_optimized_method_call_proc_call_kwarg
+ assert_compiles '1', %q{
+ p = proc { |a:| a }
+ def test(p)
+ p.call(a: 1)
+ end
+ test(p)
+ test(p)
+ }, call_threshold: 2
+ end
+
+ def test_call_a_forwardable_method
+ assert_runs '[]', %q{
+ def test_root = forwardable
+ def forwardable(...) = Array.[](...)
+ test_root
+ test_root
+ }, call_threshold: 2
+ end
+
+ def test_setlocal_on_eval_with_spill
+ assert_compiles '1', %q{
+ @b = binding
+ eval('a = 1; itself', @b)
+ eval('a', @b)
+ }
+ end
+
+ def test_nested_local_access
+ assert_compiles '[1, 2, 3]', %q{
+ 1.times do |l2|
+ 1.times do |l1|
+ define_method(:test) do
+ l1 = 1
+ l2 = 2
+ l3 = 3
+ [l1, l2, l3]
+ end
+ end
+ end
+
+ test
+ test
+ test
+ }, call_threshold: 3, insns: [:getlocal, :setlocal, :getlocal_WC_0, :setlocal_WC_1]
+ end
+
+ def test_send_with_local_written_by_blockiseq
+ assert_compiles '[1, 2]', %q{
+ def test
+ l1 = nil
+ l2 = nil
+ tap do |_|
+ l1 = 1
+ tap do |_|
+ l2 = 2
+ end
+ end
+
+ [l1, l2]
+ end
+
+ test
+ test
+ }, call_threshold: 2
+ end
+
def test_send_without_block
assert_compiles '[1, 2, 3]', %q{
def foo = 1
@@ -62,6 +608,802 @@ class TestZJIT < Test::Unit::TestCase
}
end
+ def test_send_with_six_args
+ assert_compiles '[1, 2, 3, 4, 5, 6]', %q{
+ def foo(a1, a2, a3, a4, a5, a6)
+ [a1, a2, a3, a4, a5, a6]
+ end
+
+ def test
+ foo(1, 2, 3, 4, 5, 6)
+ end
+
+ test # profile send
+ test
+ }, call_threshold: 2
+ end
+
+ def test_send_on_heap_object_in_spilled_arg
+ # This leads to a register spill, so not using `assert_compiles`
+ assert_runs 'Hash', %q{
+ def entry(a1, a2, a3, a4, a5, a6, a7, a8, a9)
+ a9.itself.class
+ end
+
+ entry(1, 2, 3, 4, 5, 6, 7, 8, {}) # profile
+ entry(1, 2, 3, 4, 5, 6, 7, 8, {})
+ }, call_threshold: 2
+ end
+
+ def test_send_exit_with_uninitialized_locals
+ assert_runs 'nil', %q{
+ def entry(init)
+ function_stub_exit(init)
+ end
+
+ def function_stub_exit(init)
+ uninitialized_local = 1 if init
+ uninitialized_local
+ end
+
+ entry(true) # profile and set 1 to the local slot
+ entry(false)
+ }, call_threshold: 2, allowed_iseqs: 'entry@-e:2'
+ end
+
+ def test_send_optional_arguments
+ assert_compiles '[[1, 2], [3, 4]]', %q{
+ def test(a, b = 2) = [a, b]
+ def entry = [test(1), test(3, 4)]
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_nil_block_arg
+ assert_compiles 'false', %q{
+ def test = block_given?
+ def entry = test(&nil)
+ test
+ }
+ end
+
+ def test_send_symbol_block_arg
+ assert_compiles '["1", "2"]', %q{
+ def test = [1, 2].map(&:to_s)
+ test
+ }
+ end
+
+ def test_send_variadic_with_block
+ assert_compiles '[[1, "a"], [2, "b"], [3, "c"]]', %q{
+ A = [1, 2, 3]
+ B = ["a", "b", "c"]
+
+ def test
+ result = []
+ A.zip(B) { |x, y| result << [x, y] }
+ result
+ end
+
+ test; test
+ }, call_threshold: 2
+ end
+
+ def test_send_splat
+ assert_runs '[1, 2]', %q{
+ def test(a, b) = [a, b]
+ def entry(arr) = test(*arr)
+ entry([1, 2])
+ }
+ end
+
+ def test_send_kwarg
+ assert_runs '[1, 2]', %q{
+ def test(a:, b:) = [a, b]
+ def entry = test(b: 2, a: 1) # change order
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_kwarg_optional
+ assert_compiles '[1, 2]', %q{
+ def test(a: 1, b: 2) = [a, b]
+ def entry = test
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_kwarg_optional_too_many
+ assert_compiles '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]', %q{
+ def test(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10) = [a, b, c, d, e, f, g, h, i, j]
+ def entry = test
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_kwarg_required_and_optional
+ assert_compiles '[3, 2]', %q{
+ def test(a:, b: 2) = [a, b]
+ def entry = test(a: 3)
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_kwarg_to_hash
+ assert_compiles '{a: 3}', %q{
+ def test(hash) = hash
+ def entry = test(a: 3)
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_kwarg_to_ccall
+ assert_compiles '["a", "b", "c"]', %q{
+ def test(s) = s.each_line(chomp: true).to_a
+ def entry = test(%(a\nb\nc))
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_kwarg_and_block_to_ccall
+ assert_compiles '["a", "b", "c"]', %q{
+ def test(s)
+ a = []
+ s.each_line(chomp: true) { |l| a << l }
+ a
+ end
+ def entry = test(%(a\nb\nc))
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_kwarg_with_too_many_args_to_c_call
+ assert_compiles '"a b c d {kwargs: :e}"', %q{
+ def test(a:, b:, c:, d:, e:) = sprintf("%s %s %s %s %s", a, b, c, d, kwargs: e)
+ def entry = test(e: :e, d: :d, c: :c, a: :a, b: :b)
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_kwsplat
+ assert_compiles '3', %q{
+ def test(a:) = a
+ def entry = test(**{a: 3})
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_kwrest
+ assert_compiles '{a: 3}', %q{
+ def test(**kwargs) = kwargs
+ def entry = test(a: 3)
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_req_kwreq
+ assert_compiles '[1, 3]', %q{
+ def test(a, c:) = [a, c]
+ def entry = test(1, c: 3)
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_req_opt_kwreq
+ assert_compiles '[[1, 2, 3], [-1, -2, -3]]', %q{
+ def test(a, b = 2, c:) = [a, b, c]
+ def entry = [test(1, c: 3), test(-1, -2, c: -3)] # specify all, change kw order
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_req_opt_kwreq_kwopt
+ assert_compiles '[[1, 2, 3, 4], [-1, -2, -3, -4]]', %q{
+ def test(a, b = 2, c:, d: 4) = [a, b, c, d]
+ def entry = [test(1, c: 3), test(-1, -2, d: -4, c: -3)] # specify all, change kw order
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_unexpected_keyword
+ assert_compiles ':error', %q{
+ def test(a: 1) = a*2
+ def entry
+ test(z: 2)
+ rescue ArgumentError
+ :error
+ end
+
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_all_arg_types
+ assert_compiles '[:req, :opt, :post, :kwr, :kwo, true]', %q{
+ def test(a, b = :opt, c, d:, e: :kwo) = [a, b, c, d, e, block_given?]
+ def entry = test(:req, :post, d: :kwr) {}
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
+ def test_send_ccall_variadic_with_different_receiver_classes
+ assert_compiles '[true, true]', %q{
+ def test(obj) = obj.start_with?("a")
+ [test("abc"), test(:abc)]
+ }, call_threshold: 2
+ end
+
+ def test_forwardable_iseq
+ assert_compiles '1', %q{
+ def test(...) = 1
+ test
+ }
+ end
+
+ def test_sendforward
+ assert_compiles '[1, 2]', %q{
+ def callee(a, b) = [a, b]
+ def test(...) = callee(...)
+ test(1, 2)
+ }, insns: [:sendforward]
+ end
+
+ def test_iseq_with_optional_arguments
+ assert_compiles '[[1, 2], [3, 4]]', %q{
+ def test(a, b = 2) = [a, b]
+ [test(1), test(3, 4)]
+ }
+ end
+
+ def test_invokesuper
+ assert_compiles '[6, 60]', %q{
+ class Foo
+ def foo(a) = a + 1
+ def bar(a) = a + 10
+ end
+
+ class Bar < Foo
+ def foo(a) = super(a) + 2
+ def bar(a) = super + 20
+ end
+
+ bar = Bar.new
+ [bar.foo(3), bar.bar(30)]
+ }
+ end
+
+ def test_invokesuper_with_local_written_by_blockiseq
+ # Using `assert_runs` because we don't compile invokeblock yet
+ assert_runs '3', %q{
+ class Foo
+ def test
+ yield
+ end
+ end
+
+ class Bar < Foo
+ def test
+ a = 1
+ super do
+ a += 2
+ end
+ a
+ end
+ end
+
+ Bar.new.test
+ }
+ end
+
+ def test_invokesuper_to_iseq
+ assert_compiles '["B", "A"]', %q{
+ class A
+ def foo
+ "A"
+ end
+ end
+
+ class B < A
+ def foo
+ ["B", super]
+ end
+ end
+
+ def test
+ B.new.foo
+ end
+
+ test # profile invokesuper
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ def test_invokesuper_with_args
+ assert_compiles '["B", 11]', %q{
+ class A
+ def foo(x)
+ x * 2
+ end
+ end
+
+ class B < A
+ def foo(x)
+ ["B", super(x) + 1]
+ end
+ end
+
+ def test
+ B.new.foo(5)
+ end
+
+ test # profile invokesuper
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ # Test super with explicit args when callee has rest parameter.
+ # This should fall back to dynamic dispatch since we can't handle rest params yet.
+ def test_invokesuper_with_args_to_rest_param
+ assert_compiles '["B", "a", ["b", "c"]]', %q{
+ class A
+ def foo(x, *rest)
+ [x, rest]
+ end
+ end
+
+ class B < A
+ def foo(x, y, z)
+ ["B", *super(x, y, z)]
+ end
+ end
+
+ def test
+ B.new.foo("a", "b", "c")
+ end
+
+ test # profile invokesuper
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ def test_invokesuper_with_block
+ assert_compiles '["B", "from_block"]', %q{
+ class A
+ def foo
+ block_given? ? yield : "no_block"
+ end
+ end
+
+ class B < A
+ def foo
+ ["B", super { "from_block" }]
+ end
+ end
+
+ def test
+ B.new.foo
+ end
+
+ test # profile invokesuper
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ def test_invokesuper_to_cfunc
+ assert_compiles '["MyArray", 3]', %q{
+ class MyArray < Array
+ def length
+ ["MyArray", super]
+ end
+ end
+
+ def test
+ MyArray.new([1, 2, 3]).length
+ end
+
+ test # profile invokesuper
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ def test_invokesuper_multilevel
+ assert_compiles '["C", ["B", "A"]]', %q{
+ class A
+ def foo
+ "A"
+ end
+ end
+
+ class B < A
+ def foo
+ ["B", super]
+ end
+ end
+
+ class C < B
+ def foo
+ ["C", super]
+ end
+ end
+
+ def test
+ C.new.foo
+ end
+
+ test # profile invokesuper
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ # Test implicit block forwarding - super without explicit block should forward caller's block
+ # Note: We call test twice to ensure ZJIT compiles it before the final call that we check
+ def test_invokesuper_forwards_block_implicitly
+ assert_compiles '["B", "forwarded_block"]', %q{
+ class A
+ def foo
+ block_given? ? yield : "no_block"
+ end
+ end
+
+ class B < A
+ def foo
+ ["B", super] # should forward the block from caller
+ end
+ end
+
+ def test
+ B.new.foo { "forwarded_block" }
+ end
+
+ test # profile invokesuper
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ # Test implicit block forwarding with explicit arguments
+ def test_invokesuper_forwards_block_implicitly_with_args
+ assert_compiles '["B", ["arg_value", "forwarded"]]', %q{
+ class A
+ def foo(x)
+ [x, (block_given? ? yield : "no_block")]
+ end
+ end
+
+ class B < A
+ def foo(x)
+ ["B", super(x)] # explicit args, but block should still be forwarded
+ end
+ end
+
+ def test
+ B.new.foo("arg_value") { "forwarded" }
+ end
+
+ test # profile
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ # Test implicit block forwarding when no block is given (should not fail)
+ def test_invokesuper_forwards_block_implicitly_no_block_given
+ assert_compiles '["B", "no_block"]', %q{
+ class A
+ def foo
+ block_given? ? yield : "no_block"
+ end
+ end
+
+ class B < A
+ def foo
+ ["B", super] # no block given by caller
+ end
+ end
+
+ def test
+ B.new.foo # called without a block
+ end
+
+ test # profile
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ # Test implicit block forwarding through multiple inheritance levels
+ def test_invokesuper_forwards_block_implicitly_multilevel
+ assert_compiles '["C", ["B", "deep_block"]]', %q{
+ class A
+ def foo
+ block_given? ? yield : "no_block"
+ end
+ end
+
+ class B < A
+ def foo
+ ["B", super] # forwards block to A
+ end
+ end
+
+ class C < B
+ def foo
+ ["C", super] # forwards block to B, which forwards to A
+ end
+ end
+
+ def test
+ C.new.foo { "deep_block" }
+ end
+
+ test # profile
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ # Test implicit block forwarding with block parameter syntax
+ def test_invokesuper_forwards_block_param
+ assert_compiles '["B", "block_param_forwarded"]', %q{
+ class A
+ def foo
+ block_given? ? yield : "no_block"
+ end
+ end
+
+ class B < A
+ def foo(&block)
+ ["B", super] # should forward &block implicitly
+ end
+ end
+
+ def test
+ B.new.foo { "block_param_forwarded" }
+ end
+
+ test # profile
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ def test_invokesuper_with_blockarg
+ assert_compiles '["B", "different block"]', %q{
+ class A
+ def foo
+ block_given? ? yield : "no block"
+ end
+ end
+
+ class B < A
+ def foo(&blk)
+ other_block = proc { "different block" }
+ ["B", super(&other_block)]
+ end
+ end
+
+ def test
+ B.new.foo { "passed block" }
+ end
+
+ test # profile
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ def test_invokesuper_with_symbol_to_proc
+ assert_compiles '["B", [3, 5, 7]]', %q{
+ class A
+ def foo(items, &blk)
+ items.map(&blk)
+ end
+ end
+
+ class B < A
+ def foo(items)
+ ["B", super(items, &:succ)]
+ end
+ end
+
+ def test
+ B.new.foo([2, 4, 6])
+ end
+
+ test # profile
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ def test_invokesuper_with_splat
+ assert_compiles '["B", 6]', %q{
+ class A
+ def foo(a, b, c)
+ a + b + c
+ end
+ end
+
+ class B < A
+ def foo(*args)
+ ["B", super(*args)]
+ end
+ end
+
+ def test
+ B.new.foo(1, 2, 3)
+ end
+
+ test # profile
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ def test_invokesuper_with_kwargs
+ assert_compiles '["B", "x=1, y=2"]', %q{
+ class A
+ def foo(x:, y:)
+ "x=#{x}, y=#{y}"
+ end
+ end
+
+ class B < A
+ def foo(x:, y:)
+ ["B", super(x: x, y: y)]
+ end
+ end
+
+ def test
+ B.new.foo(x: 1, y: 2)
+ end
+
+ test # profile
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ def test_invokesuper_with_kw_splat
+ assert_compiles '["B", "x=1, y=2"]', %q{
+ class A
+ def foo(x:, y:)
+ "x=#{x}, y=#{y}"
+ end
+ end
+
+ class B < A
+ def foo(**kwargs)
+ ["B", super(**kwargs)]
+ end
+ end
+
+ def test
+ B.new.foo(x: 1, y: 2)
+ end
+
+ test # profile
+ test # compile + run compiled code
+ }, call_threshold: 2
+ end
+
+ # Test that including a module after compilation correctly changes the super target.
+ # The included module's method should be called, not the original super target.
+ def test_invokesuper_with_include
+ assert_compiles '["B", "M"]', %q{
+ class A
+ def foo
+ "A"
+ end
+ end
+
+ class B < A
+ def foo
+ ["B", super]
+ end
+ end
+
+ def test
+ B.new.foo
+ end
+
+ test # profile invokesuper (super -> A#foo)
+ test # compile with super -> A#foo
+
+ # Now include a module in B that defines foo - super should go to M#foo instead
+ module M
+ def foo
+ "M"
+ end
+ end
+ B.include(M)
+
+ test # should call M#foo, not A#foo
+ }, call_threshold: 2
+ end
+
+ # Test that prepending a module after compilation correctly changes the super target.
+ # The prepended module's method should be called, not the original super target.
+ def test_invokesuper_with_prepend
+ assert_compiles '["B", "M"]', %q{
+ class A
+ def foo
+ "A"
+ end
+ end
+
+ class B < A
+ def foo
+ ["B", super]
+ end
+ end
+
+ def test
+ B.new.foo
+ end
+
+ test # profile invokesuper (super -> A#foo)
+ test # compile with super -> A#foo
+
+ # Now prepend a module that defines foo - super should go to M#foo instead
+ module M
+ def foo
+ "M"
+ end
+ end
+ A.prepend(M)
+
+ test # should call M#foo, not A#foo
+ }, call_threshold: 2
+ end
+
+ # Test super with positional and keyword arguments (pattern from chunky_png)
+ def test_invokesuper_with_keyword_args
+ assert_compiles '{content: "image data"}', %q{
+ class A
+ def foo(attributes = {})
+ @attributes = attributes
+ end
+ end
+
+ class B < A
+ def foo(content = '')
+ super(content: content)
+ end
+ end
+
+ def test
+ B.new.foo("image data")
+ end
+
+ test
+ test
+ }, call_threshold: 2
+ end
+
+ def test_invokebuiltin
+ # Not using assert_compiles due to register spill
+ assert_runs '["."]', %q{
+ def test = Dir.glob(".")
+ test
+ }
+ end
+
+ def test_invokebuiltin_delegate
+ assert_compiles '[[], true]', %q{
+ def test = [].clone(freeze: true)
+ r = test
+ [r, r.frozen?]
+ }
+ end
+
def test_opt_plus_const
assert_compiles '3', %q{
def test = 1 + 2
@@ -86,6 +1428,59 @@ class TestZJIT < Test::Unit::TestCase
}, call_threshold: 2
end
+ def test_opt_plus_left_imm
+ assert_compiles '3', %q{
+ def test(a) = 1 + a
+ test(1) # profile opt_plus
+ test(2)
+ }, call_threshold: 2
+ end
+
+ def test_opt_plus_type_guard_exit
+ assert_compiles '[3, 3.0]', %q{
+ def test(a) = 1 + a
+ test(1) # profile opt_plus
+ [test(2), test(2.0)]
+ }, call_threshold: 2
+ end
+
+ def test_opt_plus_type_guard_exit_with_locals
+ assert_compiles '[6, 6.0]', %q{
+ def test(a)
+ local = 3
+ 1 + a + local
+ end
+ test(1) # profile opt_plus
+ [test(2), test(2.0)]
+ }, call_threshold: 2
+ end
+
+ def test_opt_plus_type_guard_nested_exit
+ assert_compiles '[4, 4.0]', %q{
+ def side_exit(n) = 1 + n
+ def jit_frame(n) = 1 + side_exit(n)
+ def entry(n) = jit_frame(n)
+ entry(2) # profile send
+ [entry(2), entry(2.0)]
+ }, call_threshold: 2
+ end
+
+ def test_opt_plus_type_guard_nested_exit_with_locals
+ assert_compiles '[9, 9.0]', %q{
+ def side_exit(n)
+ local = 2
+ 1 + n + local
+ end
+ def jit_frame(n)
+ local = 3
+ 1 + side_exit(n) + local
+ end
+ def entry(n) = jit_frame(n)
+ entry(2) # profile send
+ [entry(2), entry(2.0)]
+ }, call_threshold: 2
+ end
+
# Test argument ordering
def test_opt_minus
assert_compiles '2', %q{
@@ -104,7 +1499,6 @@ class TestZJIT < Test::Unit::TestCase
end
def test_opt_mult_overflow
- omit 'side exits are not implemented yet'
assert_compiles '[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]', %q{
def test(a, b)
a * b
@@ -126,7 +1520,15 @@ class TestZJIT < Test::Unit::TestCase
def test(a, b) = a == b
test(0, 2) # profile opt_eq
[test(1, 1), test(0, 1)]
- }, call_threshold: 2
+ }, insns: [:opt_eq], call_threshold: 2
+ end
+
+ def test_opt_eq_with_minus_one
+ assert_compiles '[false, true]', %q{
+ def test(a) = a == -1
+ test(1) # profile opt_eq
+ [test(0), test(-1)]
+ }, insns: [:opt_eq], call_threshold: 2
end
def test_opt_neq_dynamic
@@ -136,7 +1538,7 @@ class TestZJIT < Test::Unit::TestCase
def test(a, b) = a != b
test(0, 2) # profile opt_neq
[test(1, 1), test(0, 1)]
- }, call_threshold: 1
+ }, insns: [:opt_neq], call_threshold: 1
end
def test_opt_neq_fixnum
@@ -152,7 +1554,7 @@ class TestZJIT < Test::Unit::TestCase
def test(a, b) = a < b
test(2, 3) # profile opt_lt
[test(0, 1), test(0, 0), test(1, 0)]
- }, call_threshold: 2
+ }, insns: [:opt_lt], call_threshold: 2
end
def test_opt_lt_with_literal_lhs
@@ -160,7 +1562,7 @@ class TestZJIT < Test::Unit::TestCase
def test(n) = 2 < n
test(2) # profile opt_lt
[test(1), test(2), test(3)]
- }, call_threshold: 2
+ }, insns: [:opt_lt], call_threshold: 2
end
def test_opt_le
@@ -168,7 +1570,7 @@ class TestZJIT < Test::Unit::TestCase
def test(a, b) = a <= b
test(2, 3) # profile opt_le
[test(0, 1), test(0, 0), test(1, 0)]
- }, call_threshold: 2
+ }, insns: [:opt_le], call_threshold: 2
end
def test_opt_gt
@@ -176,22 +1578,621 @@ class TestZJIT < Test::Unit::TestCase
def test(a, b) = a > b
test(2, 3) # profile opt_gt
[test(0, 1), test(0, 0), test(1, 0)]
+ }, insns: [:opt_gt], call_threshold: 2
+ end
+
+ def test_opt_empty_p
+ assert_compiles('[false, false, true]', <<~RUBY, insns: [:opt_empty_p])
+ def test(x) = x.empty?
+ return test([1]), test("1"), test({})
+ RUBY
+ end
+
+ def test_opt_succ
+ assert_compiles('[0, "B"]', <<~RUBY, insns: [:opt_succ])
+ def test(obj) = obj.succ
+ return test(-1), test("A")
+ RUBY
+ end
+
+ def test_opt_and
+ assert_compiles('[1, [3, 2, 1]]', <<~RUBY, insns: [:opt_and])
+ def test(x, y) = x & y
+ return test(0b1101, 3), test([3, 2, 1, 4], [8, 1, 2, 3])
+ RUBY
+ end
+
+ def test_opt_or
+ assert_compiles('[11, [3, 2, 1]]', <<~RUBY, insns: [:opt_or])
+ def test(x, y) = x | y
+ return test(0b1000, 3), test([3, 2, 1], [1, 2, 3])
+ RUBY
+ end
+
+ def test_fixnum_and
+ assert_compiles '[1, 2, 4]', %q{
+ def test(a, b) = a & b
+ [
+ test(5, 3),
+ test(0b011, 0b110),
+ test(-0b011, 0b110)
+ ]
+ }, call_threshold: 2, insns: [:opt_and]
+ end
+
+ def test_fixnum_and_side_exit
+ assert_compiles '[2, 2, false]', %q{
+ def test(a, b) = a & b
+ [
+ test(2, 2),
+ test(0b011, 0b110),
+ test(true, false)
+ ]
+ }, call_threshold: 2, insns: [:opt_and]
+ end
+
+ def test_fixnum_or
+ assert_compiles '[7, 3, -3]', %q{
+ def test(a, b) = a | b
+ [
+ test(5, 3),
+ test(1, 2),
+ test(1, -4)
+ ]
+ }, call_threshold: 2, insns: [:opt_or]
+ end
+
+ def test_fixnum_or_side_exit
+ assert_compiles '[3, 2, true]', %q{
+ def test(a, b) = a | b
+ [
+ test(1, 2),
+ test(2, 2),
+ test(true, false)
+ ]
+ }, call_threshold: 2, insns: [:opt_or]
+ end
+
+ def test_fixnum_xor
+ assert_compiles '[6, -8, 3]', %q{
+ def test(a, b) = a ^ b
+ [
+ test(5, 3),
+ test(-5, 3),
+ test(1, 2)
+ ]
+ }, call_threshold: 2
+ end
+
+ def test_fixnum_xor_side_exit
+ assert_compiles '[6, 6, true]', %q{
+ def test(a, b) = a ^ b
+ [
+ test(5, 3),
+ test(5, 3),
+ test(true, false)
+ ]
}, call_threshold: 2
end
+ def test_fixnum_mul
+ assert_compiles '12', %q{
+ C = 3
+ def test(n) = C * n
+ test(4)
+ test(4)
+ test(4)
+ }, call_threshold: 2, insns: [:opt_mult]
+ end
+
+ def test_fixnum_div
+ assert_compiles '12', %q{
+ C = 48
+ def test(n) = C / n
+ test(4)
+ test(4)
+ }, call_threshold: 2, insns: [:opt_div]
+ end
+
+ def test_fixnum_floor
+ assert_compiles '0', %q{
+ C = 3
+ def test(n) = C / n
+ test(4)
+ test(4)
+ }, call_threshold: 2, insns: [:opt_div]
+ end
+
+ def test_fixnum_div_zero
+ assert_runs '"divided by 0"', %q{
+ def test(n)
+ n / 0
+ rescue ZeroDivisionError => e
+ e.message
+ end
+
+ test(0)
+ test(0)
+ }, call_threshold: 2, insns: [:opt_div]
+ end
+
+ def test_opt_not
+ assert_compiles('[true, true, false]', <<~RUBY, insns: [:opt_not])
+ def test(obj) = !obj
+ return test(nil), test(false), test(0)
+ RUBY
+ end
+
+ def test_opt_regexpmatch2
+ assert_compiles('[1, nil]', <<~RUBY, insns: [:opt_regexpmatch2])
+ def test(haystack) = /needle/ =~ haystack
+ return test("kneedle"), test("")
+ RUBY
+ end
+
def test_opt_ge
assert_compiles '[false, true, true]', %q{
def test(a, b) = a >= b
test(2, 3) # profile opt_ge
[test(0, 1), test(0, 0), test(1, 0)]
+ }, insns: [:opt_ge], call_threshold: 2
+ end
+
+ def test_opt_new_does_not_push_frame
+ assert_compiles 'nil', %q{
+ class Foo
+ attr_reader :backtrace
+
+ def initialize
+ @backtrace = caller
+ end
+ end
+ def test = Foo.new
+
+ foo = test
+ foo.backtrace.find do |frame|
+ frame.include?('Class#new')
+ end
+ }, insns: [:opt_new]
+ end
+
+ def test_opt_new_with_redefined
+ assert_compiles '"foo"', %q{
+ class Foo
+ def self.new = "foo"
+
+ def initialize = raise("unreachable")
+ end
+ def test = Foo.new
+
+ test
+ }, insns: [:opt_new]
+ end
+
+ def test_opt_new_invalidate_new
+ assert_compiles '["Foo", "foo"]', %q{
+ class Foo; end
+ def test = Foo.new
+ test; test
+ result = [test.class.name]
+ def Foo.new = "foo"
+ result << test
+ result
+ }, insns: [:opt_new], call_threshold: 2
+ end
+
+ def test_opt_new_with_custom_allocator
+ assert_compiles '"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"', %q{
+ require "digest"
+ def test = Digest::SHA256.new.hexdigest
+ test; test
+ }, insns: [:opt_new], call_threshold: 2
+ end
+
+ def test_opt_new_with_custom_allocator_raises
+ assert_compiles '[42, 42]', %q{
+ require "digest"
+ class C < Digest::Base; end
+ def test
+ begin
+ Digest::Base.new
+ rescue NotImplementedError
+ 42
+ end
+ end
+ [test, test]
+ }, insns: [:opt_new], call_threshold: 2
+ end
+
+ def test_opt_newarray_send_include_p
+ assert_compiles '[true, false]', %q{
+ def test(x)
+ [:y, 1, Object.new].include?(x)
+ end
+ [test(1), test("n")]
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
+ def test_opt_newarray_send_include_p_redefined
+ assert_compiles '[:true, :false]', %q{
+ class Array
+ alias_method :old_include?, :include?
+ def include?(x)
+ old_include?(x) ? :true : :false
+ end
+ end
+
+ def test(x)
+ [:y, 1, Object.new].include?(x)
+ end
+ [test(1), test("n")]
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
+ def test_opt_duparray_send_include_p
+ assert_compiles '[true, false]', %q{
+ def test(x)
+ [:y, 1].include?(x)
+ end
+ [test(1), test("n")]
+ }, insns: [:opt_duparray_send], call_threshold: 1
+ end
+
+ def test_opt_duparray_send_include_p_redefined
+ assert_compiles '[:true, :false]', %q{
+ class Array
+ alias_method :old_include?, :include?
+ def include?(x)
+ old_include?(x) ? :true : :false
+ end
+ end
+
+ def test(x)
+ [:y, 1].include?(x)
+ end
+ [test(1), test("n")]
+ }, insns: [:opt_duparray_send], call_threshold: 1
+ end
+
+ def test_opt_newarray_send_pack_buffer
+ assert_compiles '["ABC", "ABC", "ABC", "ABC"]', %q{
+ def test(num, buffer)
+ [num].pack('C', buffer:)
+ end
+ buf = ""
+ [test(65, buf), test(66, buf), test(67, buf), buf]
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
+ def test_opt_newarray_send_pack_buffer_redefined
+ assert_compiles '["b", "A"]', %q{
+ class Array
+ alias_method :old_pack, :pack
+ def pack(fmt, buffer: nil)
+ old_pack(fmt, buffer: buffer)
+ "b"
+ end
+ end
+
+ def test(num, buffer)
+ [num].pack('C', buffer:)
+ end
+ buf = ""
+ [test(65, buf), buf]
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
+ def test_opt_newarray_send_hash
+ assert_compiles 'Integer', %q{
+ def test(x)
+ [1, 2, x].hash
+ end
+ test(20).class
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
+ def test_opt_newarray_send_hash_redefined
+ assert_compiles '42', %q{
+ Array.class_eval { def hash = 42 }
+
+ def test(x)
+ [1, 2, x].hash
+ end
+ test(20)
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
+ def test_opt_newarray_send_max
+ assert_compiles '[20, 40]', %q{
+ def test(a,b) = [a,b].max
+ [test(10, 20), test(40, 30)]
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
+ def test_opt_newarray_send_max_redefined
+ assert_compiles '[60, 90]', %q{
+ class Array
+ alias_method :old_max, :max
+ def max
+ old_max * 2
+ end
+ end
+
+ def test(a,b) = [a,b].max
+ [test(15, 30), test(45, 35)]
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
+ def test_new_hash_empty
+ assert_compiles '{}', %q{
+ def test = {}
+ test
+ }, insns: [:newhash]
+ end
+
+ def test_new_hash_nonempty
+ assert_compiles '{"key" => "value", 42 => 100}', %q{
+ def test
+ key = "key"
+ value = "value"
+ num = 42
+ result = 100
+ {key => value, num => result}
+ end
+ test
+ }, insns: [:newhash]
+ end
+
+ def test_new_hash_single_key_value
+ assert_compiles '{"key" => "value"}', %q{
+ def test = {"key" => "value"}
+ test
+ }, insns: [:newhash]
+ end
+
+ def test_new_hash_with_computation
+ assert_compiles '{"sum" => 5, "product" => 6}', %q{
+ def test(a, b)
+ {"sum" => a + b, "product" => a * b}
+ end
+ test(2, 3)
+ }, insns: [:newhash]
+ end
+
+ def test_new_hash_with_user_defined_hash_method
+ assert_runs 'true', %q{
+ class CustomKey
+ attr_reader :val
+
+ def initialize(val)
+ @val = val
+ end
+
+ def hash
+ @val.hash
+ end
+
+ def eql?(other)
+ other.is_a?(CustomKey) && @val == other.val
+ end
+ end
+
+ def test
+ key = CustomKey.new("key")
+ hash = {key => "value"}
+ hash[key] == "value"
+ end
+ test
+ }
+ end
+
+ def test_new_hash_with_user_hash_method_exception
+ assert_runs 'RuntimeError', %q{
+ class BadKey
+ def hash
+ raise "Hash method failed!"
+ end
+ end
+
+ def test
+ key = BadKey.new
+ {key => "value"}
+ end
+
+ begin
+ test
+ rescue => e
+ e.class
+ end
+ }
+ end
+
+ def test_new_hash_with_user_eql_method_exception
+ assert_runs 'RuntimeError', %q{
+ class BadKey
+ def hash
+ 42
+ end
+
+ def eql?(other)
+ raise "Eql method failed!"
+ end
+ end
+
+ def test
+ key1 = BadKey.new
+ key2 = BadKey.new
+ {key1 => "value1", key2 => "value2"}
+ end
+
+ begin
+ test
+ rescue => e
+ e.class
+ end
+ }
+ end
+
+ def test_opt_hash_freeze
+ assert_compiles "[{}, 5]", %q{
+ def test = {}.freeze
+ result = [test]
+ class Hash
+ def freeze = 5
+ end
+ result << test
+ }, insns: [:opt_hash_freeze], call_threshold: 1
+ end
+
+ def test_opt_hash_freeze_rewritten
+ assert_compiles "5", %q{
+ class Hash
+ def freeze = 5
+ end
+ def test = {}.freeze
+ test
+ }, insns: [:opt_hash_freeze], call_threshold: 1
+ end
+
+ def test_opt_aset_hash
+ assert_compiles '42', %q{
+ def test(h, k, v)
+ h[k] = v
+ end
+ h = {}
+ test(h, :key, 42)
+ test(h, :key, 42)
+ h[:key]
+ }, call_threshold: 2, insns: [:opt_aset]
+ end
+
+ def test_opt_aset_hash_returns_value
+ assert_compiles '100', %q{
+ def test(h, k, v)
+ h[k] = v
+ end
+ test({}, :key, 100)
+ test({}, :key, 100)
+ }, call_threshold: 2
+ end
+
+ def test_opt_aset_hash_string_key
+ assert_compiles '"bar"', %q{
+ def test(h, k, v)
+ h[k] = v
+ end
+ h = {}
+ test(h, "foo", "bar")
+ test(h, "foo", "bar")
+ h["foo"]
+ }, call_threshold: 2
+ end
+
+ def test_opt_aset_hash_subclass
+ assert_compiles '42', %q{
+ class MyHash < Hash; end
+ def test(h, k, v)
+ h[k] = v
+ end
+ h = MyHash.new
+ test(h, :key, 42)
+ test(h, :key, 42)
+ h[:key]
+ }, call_threshold: 2
+ end
+
+ def test_opt_aset_hash_too_few_args
+ assert_compiles '"ArgumentError"', %q{
+ def test(h)
+ h.[]= 123
+ rescue ArgumentError
+ "ArgumentError"
+ end
+ test({})
+ test({})
}, call_threshold: 2
end
+ def test_opt_aset_hash_too_many_args
+ assert_compiles '"ArgumentError"', %q{
+ def test(h)
+ h[:a, :b] = :c
+ rescue ArgumentError
+ "ArgumentError"
+ end
+ test({})
+ test({})
+ }, call_threshold: 2
+ end
+
+ def test_opt_ary_freeze
+ assert_compiles "[[], 5]", %q{
+ def test = [].freeze
+ result = [test]
+ class Array
+ def freeze = 5
+ end
+ result << test
+ }, insns: [:opt_ary_freeze], call_threshold: 1
+ end
+
+ def test_opt_ary_freeze_rewritten
+ assert_compiles "5", %q{
+ class Array
+ def freeze = 5
+ end
+ def test = [].freeze
+ test
+ }, insns: [:opt_ary_freeze], call_threshold: 1
+ end
+
+ def test_opt_str_freeze
+ assert_compiles "[\"\", 5]", %q{
+ def test = ''.freeze
+ result = [test]
+ class String
+ def freeze = 5
+ end
+ result << test
+ }, insns: [:opt_str_freeze], call_threshold: 1
+ end
+
+ def test_opt_str_freeze_rewritten
+ assert_compiles "5", %q{
+ class String
+ def freeze = 5
+ end
+ def test = ''.freeze
+ test
+ }, insns: [:opt_str_freeze], call_threshold: 1
+ end
+
+ def test_opt_str_uminus
+ assert_compiles "[\"\", 5]", %q{
+ def test = -''
+ result = [test]
+ class String
+ def -@ = 5
+ end
+ result << test
+ }, insns: [:opt_str_uminus], call_threshold: 1
+ end
+
+ def test_opt_str_uminus_rewritten
+ assert_compiles "5", %q{
+ class String
+ def -@ = 5
+ end
+ def test = -''
+ test
+ }, insns: [:opt_str_uminus], call_threshold: 1
+ end
+
def test_new_array_empty
assert_compiles '[]', %q{
def test = []
test
- }
+ }, insns: [:newarray]
end
def test_new_array_nonempty
@@ -219,6 +2220,272 @@ class TestZJIT < Test::Unit::TestCase
}
end
+ def test_array_fixnum_aref
+ assert_compiles '3', %q{
+ def test(x) = [1,2,3][x]
+ test(2)
+ test(2)
+ }, call_threshold: 2, insns: [:opt_aref]
+ end
+
+ def test_array_fixnum_aref_negative_index
+ assert_compiles '3', %q{
+ def test(x) = [1,2,3][x]
+ test(-1)
+ test(-1)
+ }, call_threshold: 2, insns: [:opt_aref]
+ end
+
+ def test_array_fixnum_aref_out_of_bounds_positive
+ assert_compiles 'nil', %q{
+ def test(x) = [1,2,3][x]
+ test(10)
+ test(10)
+ }, call_threshold: 2, insns: [:opt_aref]
+ end
+
+ def test_array_fixnum_aref_out_of_bounds_negative
+ assert_compiles 'nil', %q{
+ def test(x) = [1,2,3][x]
+ test(-10)
+ test(-10)
+ }, call_threshold: 2, insns: [:opt_aref]
+ end
+
+ def test_array_fixnum_aref_array_subclass
+ assert_compiles '3', %q{
+ class MyArray < Array; end
+ def test(arr, idx) = arr[idx]
+ arr = MyArray[1,2,3]
+ test(arr, 2)
+ arr = MyArray[1,2,3]
+ test(arr, 2)
+ }, call_threshold: 2, insns: [:opt_aref]
+ end
+
+ def test_array_aref_non_fixnum_index
+ assert_compiles 'TypeError', %q{
+ def test(arr, idx) = arr[idx]
+ test([1,2,3], 1)
+ test([1,2,3], 1)
+ begin
+ test([1,2,3], "1")
+ rescue => e
+ e.class
+ end
+ }, call_threshold: 2
+ end
+
+ def test_array_fixnum_aset
+ assert_compiles '[1, 2, 7]', %q{
+ def test(arr, idx)
+ arr[idx] = 7
+ end
+ arr = [1,2,3]
+ test(arr, 2)
+ arr = [1,2,3]
+ test(arr, 2)
+ arr
+ }, call_threshold: 2, insns: [:opt_aset]
+ end
+
+ def test_array_fixnum_aset_returns_value
+ assert_compiles '7', %q{
+ def test(arr, idx)
+ arr[idx] = 7
+ end
+ test([1,2,3], 2)
+ test([1,2,3], 2)
+ }, call_threshold: 2, insns: [:opt_aset]
+ end
+
+ def test_array_fixnum_aset_out_of_bounds
+ assert_compiles '[1, 2, 3, nil, nil, 7]', %q{
+ def test(arr)
+ arr[5] = 7
+ end
+ arr = [1,2,3]
+ test(arr)
+ arr = [1,2,3]
+ test(arr)
+ arr
+ }, call_threshold: 2
+ end
+
+ def test_array_fixnum_aset_negative_index
+ assert_compiles '[1, 2, 7]', %q{
+ def test(arr)
+ arr[-1] = 7
+ end
+ arr = [1,2,3]
+ test(arr)
+ arr = [1,2,3]
+ test(arr)
+ arr
+ }, call_threshold: 2
+ end
+
+ def test_array_fixnum_aset_shared
+ assert_compiles '[10, 999, -1, -2]', %q{
+ def test(arr, idx, val)
+ arr[idx] = val
+ end
+ arr = (0..50).to_a
+ test(arr, 0, -1)
+ test(arr, 1, -2)
+ shared = arr[10, 20]
+ test(shared, 0, 999)
+ [arr[10], shared[0], arr[0], arr[1]]
+ }, call_threshold: 2
+ end
+
+ def test_array_fixnum_aset_frozen
+ assert_compiles 'FrozenError', %q{
+ def test(arr, idx, val)
+ arr[idx] = val
+ end
+ arr = [1,2,3]
+ test(arr, 1, 9)
+ test(arr, 1, 9)
+ arr.freeze
+ begin
+ test(arr, 1, 9)
+ rescue => e
+ e.class
+ end
+ }, call_threshold: 2
+ end
+
+ def test_array_fixnum_aset_array_subclass
+ assert_compiles '7', %q{
+ class MyArray < Array; end
+ def test(arr, idx)
+ arr[idx] = 7
+ end
+ arr = MyArray.new
+ test(arr, 0)
+ arr = MyArray.new
+ test(arr, 0)
+ arr[0]
+ }, call_threshold: 2, insns: [:opt_aset]
+ end
+
+ def test_array_aset_non_fixnum_index
+ assert_compiles 'TypeError', %q{
+ def test(arr, idx)
+ arr[idx] = 7
+ end
+ test([1,2,3], 0)
+ test([1,2,3], 0)
+ begin
+ test([1,2,3], "0")
+ rescue => e
+ e.class
+ end
+ }, call_threshold: 2
+ end
+
+ def test_empty_array_pop
+ assert_compiles 'nil', %q{
+ def test(arr) = arr.pop
+ test([])
+ test([])
+ }, call_threshold: 2
+ end
+
+ def test_array_pop_no_arg
+ assert_compiles '42', %q{
+ def test(arr) = arr.pop
+ test([32, 33, 42])
+ test([32, 33, 42])
+ }, call_threshold: 2
+ end
+
+ def test_array_pop_arg
+ assert_compiles '[33, 42]', %q{
+ def test(arr) = arr.pop(2)
+ test([32, 33, 42])
+ test([32, 33, 42])
+ }, call_threshold: 2
+ end
+
+ def test_new_range_inclusive
+ assert_compiles '1..5', %q{
+ def test(a, b) = a..b
+ test(1, 5)
+ }
+ end
+
+ def test_new_range_exclusive
+ assert_compiles '1...5', %q{
+ def test(a, b) = a...b
+ test(1, 5)
+ }
+ end
+
+ def test_new_range_with_literal
+ assert_compiles '3..10', %q{
+ def test(n) = n..10
+ test(3)
+ }
+ end
+
+ def test_new_range_fixnum_both_literals_inclusive
+ assert_compiles '1..2', %q{
+ def test()
+ a = 2
+ (1..a)
+ end
+ test; test
+ }, call_threshold: 2, insns: [:newrange]
+ end
+
+ def test_new_range_fixnum_both_literals_exclusive
+ assert_compiles '1...2', %q{
+ def test()
+ a = 2
+ (1...a)
+ end
+ test; test
+ }, call_threshold: 2, insns: [:newrange]
+ end
+
+ def test_new_range_fixnum_low_literal_inclusive
+ assert_compiles '1..3', %q{
+ def test(a)
+ (1..a)
+ end
+ test(2); test(3)
+ }, call_threshold: 2, insns: [:newrange]
+ end
+
+ def test_new_range_fixnum_low_literal_exclusive
+ assert_compiles '1...3', %q{
+ def test(a)
+ (1...a)
+ end
+ test(2); test(3)
+ }, call_threshold: 2, insns: [:newrange]
+ end
+
+ def test_new_range_fixnum_high_literal_inclusive
+ assert_compiles '3..10', %q{
+ def test(a)
+ (a..10)
+ end
+ test(2); test(3)
+ }, call_threshold: 2, insns: [:newrange]
+ end
+
+ def test_new_range_fixnum_high_literal_exclusive
+ assert_compiles '3...10', %q{
+ def test(a)
+ (a...10)
+ end
+ test(2); test(3)
+ }, call_threshold: 2, insns: [:newrange]
+ end
+
def test_if
assert_compiles '[0, nil]', %q{
def test(n)
@@ -479,6 +2746,675 @@ class TestZJIT < Test::Unit::TestCase
}, call_threshold: 5, num_profiles: 3
end
+ def test_spilled_basic_block_args
+ assert_compiles '55', %q{
+ def test(n1, n2)
+ n3 = 3
+ n4 = 4
+ n5 = 5
+ n6 = 6
+ n7 = 7
+ n8 = 8
+ n9 = 9
+ n10 = 10
+ if n1 < n2
+ n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10
+ end
+ end
+ test(1, 2)
+ }
+ end
+
+ def test_spilled_method_args
+ assert_runs '55', %q{
+ def foo(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10)
+ n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10
+ end
+
+ def test
+ foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+ end
+
+ test
+ }
+
+ # TODO(Shopify/ruby#716): Support spills and change to assert_compiles
+ assert_runs '1', %q{
+ def a(n1,n2,n3,n4,n5,n6,n7,n8,n9) = n1+n9
+ a(2,0,0,0,0,0,0,0,-1)
+ }
+
+ # TODO(Shopify/ruby#716): Support spills and change to assert_compiles
+ assert_runs '0', %q{
+ def a(n1,n2,n3,n4,n5,n6,n7,n8) = n8
+ a(1,1,1,1,1,1,1,0)
+ }
+
+ # TODO(Shopify/ruby#716): Support spills and change to assert_compiles
+ # self param with spilled param
+ assert_runs '"main"', %q{
+ def a(n1,n2,n3,n4,n5,n6,n7,n8) = self
+ a(1,0,0,0,0,0,0,0).to_s
+ }
+ end
+
+ def test_spilled_param_new_arary
+ # TODO(Shopify/ruby#716): Support spills and change to assert_compiles
+ assert_runs '[:ok]', %q{
+ def a(n1,n2,n3,n4,n5,n6,n7,n8) = [n8]
+ a(0,0,0,0,0,0,0, :ok)
+ }
+ end
+
+ def test_forty_param_method
+ # This used to a trigger a miscomp on A64 due
+ # to a memory displacement larger than 9 bits.
+ # Using assert_runs again due to register spill.
+ # TODO: It should be fixed by register spill support.
+ assert_runs '1', %Q{
+ def foo(#{'_,' * 39} n40) = n40
+
+ foo(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1)
+ }
+ end
+
+ def test_putself
+ assert_compiles '3', %q{
+ class Integer
+ def minus(a)
+ self - a
+ end
+ end
+ 5.minus(2)
+ }
+ end
+
+ def test_getinstancevariable
+ assert_compiles 'nil', %q{
+ def test() = @foo
+
+ test()
+ }
+ assert_compiles '3', %q{
+ @foo = 3
+ def test() = @foo
+
+ test()
+ }
+ end
+
+ def test_getinstancevariable_miss
+ assert_compiles '[1, 1, 4]', %q{
+ class C
+ def foo
+ @foo
+ end
+
+ def foo_then_bar
+ @foo = 1
+ @bar = 2
+ end
+
+ def bar_then_foo
+ @bar = 3
+ @foo = 4
+ end
+ end
+
+ o1 = C.new
+ o1.foo_then_bar
+ result = []
+ result << o1.foo
+ result << o1.foo
+ o2 = C.new
+ o2.bar_then_foo
+ result << o2.foo
+ result
+ }
+ end
+
+ def test_setinstancevariable
+ assert_compiles '1', %q{
+ def test() = @foo = 1
+
+ test()
+ @foo
+ }
+ end
+
+ def test_getclassvariable
+ assert_compiles '42', %q{
+ class Foo
+ def self.test = @@x
+ end
+
+ Foo.class_variable_set(:@@x, 42)
+ Foo.test()
+ }
+ end
+
+ def test_getclassvariable_raises
+ assert_compiles '"uninitialized class variable @@x in Foo"', %q{
+ class Foo
+ def self.test = @@x
+ end
+
+ begin
+ Foo.test
+ rescue NameError => e
+ e.message
+ end
+ }
+ end
+
+ def test_setclassvariable
+ assert_compiles '42', %q{
+ class Foo
+ def self.test = @@x = 42
+ end
+
+ Foo.test()
+ Foo.class_variable_get(:@@x)
+ }
+ end
+
+ def test_setclassvariable_raises
+ assert_compiles '"can\'t modify frozen Class: Foo"', %q{
+ class Foo
+ def self.test = @@x = 42
+ freeze
+ end
+
+ begin
+ Foo.test
+ rescue FrozenError => e
+ e.message
+ end
+ }
+ end
+
+ def test_attr_reader
+ assert_compiles '[4, 4]', %q{
+ class C
+ attr_reader :foo
+
+ def initialize
+ @foo = 4
+ end
+ end
+
+ def test(c) = c.foo
+ c = C.new
+ [test(c), test(c)]
+ }, call_threshold: 2, insns: [:opt_send_without_block]
+ end
+
+ def test_attr_accessor_getivar
+ assert_compiles '[4, 4]', %q{
+ class C
+ attr_accessor :foo
+
+ def initialize
+ @foo = 4
+ end
+ end
+
+ def test(c) = c.foo
+ c = C.new
+ [test(c), test(c)]
+ }, call_threshold: 2, insns: [:opt_send_without_block]
+ end
+
+ def test_attr_accessor_setivar
+ assert_compiles '[5, 5]', %q{
+ class C
+ attr_accessor :foo
+
+ def initialize
+ @foo = 4
+ end
+ end
+
+ def test(c)
+ c.foo = 5
+ c.foo
+ end
+
+ c = C.new
+ [test(c), test(c)]
+ }, call_threshold: 2, insns: [:opt_send_without_block]
+ end
+
+ def test_attr_writer
+ assert_compiles '[5, 5]', %q{
+ class C
+ attr_writer :foo
+
+ def initialize
+ @foo = 4
+ end
+
+ def get_foo = @foo
+ end
+
+ def test(c)
+ c.foo = 5
+ c.get_foo
+ end
+ c = C.new
+ [test(c), test(c)]
+ }, call_threshold: 2, insns: [:opt_send_without_block]
+ end
+
+ def test_uncached_getconstant_path
+ assert_compiles RUBY_COPYRIGHT.dump, %q{
+ def test = RUBY_COPYRIGHT
+ test
+ }, call_threshold: 1, insns: [:opt_getconstant_path]
+ end
+
+ def test_expandarray_no_splat
+ assert_compiles '[3, 4]', %q{
+ def test(o)
+ a, b = o
+ [a, b]
+ end
+ test [3, 4]
+ }, call_threshold: 1, insns: [:expandarray]
+ end
+
+ def test_expandarray_splat
+ assert_compiles '[3, [4]]', %q{
+ def test(o)
+ a, *b = o
+ [a, b]
+ end
+ test [3, 4]
+ }, call_threshold: 1, insns: [:expandarray]
+ end
+
+ def test_expandarray_splat_post
+ assert_compiles '[3, [4], 5]', %q{
+ def test(o)
+ a, *b, c = o
+ [a, b, c]
+ end
+ test [3, 4, 5]
+ }, call_threshold: 1, insns: [:expandarray]
+ end
+
+ def test_getconstant_path_autoload
+ # A constant-referencing expression can run arbitrary code through Kernel#autoload.
+ Dir.mktmpdir('autoload') do |tmpdir|
+ autoload_path = File.join(tmpdir, 'test_getconstant_path_autoload.rb')
+ File.write(autoload_path, 'X = RUBY_COPYRIGHT')
+
+ assert_compiles RUBY_COPYRIGHT.dump, %Q{
+ Object.autoload(:X, #{File.realpath(autoload_path).inspect})
+ def test = X
+ test
+ }, call_threshold: 1, insns: [:opt_getconstant_path]
+ end
+ end
+
+ def test_constant_invalidation
+ assert_compiles '123', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path]
+ class C; end
+ def test = C
+ test
+ test
+
+ C = 123
+ test
+ RUBY
+ end
+
+ def test_constant_path_invalidation
+ assert_compiles '["Foo::C", "Foo::C", "Bar::C"]', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path]
+ module A
+ module B; end
+ end
+
+ module Foo
+ C = "Foo::C"
+ end
+
+ module Bar
+ C = "Bar::C"
+ end
+
+ A::B = Foo
+
+ def test = A::B::C
+
+ result = []
+
+ result << test
+ result << test
+
+ A::B = Bar
+
+ result << test
+ result
+ RUBY
+ end
+
+ def test_single_ractor_mode_invalidation
+ # Without invalidating the single-ractor mode, the test would crash
+ assert_compiles '"errored but not crashed"', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path]
+ C = Object.new
+
+ def test
+ C
+ rescue Ractor::IsolationError
+ "errored but not crashed"
+ end
+
+ test
+ test
+
+ Ractor.new {
+ test
+ }.value
+ RUBY
+ end
+
+ def test_dupn
+ assert_compiles '[[1], [1, 1], :rhs, [nil, :rhs]]', <<~RUBY, insns: [:dupn]
+ def test(array) = (array[1, 2] ||= :rhs)
+
+ one = [1, 1]
+ start_empty = []
+ [test(one), one, test(start_empty), start_empty]
+ RUBY
+ end
+
+ def test_send_backtrace
+ backtrace = [
+ "-e:2:in 'Object#jit_frame1'",
+ "-e:3:in 'Object#entry'",
+ "-e:5:in 'block in <main>'",
+ "-e:6:in '<main>'",
+ ]
+ assert_compiles backtrace.inspect, %q{
+ def jit_frame2 = caller # 1
+ def jit_frame1 = jit_frame2 # 2
+ def entry = jit_frame1 # 3
+ entry # profile send # 4
+ entry # 5
+ }, call_threshold: 2
+ end
+
+ def test_bop_invalidation
+ assert_compiles '100', %q{
+ def test
+ eval(<<~RUBY)
+ class Integer
+ def +(_) = 100
+ end
+ RUBY
+ 1 + 2
+ end
+ test
+ }
+ end
+
+ def test_defined_with_defined_values
+ assert_compiles '["constant", "method", "global-variable"]', %q{
+ class Foo; end
+ def bar; end
+ $ruby = 1
+
+ def test = return defined?(Foo), defined?(bar), defined?($ruby)
+
+ test
+ }, insns: [:defined]
+ end
+
+ def test_defined_with_undefined_values
+ assert_compiles '[nil, nil, nil]', %q{
+ def test = return defined?(Foo), defined?(bar), defined?($ruby)
+
+ test
+ }, insns: [:defined]
+ end
+
+ def test_defined_with_method_call
+ assert_compiles '["method", nil]', %q{
+ def test = return defined?("x".reverse(1)), defined?("x".reverse(1).reverse)
+
+ test
+ }, insns: [:defined]
+ end
+
+ def test_defined_method_raise
+ assert_compiles '[nil, nil, nil]', %q{
+ class C
+ def assert_equal expected, actual
+ if expected != actual
+ raise "NO"
+ end
+ end
+
+ def test_defined_method
+ assert_equal(nil, defined?("x".reverse(1).reverse))
+ end
+ end
+
+ c = C.new
+ result = []
+ result << c.test_defined_method
+ result << c.test_defined_method
+ result << c.test_defined_method
+ result
+ }
+ end
+
+ def test_defined_yield
+ assert_compiles "nil", "defined?(yield)"
+ assert_compiles '[nil, nil, "yield"]', %q{
+ def test = defined?(yield)
+ [test, test, test{}]
+ }, call_threshold: 2, insns: [:defined]
+ end
+
+ def test_defined_yield_from_block
+ # This will do some EP hopping to find the local EP,
+ # so it's slightly different than doing it outside of a block.
+
+ assert_compiles '[nil, nil, "yield"]', %q{
+ def test
+ yield_self { yield_self { defined?(yield) } }
+ end
+
+ [test, test, test{}]
+ }, call_threshold: 2
+ end
+
+ def test_block_given_p
+ assert_compiles "false", "block_given?"
+ assert_compiles '[false, false, true]', %q{
+ def test = block_given?
+ [test, test, test{}]
+ }, call_threshold: 2, insns: [:opt_send_without_block]
+ end
+
+ def test_block_given_p_from_block
+ # This will do some EP hopping to find the local EP,
+ # so it's slightly different than doing it outside of a block.
+
+ assert_compiles '[false, false, true]', %q{
+ def test
+ yield_self { yield_self { block_given? } }
+ end
+
+ [test, test, test{}]
+ }, call_threshold: 2
+ end
+
+ def test_invokeblock_without_block_after_jit_call
+ assert_compiles '"no block given (yield)"', %q{
+ def test(*arr, &b)
+ arr.class
+ yield
+ end
+ begin
+ test
+ rescue => e
+ e.message
+ end
+ }
+ end
+
+ def test_putspecialobject_vm_core_and_cbase
+ assert_compiles '10', %q{
+ def test
+ alias bar test
+ 10
+ end
+
+ test
+ bar
+ }, insns: [:putspecialobject]
+ end
+
+ def test_putspecialobject_const_base
+ assert_compiles '1', %q{
+ Foo = 1
+
+ def test = Foo
+
+ # First call: populates the constant cache
+ test
+ # Second call: triggers ZJIT compilation with warm cache
+ # RubyVM::ZJIT.assert_compiles will panic if this fails to compile
+ test
+ }, call_threshold: 2
+ end
+
+ def test_branchnil
+ assert_compiles '[2, nil]', %q{
+ def test(x)
+ x&.succ
+ end
+ [test(1), test(nil)]
+ }, call_threshold: 1, insns: [:branchnil]
+ end
+
+ def test_nil_nil
+ assert_compiles 'true', %q{
+ def test = nil.nil?
+ test
+ }, insns: [:opt_nil_p]
+ end
+
+ def test_non_nil_nil
+ assert_compiles 'false', %q{
+ def test = 1.nil?
+ test
+ }, insns: [:opt_nil_p]
+ end
+
+ def test_getspecial_last_match
+ assert_compiles '"hello"', %q{
+ def test(str)
+ str =~ /hello/
+ $&
+ end
+ test("hello world")
+ }, insns: [:getspecial]
+ end
+
+ def test_getspecial_match_pre
+ assert_compiles '"hello "', %q{
+ def test(str)
+ str =~ /world/
+ $`
+ end
+ test("hello world")
+ }, insns: [:getspecial]
+ end
+
+ def test_getspecial_match_post
+ assert_compiles '" world"', %q{
+ def test(str)
+ str =~ /hello/
+ $'
+ end
+ test("hello world")
+ }, insns: [:getspecial]
+ end
+
+ def test_getspecial_match_last_group
+ assert_compiles '"world"', %q{
+ def test(str)
+ str =~ /(hello) (world)/
+ $+
+ end
+ test("hello world")
+ }, insns: [:getspecial]
+ end
+
+ def test_getspecial_numbered_match_1
+ assert_compiles '"hello"', %q{
+ def test(str)
+ str =~ /(hello) (world)/
+ $1
+ end
+ test("hello world")
+ }, insns: [:getspecial]
+ end
+
+ def test_getspecial_numbered_match_2
+ assert_compiles '"world"', %q{
+ def test(str)
+ str =~ /(hello) (world)/
+ $2
+ end
+ test("hello world")
+ }, insns: [:getspecial]
+ end
+
+ def test_getspecial_numbered_match_nonexistent
+ assert_compiles 'nil', %q{
+ def test(str)
+ str =~ /(hello)/
+ $2
+ end
+ test("hello world")
+ }, insns: [:getspecial]
+ end
+
+ def test_getspecial_no_match
+ assert_compiles 'nil', %q{
+ def test(str)
+ str =~ /xyz/
+ $&
+ end
+ test("hello world")
+ }, insns: [:getspecial]
+ end
+
+ def test_getspecial_complex_pattern
+ assert_compiles '"123"', %q{
+ def test(str)
+ str =~ /(\d+)/
+ $1
+ end
+ test("abc123def")
+ }, insns: [:getspecial]
+ end
+
+ def test_getspecial_multiple_groups
+ assert_compiles '"456"', %q{
+ def test(str)
+ str =~ /(\d+)-(\d+)/
+ $2
+ end
+ test("123-456")
+ }, insns: [:getspecial]
+ end
+
# tool/ruby_vm/views/*.erb relies on the zjit instructions a) being contiguous and
# b) being reliably ordered after all the other instructions.
def test_instruction_order
@@ -492,58 +3428,1111 @@ class TestZJIT < Test::Unit::TestCase
end
end
+ def test_require_rubygems
+ assert_runs 'true', %q{
+ require 'rubygems'
+ }, call_threshold: 2
+ end
+
+ def test_require_rubygems_with_auto_compact
+ omit("GC.auto_compact= support is required for this test") unless GC.respond_to?(:auto_compact=)
+ assert_runs 'true', %q{
+ GC.auto_compact = true
+ require 'rubygems'
+ }, call_threshold: 2
+ end
+
+ def test_stats_availability
+ assert_runs '[true, true]', %q{
+ def test = 1
+ test
+ [
+ RubyVM::ZJIT.stats[:zjit_insn_count] > 0,
+ RubyVM::ZJIT.stats(:zjit_insn_count) > 0,
+ ]
+ }, stats: true
+ end
+
+ def test_stats_consistency
+ assert_runs '[]', %q{
+ def test = 1
+ test # increment some counters
+
+ RubyVM::ZJIT.stats.to_a.filter_map do |key, value|
+ # The value may be incremented, but the class should stay the same
+ other_value = RubyVM::ZJIT.stats(key)
+ if value.class != other_value.class
+ [key, value, other_value]
+ end
+ end
+ }, stats: true
+ end
+
+ def test_reset_stats
+ assert_runs 'true', %q{
+ def test = 1
+ 100.times { test }
+
+ # Get initial stats and verify they're non-zero
+ initial_stats = RubyVM::ZJIT.stats
+
+ # Reset the stats
+ RubyVM::ZJIT.reset_stats!
+
+ # Get stats after reset
+ reset_stats = RubyVM::ZJIT.stats
+
+ [
+ # After reset, counters should be zero or at least much smaller
+ # (some instructions might execute between reset and reading stats)
+ :zjit_insn_count.then { |s| initial_stats[s] > 0 && reset_stats[s] < initial_stats[s] },
+ :compiled_iseq_count.then { |s| initial_stats[s] > 0 && reset_stats[s] < initial_stats[s] }
+ ].all?
+ }, stats: true
+ end
+
+ def test_zjit_option_uses_array_each_in_ruby
+ omit 'ZJIT wrongly compiles Array#each, so it is disabled for now'
+ assert_runs '"<internal:array>"', %q{
+ Array.instance_method(:each).source_location&.first
+ }
+ end
+
+ def test_profile_under_nested_jit_call
+ assert_compiles '[nil, nil, 3]', %q{
+ def profile
+ 1 + 2
+ end
+
+ def jit_call(flag)
+ if flag
+ profile
+ end
+ end
+
+ def entry(flag)
+ jit_call(flag)
+ end
+
+ [entry(false), entry(false), entry(true)]
+ }, call_threshold: 2
+ end
+
+ def test_bop_redefined
+ assert_runs '[3, :+, 100]', %q{
+ def test
+ 1 + 2
+ end
+
+ test # profile opt_plus
+ [test, Integer.class_eval { def +(_) = 100 }, test]
+ }, call_threshold: 2
+ end
+
+ def test_bop_redefined_with_adjacent_patch_points
+ assert_runs '[15, :+, 100]', %q{
+ def test
+ 1 + 2 + 3 + 4 + 5
+ end
+
+ test # profile opt_plus
+ [test, Integer.class_eval { def +(_) = 100 }, test]
+ }, call_threshold: 2
+ end
+
+ # ZJIT currently only generates a MethodRedefined patch point when the method
+ # is called on the top-level self.
+ def test_method_redefined_with_top_self
+ assert_runs '["original", "redefined"]', %q{
+ def foo
+ "original"
+ end
+
+ def test = foo
+
+ test; test
+
+ result1 = test
+
+ # Redefine the method
+ def foo
+ "redefined"
+ end
+
+ result2 = test
+
+ [result1, result2]
+ }, call_threshold: 2
+ end
+
+ def test_method_redefined_with_module
+ assert_runs '["original", "redefined"]', %q{
+ module Foo
+ def self.foo = "original"
+ end
+
+ def test = Foo.foo
+ test
+ result1 = test
+
+ def Foo.foo = "redefined"
+ result2 = test
+
+ [result1, result2]
+ }, call_threshold: 2
+ end
+
+ def test_module_name_with_guard_passes
+ assert_compiles '"Integer"', %q{
+ def test(mod)
+ mod.name
+ end
+
+ test(String)
+ test(Integer)
+ }, call_threshold: 2
+ end
+
+ def test_module_name_with_guard_side_exit
+ # This test demonstrates that the guard side exit works correctly
+ # In this case, when we call with a non-Class object, it should fall back to interpreter
+ assert_compiles '["String", "Integer", "Bar"]', %q{
+ class MyClass
+ def name = "Bar"
+ end
+
+ def test(mod)
+ mod.name
+ end
+
+ results = []
+ results << test(String)
+ results << test(Integer)
+ results << test(MyClass.new)
+
+ results
+ }, call_threshold: 2
+ end
+
+ def test_objtostring_calls_to_s_on_non_strings
+ assert_compiles '["foo", "foo"]', %q{
+ results = []
+
+ class Foo
+ def to_s
+ "foo"
+ end
+ end
+
+ def test(str)
+ "#{str}"
+ end
+
+ results << test(Foo.new)
+ results << test(Foo.new)
+
+ results
+ }
+ end
+
+ def test_objtostring_rewrite_does_not_call_to_s_on_strings
+ assert_compiles '["foo", "foo"]', %q{
+ results = []
+
+ class String
+ def to_s
+ "bad"
+ end
+ end
+
+ def test(foo)
+ "#{foo}"
+ end
+
+ results << test("foo")
+ results << test("foo")
+
+ results
+ }
+ end
+
+ def test_objtostring_rewrite_does_not_call_to_s_on_string_subclasses
+ assert_compiles '["foo", "foo"]', %q{
+ results = []
+
+ class StringSubclass < String
+ def to_s
+ "bad"
+ end
+ end
+
+ foo = StringSubclass.new("foo")
+
+ def test(str)
+ "#{str}"
+ end
+
+ results << test(foo)
+ results << test(foo)
+
+ results
+ }
+ end
+
+ def test_objtostring_profiled_string_fastpath
+ assert_compiles '"foo"', %q{
+ def test(str)
+ "#{str}"
+ end
+ test('foo'); test('foo') # profile as string
+ }, call_threshold: 2
+ end
+
+ def test_objtostring_profiled_string_subclass_fastpath
+ assert_compiles '"foo"', %q{
+ class MyString < String; end
+
+ def test(str)
+ "#{str}"
+ end
+
+ foo = MyString.new("foo")
+ test(foo); test(foo) # still profiles as string
+ }, call_threshold: 2
+ end
+
+ def test_objtostring_profiled_string_fastpath_exits_on_nonstring
+ assert_compiles '"1"', %q{
+ def test(str)
+ "#{str}"
+ end
+
+ test('foo') # profile as string
+ test(1)
+ }, call_threshold: 2
+ end
+
+ def test_objtostring_profiled_nonstring_calls_to_s
+ assert_compiles '"[1, 2, 3]"', %q{
+ def test(str)
+ "#{str}"
+ end
+
+ test([1,2,3]); # profile as nonstring
+ test([1,2,3]);
+ }, call_threshold: 2
+ end
+
+ def test_objtostring_profiled_nonstring_guard_exits_when_string
+ assert_compiles '"foo"', %q{
+ def test(str)
+ "#{str}"
+ end
+
+ test([1,2,3]); # profiles as nonstring
+ test('foo');
+ }, call_threshold: 2
+ end
+
+ def test_string_bytesize_with_guard
+ assert_compiles '5', %q{
+ def test(str)
+ str.bytesize
+ end
+
+ test('hello')
+ test('world')
+ }, call_threshold: 2
+ end
+
+ def test_string_bytesize_multibyte
+ assert_compiles '4', %q{
+ def test(s)
+ s.bytesize
+ end
+
+ test("💎")
+ }, call_threshold: 2
+ end
+
+ def test_nil_value_nil_opt_with_guard
+ assert_compiles 'true', %q{
+ def test(val) = val.nil?
+
+ test(nil)
+ test(nil)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_nil_value_nil_opt_with_guard_side_exit
+ assert_compiles 'false', %q{
+ def test(val) = val.nil?
+
+ test(nil)
+ test(nil)
+ test(1)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_true_nil_opt_with_guard
+ assert_compiles 'false', %q{
+ def test(val) = val.nil?
+
+ test(true)
+ test(true)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_true_nil_opt_with_guard_side_exit
+ assert_compiles 'true', %q{
+ def test(val) = val.nil?
+
+ test(true)
+ test(true)
+ test(nil)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_false_nil_opt_with_guard
+ assert_compiles 'false', %q{
+ def test(val) = val.nil?
+
+ test(false)
+ test(false)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_false_nil_opt_with_guard_side_exit
+ assert_compiles 'true', %q{
+ def test(val) = val.nil?
+
+ test(false)
+ test(false)
+ test(nil)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_integer_nil_opt_with_guard
+ assert_compiles 'false', %q{
+ def test(val) = val.nil?
+
+ test(1)
+ test(2)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_integer_nil_opt_with_guard_side_exit
+ assert_compiles 'true', %q{
+ def test(val) = val.nil?
+
+ test(1)
+ test(2)
+ test(nil)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_float_nil_opt_with_guard
+ assert_compiles 'false', %q{
+ def test(val) = val.nil?
+
+ test(1.0)
+ test(2.0)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_float_nil_opt_with_guard_side_exit
+ assert_compiles 'true', %q{
+ def test(val) = val.nil?
+
+ test(1.0)
+ test(2.0)
+ test(nil)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_symbol_nil_opt_with_guard
+ assert_compiles 'false', %q{
+ def test(val) = val.nil?
+
+ test(:foo)
+ test(:bar)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_symbol_nil_opt_with_guard_side_exit
+ assert_compiles 'true', %q{
+ def test(val) = val.nil?
+
+ test(:foo)
+ test(:bar)
+ test(nil)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_class_nil_opt_with_guard
+ assert_compiles 'false', %q{
+ def test(val) = val.nil?
+
+ test(String)
+ test(Integer)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_class_nil_opt_with_guard_side_exit
+ assert_compiles 'true', %q{
+ def test(val) = val.nil?
+
+ test(String)
+ test(Integer)
+ test(nil)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_module_nil_opt_with_guard
+ assert_compiles 'false', %q{
+ def test(val) = val.nil?
+
+ test(Enumerable)
+ test(Kernel)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_module_nil_opt_with_guard_side_exit
+ assert_compiles 'true', %q{
+ def test(val) = val.nil?
+
+ test(Enumerable)
+ test(Kernel)
+ test(nil)
+ }, call_threshold: 2, insns: [:opt_nil_p]
+ end
+
+ def test_basic_object_guard_works_with_immediate
+ assert_compiles 'NilClass', %q{
+ class Foo; end
+
+ def test(val) = val.class
+
+ test(Foo.new)
+ test(Foo.new)
+ test(nil)
+ }, call_threshold: 2
+ end
+
+ def test_basic_object_guard_works_with_false
+ assert_compiles 'FalseClass', %q{
+ class Foo; end
+
+ def test(val) = val.class
+
+ test(Foo.new)
+ test(Foo.new)
+ test(false)
+ }, call_threshold: 2
+ end
+
+ def test_string_concat
+ assert_compiles '"123"', %q{
+ def test = "#{1}#{2}#{3}"
+
+ test
+ }, insns: [:concatstrings]
+ end
+
+ def test_string_concat_empty
+ assert_compiles '""', %q{
+ def test = "#{}"
+
+ test
+ }, insns: [:concatstrings]
+ end
+
+ def test_regexp_interpolation
+ assert_compiles '/123/', %q{
+ def test = /#{1}#{2}#{3}/
+
+ test
+ }, insns: [:toregexp]
+ end
+
+ def test_new_range_non_leaf
+ assert_compiles '(0/1)..1', %q{
+ def jit_entry(v) = make_range_then_exit(v)
+
+ def make_range_then_exit(v)
+ range = (v..1)
+ super rescue range # TODO(alan): replace super with side-exit intrinsic
+ end
+
+ jit_entry(0) # profile
+ jit_entry(0) # compile
+ jit_entry(0/1r) # run without stub
+ }, call_threshold: 2
+ end
+
+ def test_raise_in_second_argument
+ assert_compiles '{ok: true}', %q{
+ def write(hash, key)
+ hash[key] = raise rescue true
+ hash
+ end
+
+ write({}, :ok)
+ }
+ end
+
+ def test_ivar_attr_reader_optimization_with_multi_ractor_mode
+ assert_compiles '42', %q{
+ class Foo
+ class << self
+ attr_accessor :bar
+
+ def get_bar
+ bar
+ rescue Ractor::IsolationError
+ 42
+ end
+ end
+ end
+
+ Foo.bar = [] # needs to be a ractor unshareable object
+
+ def test
+ Foo.get_bar
+ end
+
+ test
+ test
+
+ Ractor.new { test }.value
+ }, call_threshold: 2
+ end
+
+ def test_ivar_get_with_multi_ractor_mode
+ assert_compiles '42', %q{
+ class Foo
+ def self.set_bar
+ @bar = [] # needs to be a ractor unshareable object
+ end
+
+ def self.bar
+ @bar
+ rescue Ractor::IsolationError
+ 42
+ end
+ end
+
+ Foo.set_bar
+
+ def test
+ Foo.bar
+ end
+
+ test
+ test
+
+ Ractor.new { test }.value
+ }, call_threshold: 2
+ end
+
+ def test_ivar_get_with_already_multi_ractor_mode
+ assert_compiles '42', %q{
+ class Foo
+ def self.set_bar
+ @bar = [] # needs to be a ractor unshareable object
+ end
+
+ def self.bar
+ @bar
+ rescue Ractor::IsolationError
+ 42
+ end
+ end
+
+ Foo.set_bar
+ r = Ractor.new {
+ Ractor.receive
+ Foo.bar
+ }
+
+ Foo.bar
+ Foo.bar
+
+ r << :go
+ r.value
+ }, call_threshold: 2
+ end
+
+ def test_ivar_set_with_multi_ractor_mode
+ assert_compiles '42', %q{
+ class Foo
+ def self.bar
+ _foo = 1
+ _bar = 2
+ begin
+ @bar = _foo + _bar
+ rescue Ractor::IsolationError
+ 42
+ end
+ end
+ end
+
+ def test
+ Foo.bar
+ end
+
+ test
+ test
+
+ Ractor.new { test }.value
+ }
+ end
+
+ def test_struct_set
+ assert_compiles '[42, 42, :frozen_error]', %q{
+ C = Struct.new(:foo).new(1)
+
+ def test
+ C.foo = Object.new
+ 42
+ end
+
+ r = [test, test]
+ C.freeze
+ r << begin
+ test
+ rescue FrozenError
+ :frozen_error
+ end
+ }, call_threshold: 2
+ end
+
+ def test_global_tracepoint
+ assert_compiles 'true', %q{
+ def foo = 1
+
+ foo
+ foo
+
+ called = false
+
+ tp = TracePoint.new(:return) { |event|
+ if event.method_id == :foo
+ called = true
+ end
+ }
+ tp.enable do
+ foo
+ end
+ called
+ }
+ end
+
+ def test_local_tracepoint
+ assert_compiles 'true', %q{
+ def foo = 1
+
+ foo
+ foo
+
+ called = false
+
+ tp = TracePoint.new(:return) { |_| called = true }
+ tp.enable(target: method(:foo)) do
+ foo
+ end
+ called
+ }
+ end
+
+ def test_line_tracepoint_on_c_method
+ assert_compiles '"[[:line, true]]"', %q{
+ events = []
+ events.instance_variable_set(
+ :@tp,
+ TracePoint.new(:line) { |tp| events << [tp.event, tp.lineno] if tp.path == __FILE__ }
+ )
+ def events.to_str
+ @tp.enable; ''
+ end
+
+ # Stay in generated code while enabling tracing
+ def events.compiled(obj)
+ String(obj)
+ @tp.disable; __LINE__
+ end
+
+ line = events.compiled(events)
+ events[0][-1] = (events[0][-1] == line)
+
+ events.to_s # can't dump events as it's a singleton object AND it has a TracePoint instance variable, which also can't be dumped
+ }
+ end
+
+ def test_targeted_line_tracepoint_in_c_method_call
+ assert_compiles '"[true]"', %q{
+ events = []
+ events.instance_variable_set(:@tp, TracePoint.new(:line) { |tp| events << tp.lineno })
+ def events.to_str
+ @tp.enable(target: method(:compiled))
+ ''
+ end
+
+ # Stay in generated code while enabling tracing
+ def events.compiled(obj)
+ String(obj)
+ __LINE__
+ end
+
+ line = events.compiled(events)
+ events[0] = (events[0] == line)
+
+ events.to_s # can't dump events as it's a singleton object AND it has a TracePoint instance variable, which also can't be dumped
+ }
+ end
+
+ def test_opt_case_dispatch
+ assert_compiles '[true, false]', %q{
+ def test(x)
+ case x
+ when :foo
+ true
+ else
+ false
+ end
+ end
+
+ results = []
+ results << test(:foo)
+ results << test(1)
+ results
+ }, insns: [:opt_case_dispatch]
+ end
+
+ def test_stack_overflow
+ assert_compiles 'nil', %q{
+ def recurse(n)
+ return if n == 0
+ recurse(n-1)
+ nil # no tail call
+ end
+
+ recurse(2)
+ recurse(2)
+ begin
+ recurse(20_000)
+ rescue SystemStackError
+ # Not asserting an exception is raised here since main
+ # thread stack size is environment-sensitive. Only
+ # that we don't crash or infinite loop.
+ end
+ }, call_threshold: 2
+ end
+
+ def test_invokeblock
+ assert_compiles '42', %q{
+ def test
+ yield
+ end
+ test { 42 }
+ }, insns: [:invokeblock]
+ end
+
+ def test_invokeblock_with_args
+ assert_compiles '3', %q{
+ def test(x, y)
+ yield x, y
+ end
+ test(1, 2) { |a, b| a + b }
+ }, insns: [:invokeblock]
+ end
+
+ def test_invokeblock_no_block_given
+ assert_compiles ':error', %q{
+ def test
+ yield rescue :error
+ end
+ test
+ }, insns: [:invokeblock]
+ end
+
+ def test_invokeblock_multiple_yields
+ assert_compiles "[1, 2, 3]", %q{
+ results = []
+ def test
+ yield 1
+ yield 2
+ yield 3
+ end
+ test { |x| results << x }
+ results
+ }, insns: [:invokeblock]
+ end
+
+ def test_ccall_variadic_with_multiple_args
+ assert_compiles "[1, 2, 3]", %q{
+ def test
+ a = []
+ a.push(1, 2, 3)
+ a
+ end
+
+ test
+ test
+ }, insns: [:opt_send_without_block]
+ end
+
+ def test_ccall_variadic_with_no_args
+ assert_compiles "[1]", %q{
+ def test
+ a = [1]
+ a.push
+ end
+
+ test
+ test
+ }, insns: [:opt_send_without_block]
+ end
+
+ def test_ccall_variadic_with_no_args_causing_argument_error
+ assert_compiles ":error", %q{
+ def test
+ format
+ rescue ArgumentError
+ :error
+ end
+
+ test
+ test
+ }, insns: [:opt_send_without_block]
+ end
+
+ def test_allocating_in_hir_c_method_is
+ assert_compiles ":k", %q{
+ # Put opt_new in a frame JIT code sets up that doesn't set cfp->pc
+ def a(f) = test(f)
+ def test(f) = (f.new if f)
+ # A parallel couple methods that will set PC at the same stack height
+ def second = third
+ def third = nil
+
+ a(nil)
+ a(nil)
+
+ class Foo
+ def self.new = :k
+ end
+
+ second
+
+ a(Foo)
+ }, call_threshold: 2, insns: [:opt_new]
+ end
+
+ def test_singleton_class_invalidation_annotated_ccall
+ assert_compiles '[false, true]', %q{
+ def define_singleton(obj, define)
+ if define
+ # Wrap in C method frame to avoid exiting JIT on defineclass
+ [nil].reverse_each do
+ class << obj
+ def ==(_)
+ true
+ end
+ end
+ end
+ end
+ false
+ end
+
+ def test(define)
+ obj = BasicObject.new
+ # This == call gets compiled to a CCall
+ obj == define_singleton(obj, define)
+ end
+
+ result = []
+ result << test(false) # Compiles BasicObject#==
+ result << test(true) # Should use singleton#== now
+ result
+ }, call_threshold: 2
+ end
+
+ def test_singleton_class_invalidation_optimized_variadic_ccall
+ assert_compiles '[1, 1000]', %q{
+ def define_singleton(arr, define)
+ if define
+ # Wrap in C method frame to avoid exiting JIT on defineclass
+ [nil].reverse_each do
+ class << arr
+ def push(x)
+ super(x * 1000)
+ end
+ end
+ end
+ end
+ 1
+ end
+
+ def test(define)
+ arr = []
+ val = define_singleton(arr, define)
+ arr.push(val) # This CCall should be invalidated if singleton was defined
+ arr[0]
+ end
+
+ result = []
+ result << test(false) # Compiles Array#push as CCall
+ result << test(true) # Singleton defined, CCall should be invalidated
+ result
+ }, call_threshold: 2
+ end
+
+ def test_regression_cfp_sp_set_correctly_before_leaf_gc_call
+ assert_compiles ':ok', %q{
+ def check(l, r)
+ return 1 unless l
+ 1 + check(*l) + check(*r)
+ end
+
+ def tree(depth)
+ # This duparray is our leaf-gc target.
+ return [nil, nil] unless depth > 0
+
+ # Modify the local and pass it to the following calls.
+ depth -= 1
+ [tree(depth), tree(depth)]
+ end
+
+ def test
+ GC.stress = true
+ 2.times do
+ t = tree(11)
+ check(*t)
+ end
+ :ok
+ end
+
+ test
+ }, call_threshold: 14, num_profiles: 5
+ end
+
private
# Assert that every method call in `test_script` can be compiled by ZJIT
# at a given call_threshold
- def assert_compiles(expected, test_script, **opts)
+ def assert_compiles(expected, test_script, insns: [], **opts)
+ assert_runs(expected, test_script, insns:, assert_compiles: true, **opts)
+ end
+
+ # Assert that `test_script` runs successfully with ZJIT enabled.
+ # Unlike `assert_compiles`, `assert_runs(assert_compiles: false)`
+ # allows ZJIT to skip compiling methods.
+ def assert_runs(expected, test_script, insns: [], assert_compiles: false, **opts)
pipe_fd = 3
+ disasm_method = :test
script = <<~RUBY
- _test_proc = -> {
- RubyVM::ZJIT.assert_compiles
- #{test_script}
+ ret_val = (_test_proc = -> { #{('RubyVM::ZJIT.assert_compiles; ' if assert_compiles)}#{test_script.lstrip} }).call
+ result = {
+ ret_val:,
+ #{ unless insns.empty?
+ "insns: RubyVM::InstructionSequence.of(method(#{disasm_method.inspect})).to_a"
+ end}
}
- result = _test_proc.call
- IO.open(#{pipe_fd}).write(result.inspect)
+ IO.open(#{pipe_fd}).write(Marshal.dump(result))
RUBY
- status, out, err, actual = eval_with_jit(script, pipe_fd:, **opts)
+ out, err, status, result = eval_with_jit(script, pipe_fd:, **opts)
+ assert_success(out, err, status)
- message = "exited with status #{status.to_i}"
- message << "\nstdout:\n```\n#{out}```\n" unless out.empty?
- message << "\nstderr:\n```\n#{err}```\n" unless err.empty?
- assert status.success?, message
+ result = Marshal.load(result)
+ assert_equal(expected, result.fetch(:ret_val).inspect)
+
+ unless insns.empty?
+ iseq = result.fetch(:insns)
+ assert_equal(
+ "YARVInstructionSequence/SimpleDataFormat",
+ iseq.first,
+ "Failed to get ISEQ disassembly. " \
+ "Make sure to put code directly under the '#{disasm_method}' method."
+ )
+ iseq_insns = iseq.last
- assert_equal expected, actual
+ expected_insns = Set.new(insns)
+ iseq_insns.each do
+ next unless it.is_a?(Array)
+ expected_insns.delete(it.first)
+ end
+ assert(expected_insns.empty?, -> { "Not present in ISeq: #{expected_insns.to_a}" })
+ end
end
# Run a Ruby process with ZJIT options and a pipe for writing test results
- def eval_with_jit(script, call_threshold: 1, num_profiles: 1, timeout: 1000, pipe_fd:, debug: true)
- args = [
- "--disable-gems",
- "--zjit-call-threshold=#{call_threshold}",
- "--zjit-num-profiles=#{num_profiles}",
- ]
- args << "--zjit-debug" if debug
+ def eval_with_jit(
+ script,
+ call_threshold: 1,
+ num_profiles: 1,
+ zjit: true,
+ stats: false,
+ debug: true,
+ allowed_iseqs: nil,
+ timeout: 1000,
+ pipe_fd: nil
+ )
+ args = ["--disable-gems"]
+ if zjit
+ args << "--zjit-call-threshold=#{call_threshold}"
+ args << "--zjit-num-profiles=#{num_profiles}"
+ case stats
+ when true
+ args << "--zjit-stats"
+ when :quiet
+ args << "--zjit-stats-quiet"
+ else
+ args << "--zjit-stats=#{stats}" if stats
+ end
+ args << "--zjit-debug" if debug
+ if allowed_iseqs
+ jitlist = Tempfile.new("jitlist")
+ jitlist.write(allowed_iseqs)
+ jitlist.close
+ args << "--zjit-allowed-iseqs=#{jitlist.path}"
+ end
+ end
args << "-e" << script_shell_encode(script)
- pipe_r, pipe_w = IO.pipe
- # Separate thread so we don't deadlock when
- # the child ruby blocks writing the output to pipe_fd
- pipe_out = nil
- pipe_reader = Thread.new do
- pipe_out = pipe_r.read
- pipe_r.close
+ ios = {}
+ if pipe_fd
+ pipe_r, pipe_w = IO.pipe
+ # Separate thread so we don't deadlock when
+ # the child ruby blocks writing the output to pipe_fd
+ pipe_out = nil
+ pipe_reader = Thread.new do
+ pipe_out = pipe_r.read
+ pipe_r.close
+ end
+ ios[pipe_fd] = pipe_w
+ end
+ result = EnvUtil.invoke_ruby(args, '', true, true, rubybin: RbConfig.ruby, timeout: timeout, ios:)
+ if pipe_fd
+ pipe_w.close
+ pipe_reader.join(timeout)
+ result << pipe_out
end
- out, err, status = EnvUtil.invoke_ruby(args, '', true, true, rubybin: RbConfig.ruby, timeout: timeout, ios: { pipe_fd => pipe_w })
- pipe_w.close
- pipe_reader.join(timeout)
- [status, out, err, pipe_out]
+ result
ensure
pipe_reader&.kill
pipe_reader&.join(timeout)
pipe_r&.close
pipe_w&.close
+ jitlist&.unlink
+ end
+
+ def assert_success(out, err, status)
+ message = "exited with status #{status.to_i}"
+ message << "\nstdout:\n```\n#{out}```\n" unless out.empty?
+ message << "\nstderr:\n```\n#{err}```\n" unless err.empty?
+ assert status.success?, message
end
def script_shell_encode(s)