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_case_comprehensive.rb71
-rw-r--r--test/ruby/enc/test_case_mapping.rb43
-rw-r--r--test/ruby/enc/test_case_options.rb12
-rw-r--r--test/ruby/enc/test_cesu8.rb113
-rw-r--r--test/ruby/enc/test_emoji_breaks.rb155
-rw-r--r--test/ruby/enc/test_grapheme_breaks.rb116
-rw-r--r--test/ruby/enc/test_regex_casefold.rb6
-rw-r--r--test/ruby/marshaltestlib.rb4
-rw-r--r--test/ruby/sentence.rb4
-rw-r--r--test/ruby/test_alias.rb116
-rw-r--r--test/ruby/test_allocation.rb957
-rw-r--r--test/ruby/test_argf.rb264
-rw-r--r--test/ruby/test_arithmetic_sequence.rb491
-rw-r--r--test/ruby/test_arity.rb43
-rw-r--r--test/ruby/test_array.rb1136
-rw-r--r--test/ruby/test_assignment.rb216
-rw-r--r--test/ruby/test_ast.rb1753
-rw-r--r--test/ruby/test_autoload.rb323
-rw-r--r--test/ruby/test_backtrace.rb177
-rw-r--r--test/ruby/test_basicinstructions.rb6
-rw-r--r--test/ruby/test_beginendblock.rb14
-rw-r--r--test/ruby/test_bignum.rb167
-rw-r--r--test/ruby/test_box.rb874
-rw-r--r--test/ruby/test_call.rb1391
-rw-r--r--test/ruby/test_case.rb21
-rw-r--r--test/ruby/test_class.rb370
-rw-r--r--test/ruby/test_clone.rb88
-rw-r--r--test/ruby/test_comparable.rb40
-rw-r--r--test/ruby/test_compile_prism.rb2755
-rw-r--r--test/ruby/test_complex.rb353
-rw-r--r--test/ruby/test_complex2.rb2
-rw-r--r--test/ruby/test_complexrational.rb12
-rw-r--r--test/ruby/test_const.rb39
-rw-r--r--test/ruby/test_continuation.rb21
-rw-r--r--test/ruby/test_data.rb290
-rw-r--r--test/ruby/test_default_gems.rb29
-rw-r--r--test/ruby/test_defined.rb242
-rw-r--r--test/ruby/test_dir.rb481
-rw-r--r--test/ruby/test_dir_m17n.rb121
-rw-r--r--test/ruby/test_dup.rb110
-rw-r--r--test/ruby/test_econv.rb29
-rw-r--r--test/ruby/test_encoding.rb79
-rw-r--r--test/ruby/test_enum.rb341
-rw-r--r--test/ruby/test_enumerator.rb436
-rw-r--r--test/ruby/test_env.rb1068
-rw-r--r--test/ruby/test_eval.rb175
-rw-r--r--test/ruby/test_exception.rb694
-rw-r--r--test/ruby/test_fiber.rb270
-rw-r--r--test/ruby/test_file.rb477
-rw-r--r--test/ruby/test_file_exhaustive.rb350
-rw-r--r--test/ruby/test_fixnum.rb1
-rw-r--r--test/ruby/test_flip.rb3
-rw-r--r--test/ruby/test_float.rb147
-rw-r--r--test/ruby/test_fnmatch.rb31
-rw-r--r--test/ruby/test_frozen.rb46
-rw-r--r--test/ruby/test_frozen_error.rb57
-rw-r--r--test/ruby/test_gc.rb688
-rw-r--r--test/ruby/test_gc_compact.rb488
-rw-r--r--test/ruby/test_hash.rb1306
-rw-r--r--test/ruby/test_ifunless.rb2
-rw-r--r--test/ruby/test_inlinecache.rb110
-rw-r--r--test/ruby/test_insns_leaf.rb46
-rw-r--r--test/ruby/test_integer.rb309
-rw-r--r--test/ruby/test_integer_comb.rb23
-rw-r--r--test/ruby/test_io.rb1075
-rw-r--r--test/ruby/test_io_buffer.rb933
-rw-r--r--test/ruby/test_io_m17n.rb210
-rw-r--r--test/ruby/test_io_timeout.rb58
-rw-r--r--test/ruby/test_iseq.rb674
-rw-r--r--test/ruby/test_iterator.rb46
-rw-r--r--test/ruby/test_key_error.rb42
-rw-r--r--test/ruby/test_keyword.rb3960
-rw-r--r--test/ruby/test_lambda.rb157
-rw-r--r--test/ruby/test_lazy_enumerator.rb141
-rw-r--r--test/ruby/test_literal.rb174
-rw-r--r--test/ruby/test_m17n.rb329
-rw-r--r--test/ruby/test_m17n_comb.rb42
-rw-r--r--test/ruby/test_marshal.rb370
-rw-r--r--test/ruby/test_math.rb95
-rw-r--r--test/ruby/test_memory_view.rb341
-rw-r--r--test/ruby/test_method.rb956
-rw-r--r--test/ruby/test_method_cache.rb76
-rw-r--r--test/ruby/test_mixed_unicode_escapes.rb2
-rw-r--r--test/ruby/test_module.rb1366
-rw-r--r--test/ruby/test_name_error.rb156
-rw-r--r--test/ruby/test_nomethod_error.rb137
-rw-r--r--test/ruby/test_not.rb2
-rw-r--r--test/ruby/test_notimp.rb85
-rw-r--r--test/ruby/test_numeric.rb154
-rw-r--r--test/ruby/test_object.rb293
-rw-r--r--test/ruby/test_object_id.rb303
-rw-r--r--test/ruby/test_objectspace.rb131
-rw-r--r--test/ruby/test_optimization.rb551
-rw-r--r--test/ruby/test_pack.rb198
-rw-r--r--test/ruby/test_parse.rb1263
-rw-r--r--test/ruby/test_path.rb8
-rw-r--r--test/ruby/test_pattern_matching.rb1763
-rw-r--r--test/ruby/test_pipe.rb19
-rw-r--r--test/ruby/test_primitive.rb78
-rw-r--r--test/ruby/test_proc.rb946
-rw-r--r--test/ruby/test_process.rb826
-rw-r--r--test/ruby/test_ractor.rb229
-rw-r--r--test/ruby/test_rand.rb369
-rw-r--r--test/ruby/test_random_formatter.rb183
-rw-r--r--test/ruby/test_range.rb1007
-rw-r--r--test/ruby/test_rational.rb121
-rw-r--r--test/ruby/test_refinement.rb903
-rw-r--r--test/ruby/test_regexp.rb1193
-rw-r--r--test/ruby/test_require.rb404
-rw-r--r--test/ruby/test_require_lib.rb28
-rw-r--r--test/ruby/test_rubyoptions.rb622
-rw-r--r--test/ruby/test_rubyvm.rb59
-rw-r--r--test/ruby/test_set.rb1052
-rw-r--r--test/ruby/test_settracefunc.rb1595
-rw-r--r--test/ruby/test_shapes.rb1212
-rw-r--r--test/ruby/test_signal.rb97
-rw-r--r--test/ruby/test_sleep.rb35
-rw-r--r--test/ruby/test_sprintf.rb54
-rw-r--r--test/ruby/test_stack.rb81
-rw-r--r--test/ruby/test_string.rb2071
-rw-r--r--test/ruby/test_string_memory.rb65
-rw-r--r--test/ruby/test_struct.rb169
-rw-r--r--test/ruby/test_super.rb218
-rw-r--r--test/ruby/test_symbol.rb130
-rw-r--r--test/ruby/test_syntax.rb1359
-rw-r--r--test/ruby/test_system.rb54
-rw-r--r--test/ruby/test_thread.rb462
-rw-r--r--test/ruby/test_thread_cv.rb246
-rw-r--r--test/ruby/test_thread_queue.rb730
-rw-r--r--test/ruby/test_time.rb406
-rw-r--r--test/ruby/test_time_tz.rb381
-rw-r--r--test/ruby/test_trace.rb11
-rw-r--r--test/ruby/test_transcode.rb694
-rw-r--r--test/ruby/test_undef.rb18
-rw-r--r--test/ruby/test_variable.rb399
-rw-r--r--test/ruby/test_vm_dump.rb14
-rw-r--r--test/ruby/test_warning.rb32
-rw-r--r--test/ruby/test_weakkeymap.rb159
-rw-r--r--test/ruby/test_weakmap.rb186
-rw-r--r--test/ruby/test_whileuntil.rb18
-rw-r--r--test/ruby/test_yield.rb2
-rw-r--r--test/ruby/test_yjit.rb1985
-rw-r--r--test/ruby/test_yjit_exit_locations.rb96
-rw-r--r--test/ruby/test_zjit.rb4542
172 files changed, 57423 insertions, 5420 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_case_comprehensive.rb b/test/ruby/enc/test_case_comprehensive.rb
index cd6447e928..b812b88b83 100644
--- a/test/ruby/enc/test_case_comprehensive.rb
+++ b/test/ruby/enc/test_case_comprehensive.rb
@@ -24,7 +24,7 @@ class TestComprehensiveCaseMapping < Test::Unit::TestCase
def test_data_files_available
unless TestComprehensiveCaseMapping.data_files_available?
- skip "Unicode data files not available in #{UNICODE_DATA_PATH}."
+ omit "Unicode data files not available in #{UNICODE_DATA_PATH}."
end
end
end
@@ -37,7 +37,7 @@ TestComprehensiveCaseMapping.data_files_available? and class TestComprehensiveC
end
def self.read_data_file(filename)
- IO.foreach(expand_filename(filename), encoding: Encoding::ASCII_8BIT) do |line|
+ File.foreach(expand_filename(filename), encoding: Encoding::ASCII_8BIT) do |line|
if $. == 1
if filename == 'UnicodeData'
elsif line.start_with?("# #{filename}-#{UNICODE_VERSION}.txt")
@@ -73,7 +73,11 @@ TestComprehensiveCaseMapping.data_files_available? and class TestComprehensiveC
@@codepoints << code
upcase[code] = hex2utf8 data[12] unless data[12].empty?
downcase[code] = hex2utf8 data[13] unless data[13].empty?
- titlecase[code] = hex2utf8 data[14] unless data[14].empty?
+ if code>="\u1C90" and code<="\u1CBF" # exception for Georgian: use lowercase for titlecase
+ titlecase[code] = hex2utf8(data[13]) unless data[13].empty?
+ else
+ titlecase[code] = hex2utf8 data[14] unless data[14].empty?
+ end
end
read_data_file('CaseFolding') do |code, data|
casefold[code] = hex2utf8(data[2]) if data[1] =~ /^[CF]$/
@@ -157,15 +161,14 @@ TestComprehensiveCaseMapping.data_files_available? and class TestComprehensiveC
end
end
- def self.generate_case_mapping_tests(encoding)
+ def self.generate_single_byte_case_mapping_tests(encoding)
all_tests
- # preselect codepoints to speed up testing for small encodings
- codepoints = @@codepoints.select do |code|
+ # precalculate codepoints to speed up testing for small encodings
+ codepoints = []
+ (0..255).each do |cp|
begin
- code.encode(encoding)
- true
- rescue Encoding::UndefinedConversionError
- false
+ codepoints << cp.chr(encoding).encode('UTF-8')
+ rescue Encoding::UndefinedConversionError, RangeError
end
end
all_tests.each do |test|
@@ -260,23 +263,23 @@ TestComprehensiveCaseMapping.data_files_available? and class TestComprehensiveC
end
end
- generate_case_mapping_tests 'US-ASCII'
- generate_case_mapping_tests 'ASCII-8BIT'
- generate_case_mapping_tests 'ISO-8859-1'
- generate_case_mapping_tests 'ISO-8859-2'
- generate_case_mapping_tests 'ISO-8859-3'
- generate_case_mapping_tests 'ISO-8859-4'
- generate_case_mapping_tests 'ISO-8859-5'
- generate_case_mapping_tests 'ISO-8859-6'
- generate_case_mapping_tests 'ISO-8859-7'
- generate_case_mapping_tests 'ISO-8859-8'
- generate_case_mapping_tests 'ISO-8859-9'
- generate_case_mapping_tests 'ISO-8859-10'
- generate_case_mapping_tests 'ISO-8859-11'
- generate_case_mapping_tests 'ISO-8859-13'
- generate_case_mapping_tests 'ISO-8859-14'
- generate_case_mapping_tests 'ISO-8859-15'
- generate_case_mapping_tests 'ISO-8859-16'
+ generate_single_byte_case_mapping_tests 'US-ASCII'
+ generate_single_byte_case_mapping_tests 'ASCII-8BIT'
+ generate_single_byte_case_mapping_tests 'ISO-8859-1'
+ generate_single_byte_case_mapping_tests 'ISO-8859-2'
+ generate_single_byte_case_mapping_tests 'ISO-8859-3'
+ generate_single_byte_case_mapping_tests 'ISO-8859-4'
+ generate_single_byte_case_mapping_tests 'ISO-8859-5'
+ generate_single_byte_case_mapping_tests 'ISO-8859-6'
+ generate_single_byte_case_mapping_tests 'ISO-8859-7'
+ generate_single_byte_case_mapping_tests 'ISO-8859-8'
+ generate_single_byte_case_mapping_tests 'ISO-8859-9'
+ generate_single_byte_case_mapping_tests 'ISO-8859-10'
+ generate_single_byte_case_mapping_tests 'ISO-8859-11'
+ generate_single_byte_case_mapping_tests 'ISO-8859-13'
+ generate_single_byte_case_mapping_tests 'ISO-8859-14'
+ generate_single_byte_case_mapping_tests 'ISO-8859-15'
+ generate_single_byte_case_mapping_tests 'ISO-8859-16'
generate_ascii_only_case_mapping_tests 'KOI8-R'
generate_ascii_only_case_mapping_tests 'KOI8-U'
generate_ascii_only_case_mapping_tests 'Big5'
@@ -287,14 +290,14 @@ TestComprehensiveCaseMapping.data_files_available? and class TestComprehensiveC
generate_ascii_only_case_mapping_tests 'GBK'
generate_ascii_only_case_mapping_tests 'Shift_JIS'
generate_ascii_only_case_mapping_tests 'Windows-31J'
- generate_case_mapping_tests 'Windows-1250'
- generate_case_mapping_tests 'Windows-1251'
- generate_case_mapping_tests 'Windows-1252'
- generate_case_mapping_tests 'Windows-1253'
- generate_case_mapping_tests 'Windows-1254'
- generate_case_mapping_tests 'Windows-1255'
+ generate_single_byte_case_mapping_tests 'Windows-1250'
+ generate_single_byte_case_mapping_tests 'Windows-1251'
+ generate_single_byte_case_mapping_tests 'Windows-1252'
+ generate_single_byte_case_mapping_tests 'Windows-1253'
+ generate_single_byte_case_mapping_tests 'Windows-1254'
+ generate_single_byte_case_mapping_tests 'Windows-1255'
generate_ascii_only_case_mapping_tests 'Windows-1256'
- generate_case_mapping_tests 'Windows-1257'
+ generate_single_byte_case_mapping_tests 'Windows-1257'
generate_unicode_case_mapping_tests 'UTF-8'
generate_unicode_case_mapping_tests 'UTF-16BE'
generate_unicode_case_mapping_tests 'UTF-16LE'
diff --git a/test/ruby/enc/test_case_mapping.rb b/test/ruby/enc/test_case_mapping.rb
index d095cd569c..a7d1ed0d16 100644
--- a/test/ruby/enc/test_case_mapping.rb
+++ b/test/ruby/enc/test_case_mapping.rb
@@ -47,7 +47,7 @@ class TestCaseMappingPreliminary < Test::Unit::TestCase
# different properties; careful: roundtrip isn't always guaranteed
def check_swapcase_properties(expected, start, *flags)
assert_equal expected, start.swapcase(*flags)
- temp = start
+ temp = +start
assert_equal expected, temp.swapcase!(*flags)
assert_equal start, start.swapcase(*flags).swapcase(*flags)
assert_equal expected, expected.swapcase(*flags).swapcase(*flags)
@@ -61,10 +61,10 @@ class TestCaseMappingPreliminary < Test::Unit::TestCase
end
def test_invalid
- assert_raise(ArgumentError, "Should not be possible to upcase invalid string.") { "\xEB".force_encoding('UTF-8').upcase }
- assert_raise(ArgumentError, "Should not be possible to downcase invalid string.") { "\xEB".force_encoding('UTF-8').downcase }
- assert_raise(ArgumentError, "Should not be possible to capitalize invalid string.") { "\xEB".force_encoding('UTF-8').capitalize }
- assert_raise(ArgumentError, "Should not be possible to swapcase invalid string.") { "\xEB".force_encoding('UTF-8').swapcase }
+ assert_raise(ArgumentError, "Should not be possible to upcase invalid string.") { "\xEB".dup.force_encoding('UTF-8').upcase }
+ assert_raise(ArgumentError, "Should not be possible to downcase invalid string.") { "\xEB".dup.force_encoding('UTF-8').downcase }
+ assert_raise(ArgumentError, "Should not be possible to capitalize invalid string.") { "\xEB".dup.force_encoding('UTF-8').capitalize }
+ assert_raise(ArgumentError, "Should not be possible to swapcase invalid string.") { "\xEB".dup.force_encoding('UTF-8').swapcase }
end
def test_general
@@ -187,6 +187,39 @@ class TestCaseMappingPreliminary < Test::Unit::TestCase
assert_equal 0, "\ua64A" =~ /\uA64B/i
end
+ def test_georgian_canary
+ message = "Reexamine implementation of Georgian in String#capitalize"
+ assert_equal false, "\u1CBB".match?(/\p{assigned}/), message
+ assert_equal false, "\u1CBC".match?(/\p{assigned}/), message
+ end
+
+ def test_georgian_unassigned
+ message = "Unassigned codepoints should not be converted"
+ assert_equal "\u1CBB", "\u1CBB".capitalize, message
+ assert_equal "\u1CBC", "\u1CBC".capitalize, message
+ end
+
+ def test_georgian_capitalize
+ assert_equal "\u10D0\u10D1\u10D2", "\u1C90\u1C91\u1C92".capitalize
+ assert_equal "\u10D0\u10D1\u10D2", "\u1C90\u1C91\u10D2".capitalize
+ assert_equal "\u10D0\u10D1\u10D2", "\u1C90\u10D1\u1C92".capitalize
+ assert_equal "\u10D0\u10D1\u10D2", "\u1C90\u10D1\u10D2".capitalize
+ assert_equal "\u10D0\u10D1\u10D2", "\u10D0\u1C91\u1C92".capitalize
+ assert_equal "\u10D0\u10D1\u10D2", "\u10D0\u1C91\u10D2".capitalize
+ assert_equal "\u10D0\u10D1\u10D2", "\u10D0\u10D1\u1C92".capitalize
+ assert_equal "\u10D0\u10D1\u10D2", "\u10D0\u10D1\u10D2".capitalize
+ end
+
+ def test_shift_jis_downcase_ascii
+ s = ("A".."Z").map {|c| "\x89#{c}"}.join("").force_encoding("Shift_JIS")
+ assert_equal s, s.downcase(:ascii)
+ end
+
+ def test_shift_jis_upcase_ascii
+ s = ("a".."z").map {|c| "\x89#{c}"}.join("").force_encoding("Shift_JIS")
+ assert_equal s, s.upcase(:ascii)
+ end
+
def no_longer_a_test_buffer_allocations
assert_equal 'TURKISH*ı'*10, ('I'*10).downcase(:turkic)
assert_equal 'TURKISH*ı'*100, ('I'*100).downcase(:turkic)
diff --git a/test/ruby/enc/test_case_options.rb b/test/ruby/enc/test_case_options.rb
index e9bf50fcfc..e9c81d804e 100644
--- a/test/ruby/enc/test_case_options.rb
+++ b/test/ruby/enc/test_case_options.rb
@@ -19,7 +19,7 @@ class TestCaseOptions < Test::Unit::TestCase
def assert_raise_both_types(*options)
assert_raise_functional_operations 'a', *options
- assert_raise_bang_operations 'a', *options
+ assert_raise_bang_operations(+'a', *options)
assert_raise_functional_operations :a, *options
end
@@ -51,7 +51,7 @@ class TestCaseOptions < Test::Unit::TestCase
def assert_okay_both_types(*options)
assert_okay_functional_operations 'a', *options
- assert_okay_bang_operations 'a', *options
+ assert_okay_bang_operations(+'a', *options)
assert_okay_functional_operations :a, *options
end
@@ -69,10 +69,10 @@ class TestCaseOptions < Test::Unit::TestCase
assert_raise(ArgumentError) { 'a'.upcase :fold }
assert_raise(ArgumentError) { 'a'.capitalize :fold }
assert_raise(ArgumentError) { 'a'.swapcase :fold }
- assert_nothing_raised { 'a'.downcase! :fold }
- assert_raise(ArgumentError) { 'a'.upcase! :fold }
- assert_raise(ArgumentError) { 'a'.capitalize! :fold }
- assert_raise(ArgumentError) { 'a'.swapcase! :fold }
+ assert_nothing_raised { 'a'.dup.downcase! :fold }
+ assert_raise(ArgumentError) { 'a'.dup.upcase! :fold }
+ assert_raise(ArgumentError) { 'a'.dup.capitalize! :fold }
+ assert_raise(ArgumentError) { 'a'.dup.swapcase! :fold }
assert_nothing_raised { :a.downcase :fold }
assert_raise(ArgumentError) { :a.upcase :fold }
assert_raise(ArgumentError) { :a.capitalize :fold }
diff --git a/test/ruby/enc/test_cesu8.rb b/test/ruby/enc/test_cesu8.rb
new file mode 100644
index 0000000000..68a08389ea
--- /dev/null
+++ b/test/ruby/enc/test_cesu8.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestCESU8 < Test::Unit::TestCase
+
+ def encdump(obj)
+ case obj
+ when String
+ obj.dump
+ when Regexp
+ "Regexp.new(#{encdump(obj.source)}, #{obj.options})"
+ else
+ raise Argument, "unexpected: #{obj.inspect}"
+ end
+ end
+
+ def enccall(recv, meth, *args)
+ desc = ''
+ if String === recv
+ desc << encdump(recv)
+ else
+ desc << recv.inspect
+ end
+ desc << '.' << meth.to_s
+ if !args.empty?
+ desc << '('
+ args.each_with_index {|a, i|
+ desc << ',' if 0 < i
+ if String === a
+ desc << encdump(a)
+ else
+ desc << a.inspect
+ end
+ }
+ desc << ')'
+ end
+ result = nil
+ assert_nothing_raised(desc) {
+ result = recv.send(meth, *args)
+ }
+ result
+ end
+
+ def assert_str_equal(expected, actual, message=nil)
+ full_message = build_message(message, <<EOT)
+#{encdump expected} expected but not equal to
+#{encdump actual}.
+EOT
+ assert_equal(expected, actual, full_message)
+ end
+
+ # tests start
+
+ def test_cesu8_valid_encoding
+ all_assertions do |a|
+ [
+ "\x00",
+ "\x7f",
+ "\u0080",
+ "\u07ff",
+ "\u0800",
+ "\ud7ff",
+ "\xed\xa0\x80\xed\xb0\x80",
+ "\xed\xaf\xbf\xed\xbf\xbf",
+ "\ue000",
+ "\uffff",
+ ].each {|s|
+ s.force_encoding("cesu-8")
+ a.for(s) {
+ assert_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?")
+ }
+ }
+ [
+ "\x80",
+ "\xc0\x80",
+ "\xc0",
+ "\xe0\x80\x80",
+ "\xed\xa0\x80",
+ "\xed\xb0\x80\xed\xb0\x80",
+ "\xe0",
+ "\xff",
+ ].each {|s|
+ s.force_encoding("cesu-8")
+ a.for(s) {
+ assert_not_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?")
+ }
+ }
+ end
+ end
+
+ def test_cesu8_ord
+ [
+ ["\x00", 0],
+ ["\x7f", 0x7f],
+ ["\u0080", 0x80],
+ ["\u07ff", 0x7ff],
+ ["\u0800", 0x800],
+ ["\ud7ff", 0xd7ff],
+ ["\xed\xa0\x80\xed\xb0\x80", 0x10000],
+ ["\xed\xaf\xbf\xed\xbf\xbf", 0x10ffff],
+ ["\xee\x80\x80", 0xe000],
+ ["\xef\xbf\xbf", 0xffff],
+ ].each do |chr, ord|
+ chr.force_encoding("cesu-8")
+ assert_equal ord, chr.ord
+ assert_equal chr, ord.chr("cesu-8")
+ end
+ end
+
+ def test_cesu8_left_adjust_char_head
+ assert_equal("", "\u{10000}".encode("cesu-8").chop)
+ end
+end
diff --git a/test/ruby/enc/test_emoji_breaks.rb b/test/ruby/enc/test_emoji_breaks.rb
new file mode 100644
index 0000000000..0873e681c3
--- /dev/null
+++ b/test/ruby/enc/test_emoji_breaks.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+# Copyright © 2018 Martin J. Dürst (duerst@it.aoyama.ac.jp)
+
+require "test/unit"
+
+class TestEmojiBreaks < Test::Unit::TestCase
+ class BreakTest
+ attr_reader :string, :comment, :filename, :line_number, :type, :shortname
+
+ def initialize(filename, line_number, data, comment='')
+ @filename = filename
+ @line_number = line_number
+ @comment = comment.gsub(/\s+/, ' ').strip
+ if filename=='emoji-test' or filename=='emoji-variation-sequences'
+ codes, @type = data.split(/\s*;\s*/)
+ @shortname = ''
+ else
+ codes, @type, @shortname = data.split(/\s*;\s*/)
+ end
+ @type = @type.gsub(/\s+/, ' ').strip
+ @shortname = @shortname.gsub(/\s+/, ' ').strip
+ @string = codes.split(/\s+/)
+ .map do |ch|
+ c = ch.to_i(16)
+ # eliminate cases with surrogates
+ # raise ArgumentError if 0xD800 <= c and c <= 0xDFFF
+ c.chr('UTF-8')
+ end.join
+ end
+ end
+
+ class BreakFile
+ attr_reader :basename, :fullname, :version
+ FILES = []
+
+ def initialize(basename, path, version)
+ @basename = basename
+ @fullname = "#{path}/#{basename}.txt" # File.expand_path(path + version, __dir__)
+ @version = version
+ FILES << self
+ end
+
+ def self.files
+ FILES
+ end
+ end
+
+ UNICODE_VERSION = RbConfig::CONFIG['UNICODE_VERSION']
+ UNICODE_DATA_PATH = File.expand_path("../../../enc/unicode/data/#{UNICODE_VERSION}/ucd/emoji", __dir__)
+ EMOJI_VERSION = RbConfig::CONFIG['UNICODE_EMOJI_VERSION']
+ EMOJI_DATA_PATH = File.expand_path("../../../enc/unicode/data/emoji/#{EMOJI_VERSION}", __dir__)
+
+ 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, EMOJI_VERSION)
+ EMOJI_DATA_FILES << UNICODE_DATA_FILE
+
+ def self.data_files_available?
+ EMOJI_DATA_FILES.all? do |f|
+ File.exist?(f.fullname)
+ end
+ end
+
+ def test_data_files_available
+ assert_equal 4, EMOJI_DATA_FILES.size # debugging test
+ unless TestEmojiBreaks.data_files_available?
+ omit "Emoji data files not available in #{EMOJI_DATA_PATH}."
+ end
+ end
+
+ if data_files_available?
+ def read_data
+ tests = []
+ EMOJI_DATA_FILES.each do |file|
+ version_mismatch = true
+ file_tests = []
+ File.foreach(file.fullname, encoding: Encoding::UTF_8) do |line|
+ line.chomp!
+ if $.==1
+ if line=="# #{file.basename}-#{file.version}.txt"
+ version_mismatch = false
+ elsif line!="# #{file.basename}.txt"
+ raise "File Name Mismatch: line: #{line}, expected filename: #{file.basename}.txt"
+ end
+ end
+ version_mismatch = false if line =~ /^# Version: #{file.version}/ # 13.0 and older
+ version_mismatch = false if line =~ /^# Used with Emoji Version #{EMOJI_VERSION}/ # 14.0 and newer
+ next if line.match?(/\A(#|\z)/)
+ if line =~ /^(\h{4,6})\.\.(\h{4,6}) *(;.+)/ # deal with Unicode ranges in emoji-sequences.txt (Bug #18028)
+ range_start = $1.to_i(16)
+ range_end = $2.to_i(16)
+ rest = $3
+ (range_start..range_end).each do |code_point|
+ file_tests << BreakTest.new(file.basename, $., *(code_point.to_s(16)+rest).split('#', 2))
+ end
+ else
+ file_tests << BreakTest.new(file.basename, $., *line.split('#', 2))
+ end
+ end
+ raise "File Version Mismatch: file: #{file.fullname}, version: #{file.version}" if version_mismatch
+ tests += file_tests
+ end
+ tests
+ end
+
+ def all_tests
+ @@tests ||= read_data
+ rescue Errno::ENOENT
+ @@tests ||= []
+ end
+
+ def test_single_emoji
+ all_tests.each do |test|
+ expected = [test.string]
+ actual = test.string.each_grapheme_cluster.to_a
+ assert_equal expected, actual,
+ "file: #{test.filename}, line #{test.line_number}, " +
+ "type: #{test.type}, shortname: #{test.shortname}, comment: #{test.comment}"
+ end
+ end
+
+ def test_embedded_emoji
+ all_tests.each do |test|
+ expected = ["\t", test.string, "\t"]
+ actual = "\t#{test.string}\t".each_grapheme_cluster.to_a
+ assert_equal expected, actual,
+ "file: #{test.filename}, line #{test.line_number}, " +
+ "type: #{test.type}, shortname: #{test.shortname}, comment: #{test.comment}"
+ end
+ end
+
+ # test some pseodorandom combinations of emoji
+ def test_mixed_emoji
+ srand 0
+ length = all_tests.length
+ step = 503 # use a prime number
+ all_tests.each do |test1|
+ start = rand step
+ start.step(by: step, to: length-1) do |t2|
+ test2 = all_tests[t2]
+ # exclude skin tones, because they glue to previous grapheme clusters
+ next if (0x1F3FB..0x1F3FF).include? test2.string.ord
+ expected = [test1.string, test2.string]
+ actual = (test1.string+test2.string).each_grapheme_cluster.to_a
+ assert_equal expected, actual,
+ "file1: #{test1.filename}, line1 #{test1.line_number}, " +
+ "file2: #{test2.filename}, line2 #{test2.line_number},\n" +
+ "type1: #{test1.type}, shortname1: #{test1.shortname}, comment1: #{test1.comment},\n" +
+ "type2: #{test2.type}, shortname2: #{test2.shortname}, comment2: #{test2.comment}"
+ end
+ end
+ end
+ end
+end
diff --git a/test/ruby/enc/test_grapheme_breaks.rb b/test/ruby/enc/test_grapheme_breaks.rb
index 7f6c776113..7e6d722d40 100644
--- a/test/ruby/enc/test_grapheme_breaks.rb
+++ b/test/ruby/enc/test_grapheme_breaks.rb
@@ -3,29 +3,29 @@
require "test/unit"
-class BreakTest
- attr_reader :clusters, :string, :comment, :line_number
+class TestGraphemeBreaksFromFile < Test::Unit::TestCase
+ class BreakTest
+ attr_reader :clusters, :string, :comment, :line_number
- def initialize (line_number, data, comment)
- @line_number = line_number
- @comment = comment
- @clusters = data.sub(/\A\s*÷\s*/, '')
- .sub(/\s*÷\s*\z/, '')
- .split(/\s*÷\s*/)
- .map do |cl|
- cl.split(/\s*×\s*/)
- .map do |ch|
- c = ch.to_i(16)
- # eliminate cases with surrogates
- raise ArgumentError if 0xD800 <= c and c <= 0xDFFF
- c.chr('UTF-8')
- end.join
- end
- @string = @clusters.join
+ def initialize(line_number, data, comment)
+ @line_number = line_number
+ @comment = comment
+ @clusters = data.sub(/\A\s*÷\s*/, '')
+ .sub(/\s*÷\s*\z/, '')
+ .split(/\s*÷\s*/)
+ .map do |cl|
+ cl.split(/\s*×\s*/)
+ .map do |ch|
+ c = ch.to_i(16)
+ # eliminate cases with surrogates
+ raise ArgumentError if 0xD800 <= c and c <= 0xDFFF
+ c.chr('UTF-8')
+ end.join
+ end
+ @string = @clusters.join
+ end
end
-end
-class TestGraphemeBreaksFromFile < Test::Unit::TestCase
UNICODE_VERSION = RbConfig::CONFIG['UNICODE_VERSION']
path = File.expand_path("../../../enc/unicode/data/#{UNICODE_VERSION}", __dir__)
UNICODE_DATA_PATH = File.directory?("#{path}/ucd/auxiliary") ? "#{path}/ucd/auxiliary" : path
@@ -37,56 +37,56 @@ class TestGraphemeBreaksFromFile < Test::Unit::TestCase
def test_data_files_available
unless TestGraphemeBreaksFromFile.file_available?
- skip "Unicode data file GraphemeBreakTest not available in #{UNICODE_DATA_PATH}."
+ omit "Unicode data file GraphemeBreakTest not available in #{UNICODE_DATA_PATH}."
end
end
-end
-TestGraphemeBreaksFromFile.file_available? and class TestGraphemeBreaksFromFile
- def read_data
- tests = []
- IO.foreach(GRAPHEME_BREAK_TEST_FILE, encoding: Encoding::UTF_8) do |line|
- if $. == 1 and not line.start_with?("# GraphemeBreakTest-#{UNICODE_VERSION}.txt")
- raise "File Version Mismatch"
+ if file_available?
+ def read_data
+ tests = []
+ File.foreach(GRAPHEME_BREAK_TEST_FILE, encoding: Encoding::UTF_8) do |line|
+ if $. == 1 and not line.start_with?("# GraphemeBreakTest-#{UNICODE_VERSION}.txt")
+ raise "File Version Mismatch"
+ end
+ next if /\A#/.match? line
+ tests << BreakTest.new($., *line.chomp.split('#')) rescue 'whatever'
end
- next if /\A#/.match? line
- tests << BreakTest.new($., *line.chomp.split('#')) rescue 'whatever'
+ tests
end
- tests
- end
- def all_tests
- @@tests ||= read_data
- rescue Errno::ENOENT
- @@tests ||= []
- end
+ def all_tests
+ @@tests ||= read_data
+ rescue Errno::ENOENT
+ @@tests ||= []
+ end
- def test_each_grapheme_cluster
- all_tests.each do |test|
- expected = test.clusters
- actual = test.string.each_grapheme_cluster.to_a
- assert_equal expected, actual,
- "line #{test.line_number}, expected '#{expected}', " +
- "but got '#{actual}', comment: #{test.comment}"
+ def test_each_grapheme_cluster
+ all_tests.each do |test|
+ expected = test.clusters
+ actual = test.string.each_grapheme_cluster.to_a
+ assert_equal expected, actual,
+ "line #{test.line_number}, expected '#{expected}', " +
+ "but got '#{actual}', comment: #{test.comment}"
+ end
end
- end
- def test_backslash_X
- all_tests.each do |test|
- clusters = test.clusters.dup
- string = test.string.dup
- removals = 0
- while string.sub!(/\A\X/, '')
- removals += 1
- clusters.shift
- expected = clusters.join
+ def test_backslash_X
+ all_tests.each do |test|
+ clusters = test.clusters.dup
+ string = test.string.dup
+ removals = 0
+ while string.sub!(/\A\X/, '')
+ removals += 1
+ clusters.shift
+ expected = clusters.join
+ assert_equal expected, string,
+ "line #{test.line_number}, removals: #{removals}, expected '#{expected}', " +
+ "but got '#{string}', comment: #{test.comment}"
+ end
assert_equal expected, string,
- "line #{test.line_number}, removals: #{removals}, expected '#{expected}', " +
+ "line #{test.line_number}, after last removal, expected '#{expected}', " +
"but got '#{string}', comment: #{test.comment}"
end
- assert_equal expected, string,
- "line #{test.line_number}, after last removal, expected '#{expected}', " +
- "but got '#{string}', comment: #{test.comment}"
end
end
end
diff --git a/test/ruby/enc/test_regex_casefold.rb b/test/ruby/enc/test_regex_casefold.rb
index 2b252bd441..b5d5c6e337 100644
--- a/test/ruby/enc/test_regex_casefold.rb
+++ b/test/ruby/enc/test_regex_casefold.rb
@@ -11,7 +11,7 @@ class TestCaseFold < Test::Unit::TestCase
def check_downcase_properties(expected, start, *flags)
assert_equal expected, start.downcase(*flags)
- temp = start
+ temp = start.dup
assert_equal expected, temp.downcase!(*flags)
assert_equal expected, expected.downcase(*flags)
temp = expected
@@ -19,7 +19,7 @@ class TestCaseFold < Test::Unit::TestCase
end
def read_tests
- IO.readlines("#{UNICODE_DATA_PATH}/CaseFolding.txt", encoding: Encoding::ASCII_8BIT)
+ File.readlines("#{UNICODE_DATA_PATH}/CaseFolding.txt", encoding: Encoding::ASCII_8BIT)
.collect.with_index { |linedata, linenumber| [linenumber.to_i+1, linedata.chomp] }
.reject { |number, data| data =~ /^(#|$)/ }
.collect do |linenumber, linedata|
@@ -39,7 +39,7 @@ class TestCaseFold < Test::Unit::TestCase
@@tests ||= read_tests
rescue Errno::ENOENT => e
@@tests ||= []
- skip e.message
+ omit e.message
end
def self.generate_test_casefold(encoding)
diff --git a/test/ruby/marshaltestlib.rb b/test/ruby/marshaltestlib.rb
index 358d3c5133..7f100b7873 100644
--- a/test/ruby/marshaltestlib.rb
+++ b/test/ruby/marshaltestlib.rb
@@ -110,7 +110,9 @@ module MarshalTestLib
class MyException < Exception; def initialize(v, *args) super(*args); @v = v; end; attr_reader :v; end
def test_exception
marshal_equal(Exception.new('foo')) {|o| o.message}
- marshal_equal(assert_raise(NoMethodError) {no_such_method()}) {|o| o.message}
+ obj = Object.new
+ e = assert_raise(NoMethodError) {obj.no_such_method()}
+ marshal_equal(e) {|o| o.message.lines.first.chomp}
end
def test_exception_subclass
diff --git a/test/ruby/sentence.rb b/test/ruby/sentence.rb
index 28fb5d1cf8..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.
@@ -353,7 +353,7 @@ class Sentence
# * No rule derives to empty sequence
# * Underivable rule simplified
# * No channel rule
- # * Symbols which has zero or one choices are not appered in rhs.
+ # * Symbols which has zero or one choices are not appeared in rhs.
#
# Note that the rules which can derive empty and non-empty
# sequences are modified to derive only non-empty sequences.
diff --git a/test/ruby/test_alias.rb b/test/ruby/test_alias.rb
index e81636fa43..539cd49488 100644
--- a/test/ruby/test_alias.rb
+++ b/test/ruby/test_alias.rb
@@ -35,6 +35,18 @@ class TestAlias < Test::Unit::TestCase
end
end
+ class Alias4 < Alias0
+ alias foo1 foo
+ alias foo2 foo1
+ alias foo3 foo2
+ end
+
+ class Alias5 < Alias4
+ alias foo1 foo
+ alias foo3 foo2
+ alias foo2 foo1
+ end
+
def test_alias
x = Alias2.new
assert_equal "foo", x.bar
@@ -47,10 +59,18 @@ class TestAlias < Test::Unit::TestCase
assert_raise(NoMethodError) { x.quux }
end
- class C
- def m
- $SAFE
- end
+ def test_alias_inspect
+ o = Alias4.new
+ assert_equal("TestAlias::Alias4(TestAlias::Alias0)#foo()", o.method(:foo).inspect.split[1])
+ assert_equal("TestAlias::Alias4(TestAlias::Alias0)#foo1(foo)()", o.method(:foo1).inspect.split[1])
+ assert_equal("TestAlias::Alias4(TestAlias::Alias0)#foo2(foo)()", o.method(:foo2).inspect.split[1])
+ assert_equal("TestAlias::Alias4(TestAlias::Alias0)#foo3(foo)()", o.method(:foo3).inspect.split[1])
+
+ o = Alias5.new
+ assert_equal("TestAlias::Alias5(TestAlias::Alias0)#foo()", o.method(:foo).inspect.split[1])
+ assert_equal("TestAlias::Alias5(TestAlias::Alias0)#foo1(foo)()", o.method(:foo1).inspect.split[1])
+ assert_equal("TestAlias::Alias5(TestAlias::Alias0)#foo2(foo)()", o.method(:foo2).inspect.split[1])
+ assert_equal("TestAlias::Alias5(TestAlias::Alias0)#foo3(foo)()", o.method(:foo3).inspect.split[1])
end
def test_nonexistmethod
@@ -233,4 +253,92 @@ class TestAlias < Test::Unit::TestCase
assert_equal(:foo, k.instance_method(:bar).original_name)
assert_equal(:foo, name)
end
+
+ def test_alias_suppressing_redefinition
+ assert_in_out_err(%w[-w], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class A
+ def foo; end
+ alias foo foo
+ def foo; end
+ end
+ end;
+ end
+
+ class C2
+ public :system
+ alias_method :bar, :system
+ alias_method :system, :bar
+ end
+
+ def test_zsuper_alias_visibility
+ assert(C2.new.respond_to?(:system))
+ end
+
+ def test_alias_memory_leak
+ assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", rss: true)
+ begin;
+ class A
+ 500.times do
+ 1000.times do |i|
+ define_method(:"foo_#{i}") {}
+
+ alias :"foo_#{i}" :"foo_#{i}"
+
+ remove_method :"foo_#{i}"
+ end
+ GC.start
+ end
+ end
+ end;
+ end
+
+ def test_alias_complemented_method
+ assert_in_out_err(%w[-w], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ module M
+ def foo = 1
+ self.extend M
+ end
+
+ 3.times{|i|
+ module M
+ alias foo2 foo
+ remove_method :foo
+ def foo = 2
+ ensure
+ remove_method :foo
+ alias foo foo2
+ remove_method :foo2
+ end
+
+ M.foo
+
+ original_foo = M.method(:foo)
+
+ M.class_eval do
+ remove_method :foo
+ def foo = 3
+ end
+
+ M.class_eval do
+ remove_method :foo
+ define_method :foo, original_foo
+ end
+ }
+ end;
+ end
+
+ def test_undef_method_error_message_with_zsuper_method
+ modules = [
+ Module.new { private :class },
+ Module.new { prepend Module.new { private :class } },
+ ]
+ message = "undefined method 'class' for module '%s'"
+ modules.each do |mod|
+ assert_raise_with_message(NameError, message % mod) do
+ mod.alias_method :xyz, :class
+ end
+ end
+ end
end
diff --git a/test/ruby/test_allocation.rb b/test/ruby/test_allocation.rb
new file mode 100644
index 0000000000..90d7c04f9b
--- /dev/null
+++ b/test/ruby/test_allocation.rb
@@ -0,0 +1,957 @@
+# frozen_string_literal: false
+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
+
+ def check_allocations(checks)
+ dups = checks.split("\n").reject(&:empty?).tally.select{|_,v| v > 1}
+ raise "duplicate checks:\n#{dups.keys.join("\n")}" unless dups.empty?
+
+ checks = munge_checks(checks)
+
+ assert_separately([], <<~RUBY)
+ $allocations = [0, 0]
+ $counts = {}
+ failures = []
+
+ def self.num_allocations
+ ObjectSpace.count_objects($counts)
+ arrays = $counts[:T_ARRAY]
+ hashes = $counts[:T_HASH]
+ yield
+ ObjectSpace.count_objects($counts)
+ arrays -= $counts[:T_ARRAY]
+ hashes -= $counts[:T_HASH]
+ $allocations[0] = -arrays
+ $allocations[1] = -hashes
+ end
+
+ define_singleton_method(:check_allocations) do |num_arrays, num_hashes, check_code|
+ instance_eval <<~RB
+ empty_array = empty_array = []
+ empty_hash = empty_hash = {}
+ array1 = array1 = [1]
+ r2k_array = r2k_array = [Hash.ruby2_keywords_hash(a: 3)]
+ r2k_array1 = r2k_array1 = [1, Hash.ruby2_keywords_hash(a: 3)]
+ r2k_empty_array = r2k_empty_array = [Hash.ruby2_keywords_hash({})]
+ r2k_empty_array1 = r2k_empty_array1 = [1, Hash.ruby2_keywords_hash({})]
+ hash1 = hash1 = {a: 2}
+ nill = nill = nil
+ block = block = lambda{}
+
+ num_allocations do
+ \#{check_code}
+ end
+ RB
+
+ if num_arrays != $allocations[0]
+ failures << "Expected \#{num_arrays} array allocations for \#{check_code.inspect}, but \#{$allocations[0]} arrays allocated"
+ end
+ if num_hashes != $allocations[1]
+ failures << "Expected \#{num_hashes} hash allocations for \#{check_code.inspect}, but \#{$allocations[1]} hashes allocated"
+ end
+ end
+
+ GC.start
+ GC.disable
+
+ #{checks}
+
+ assert_empty(failures)
+ RUBY
+ end
+
+ class Literal < self
+ def test_array_literal
+ check_allocations(<<~RUBY)
+ check_allocations(1, 0, "[]")
+ check_allocations(1, 0, "[1]")
+ check_allocations(1, 0, "[*empty_array]")
+ check_allocations(1, 0, "[*empty_array, 1, *empty_array]")
+ check_allocations(1, 0, "[*empty_array, *empty_array]")
+ check_allocations(1, 0, "[#{'1,'*100000}]")
+ RUBY
+ end
+
+ def test_hash_literal
+ check_allocations(<<~RUBY)
+ check_allocations(0, 1, "{}")
+ check_allocations(0, 1, "{a: 1}")
+ check_allocations(0, 1, "{**empty_hash}")
+ check_allocations(0, 1, "{**empty_hash, a: 1, **empty_hash}")
+ check_allocations(0, 1, "{**empty_hash, **empty_hash}")
+ check_allocations(0, 1, "{#{100000.times.map{|i| "a#{i}: 1"}.join(',')}}")
+ RUBY
+ end
+ end
+
+ class MethodCall < self
+ def block
+ ''
+ end
+ alias only_block block
+
+ def test_no_parameters
+ check_allocations(<<~RUBY)
+ def self.none(#{only_block}); end
+
+ check_allocations(0, 0, "none(#{only_block})")
+ check_allocations(0, 0, "none(*nil#{block})")
+ check_allocations(0, 0, "none(*empty_array#{block})")
+ check_allocations(0, 0, "none(**empty_hash#{block})")
+ check_allocations(0, 0, "none(*empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "none(*empty_array, *empty_array#{block})")
+ check_allocations(0, 1, "none(**empty_hash, **empty_hash#{block})")
+ check_allocations(1, 1, "none(*empty_array, *empty_array, **empty_hash, **empty_hash#{block})")
+
+ check_allocations(0, 0, "none(*r2k_empty_array#{block})")
+ RUBY
+ end
+
+ def test_required_parameter
+ check_allocations(<<~RUBY)
+ def self.required(x#{block}); end
+
+ check_allocations(0, 0, "required(1#{block})")
+ check_allocations(0, 0, "required(1, *empty_array#{block})")
+ check_allocations(0, 0, "required(1, **empty_hash#{block})")
+ check_allocations(0, 0, "required(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "required(*array1#{block})")
+ check_allocations(0, 1, "required(**hash1#{block})")
+
+ check_allocations(1, 0, "required(*array1, *empty_array#{block})")
+ check_allocations(0, 1, "required(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "required(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "required(*r2k_empty_array1#{block})")
+ check_allocations(0, 1, "required(*r2k_array#{block})")
+
+ check_allocations(0, 1, "required(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_optional_parameter
+ check_allocations(<<~RUBY)
+ def self.optional(x=nil#{block}); end
+
+ check_allocations(0, 0, "optional(1#{block})")
+ check_allocations(0, 0, "optional(1, *empty_array#{block})")
+ check_allocations(0, 0, "optional(1, **empty_hash#{block})")
+ check_allocations(0, 0, "optional(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "optional(*array1#{block})")
+ check_allocations(0, 1, "optional(**hash1#{block})")
+
+ check_allocations(1, 0, "optional(*array1, *empty_array#{block})")
+ check_allocations(0, 1, "optional(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "optional(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "optional(*r2k_empty_array#{block})")
+ check_allocations(0, 0, "optional(*r2k_empty_array1#{block})")
+ check_allocations(0, 1, "optional(*r2k_array#{block})")
+
+ check_allocations(0, 0, "optional(*empty_array#{block})")
+ check_allocations(0, 0, "optional(*nil#{block})")
+ check_allocations(0, 0, "optional(#{only_block})")
+ check_allocations(0, 1, "optional(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_positional_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.splat(*x#{block}); end
+
+ check_allocations(1, 0, "splat(1#{block})")
+ check_allocations(1, 0, "splat(1, *empty_array#{block})")
+ check_allocations(1, 0, "splat(1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat(*array1#{block})")
+ check_allocations(1, 0, "splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat(1, *array1#{block})")
+ check_allocations(1, 0, "splat(1, *array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat(1, *array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat(1, *array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat(*nil#{block})")
+ check_allocations(1, 0, "splat(#{only_block})")
+ check_allocations(1, 1, "splat(**hash1#{block})")
+
+ check_allocations(1, 1, "splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat(*empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat(*r2k_empty_array#{block})")
+ check_allocations(1, 0, "splat(*r2k_empty_array1#{block})")
+ check_allocations(1, 1, "splat(*r2k_array#{block})")
+ check_allocations(1, 1, "splat(*r2k_array1#{block})")
+ RUBY
+ end
+
+ def test_required_and_positional_splat_parameters
+ check_allocations(<<~RUBY)
+ def self.req_splat(x, *y#{block}); end
+
+ check_allocations(1, 0, "req_splat(1#{block})")
+ check_allocations(1, 0, "req_splat(1, *nil#{block})")
+ check_allocations(1, 0, "req_splat(1, *empty_array#{block})")
+ check_allocations(1, 0, "req_splat(1, **empty_hash#{block})")
+ check_allocations(1, 0, "req_splat(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "req_splat(*array1#{block})")
+ check_allocations(1, 0, "req_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "req_splat(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "req_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "req_splat(1, *array1#{block})")
+ check_allocations(1, 0, "req_splat(1, *array1, *empty_array#{block})")
+ check_allocations(1, 0, "req_splat(1, *array1, **empty_hash#{block})")
+ check_allocations(1, 0, "req_splat(1, *array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "req_splat(**hash1#{block})")
+
+ check_allocations(1, 1, "req_splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "req_splat(*empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 0, "req_splat(*r2k_empty_array1#{block})")
+ check_allocations(1, 1, "req_splat(*r2k_array#{block})")
+ check_allocations(1, 1, "req_splat(*r2k_array1#{block})")
+ RUBY
+ end
+
+ def test_positional_splat_and_post_parameters
+ check_allocations(<<~RUBY)
+ def self.splat_post(*x, y#{block}); end
+
+ check_allocations(1, 0, "splat_post(1#{block})")
+ check_allocations(1, 0, "splat_post(1, *nil#{block})")
+ check_allocations(1, 0, "splat_post(1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_post(1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_post(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat_post(*array1#{block})")
+ check_allocations(1, 0, "splat_post(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_post(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_post(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat_post(1, *array1#{block})")
+ check_allocations(1, 0, "splat_post(1, *array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_post(1, *array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_post(1, *array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_post(**hash1#{block})")
+
+ check_allocations(1, 1, "splat_post(**hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_post(*empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat_post(*r2k_empty_array1#{block})")
+ check_allocations(1, 1, "splat_post(*r2k_array#{block})")
+ check_allocations(1, 1, "splat_post(*r2k_array1#{block})")
+ RUBY
+ end
+
+ def test_keyword_parameter
+ check_allocations(<<~RUBY)
+ def self.keyword(a: nil#{block}); end
+
+ check_allocations(0, 0, "keyword(a: 2#{block})")
+ check_allocations(0, 0, "keyword(*empty_array, a: 2#{block})")
+ check_allocations(0, 1, "keyword(a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword(**empty_hash, a: 2#{block})")
+
+ check_allocations(0, 0, "keyword(**nil#{block})")
+ check_allocations(0, 0, "keyword(**empty_hash#{block})")
+ check_allocations(0, 0, "keyword(**hash1#{block})")
+ check_allocations(0, 0, "keyword(*empty_array, **hash1#{block})")
+ check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword(**empty_hash, **hash1#{block})")
+
+ check_allocations(0, 0, "keyword(*nil#{block})")
+ check_allocations(0, 0, "keyword(*empty_array#{block})")
+ check_allocations(1, 0, "keyword(*empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "keyword(*r2k_empty_array#{block})")
+ check_allocations(0, 0, "keyword(*r2k_array#{block})")
+
+ check_allocations(0, 1, "keyword(*empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_keyword_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.keyword_splat(**kw#{block}); end
+
+ check_allocations(0, 1, "keyword_splat(a: 2#{block})")
+ check_allocations(0, 1, "keyword_splat(*empty_array, a: 2#{block})")
+ check_allocations(0, 1, "keyword_splat(a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_splat(**empty_hash, a: 2#{block})")
+
+ check_allocations(0, 1, "keyword_splat(**nil#{block})")
+ check_allocations(0, 1, "keyword_splat(**empty_hash#{block})")
+ check_allocations(0, 1, "keyword_splat(**hash1#{block})")
+ check_allocations(0, 1, "keyword_splat(*empty_array, **hash1#{block})")
+ check_allocations(0, 1, "keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_splat(**empty_hash, **hash1#{block})")
+
+ check_allocations(0, 1, "keyword_splat(*nil#{block})")
+ check_allocations(0, 1, "keyword_splat(*empty_array#{block})")
+ check_allocations(1, 1, "keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 1, "keyword_splat(*r2k_empty_array#{block})")
+ check_allocations(0, 1, "keyword_splat(*r2k_array#{block})")
+
+ check_allocations(0, 1, "keyword_splat(*empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_splat(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_keyword_and_keyword_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.keyword_and_keyword_splat(a: 1, **kw#{block}); end
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(a: 2#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, a: 2#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash, a: 2#{block})")
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(**nil#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**hash1#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, **hash1#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash, **hash1#{block})")
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(*nil#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array#{block})")
+ check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(*r2k_empty_array#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(*r2k_array#{block})")
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_required_positional_and_keyword_parameter
+ check_allocations(<<~RUBY)
+ def self.required_and_keyword(b, a: nil#{block}); end
+
+ check_allocations(0, 0, "required_and_keyword(1, a: 2#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, *empty_array, a: 2#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(1, **nil#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, **empty_hash#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, **hash1#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, *empty_array, **hash1#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(1, *nil#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, *empty_array#{block})")
+ check_allocations(1, 0, "required_and_keyword(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(*array1, **nill#{block})")
+ check_allocations(0, 0, "required_and_keyword(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "required_and_keyword(*array1, **hash1#{block})")
+ check_allocations(1, 0, "required_and_keyword(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "required_and_keyword(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "required_and_keyword(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "required_and_keyword(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(*r2k_empty_array1#{block})")
+ check_allocations(0, 0, "required_and_keyword(*r2k_array1#{block})")
+
+ check_allocations(0, 1, "required_and_keyword(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(0, 1, "required_and_keyword(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 0, "required_and_keyword(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_positional_splat_and_keyword_parameter
+ check_allocations(<<~RUBY)
+ def self.splat_and_keyword(*b, a: nil#{block}); end
+
+ check_allocations(1, 0, "splat_and_keyword(1, a: 2#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(1, **nil#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, **hash1#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(1, *nil#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(*array1, a: 2#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(*array1, **nill#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, **hash1#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, **nil#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(*r2k_empty_array#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*r2k_array#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*r2k_empty_array1#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*r2k_array1#{block})")
+ RUBY
+ end
+
+ def test_required_and_keyword_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.required_and_keyword_splat(b, **kw#{block}); end
+
+ check_allocations(0, 1, "required_and_keyword_splat(1, a: 2#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(1, **nil#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **hash1#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(1, *nil#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, a: 2#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, **nill#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, **hash1#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(*r2k_empty_array1#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(*r2k_array1#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_positional_splat_and_keyword_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.splat_and_keyword_splat(*b, **kw#{block}); end
+
+ check_allocations(1, 1, "splat_and_keyword_splat(1, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **nil#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *nil#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, a: 2#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **nill#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **nil#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*r2k_empty_array#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*r2k_array#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*r2k_empty_array1#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*r2k_array1#{block})")
+ 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)
+ def self.anon_splat_and_anon_keyword_splat(*, **#{block}); end
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a: 2#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **nil#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **hash1#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*nil, **nill#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nill#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **hash1#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(#{only_block})")
+ check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(a: 2#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(**empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nil#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_empty_array#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_array#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_empty_array1#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_array1#{block})")
+ RUBY
+ end
+
+ def test_nested_anonymous_splat_and_anonymous_keyword_splat_parameters
+ only_block = block.empty? ? block : block[2..]
+ check_allocations(<<~RUBY)
+ def self.t(*, **#{block}); end
+ def self.anon_splat_and_anon_keyword_splat(*, **#{block}); t(*, **) end
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a: 2#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **nil#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **hash1#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*nil, **nill#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nill#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **hash1#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(#{only_block})")
+ check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(a: 2#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(**empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nil#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_empty_array#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_array#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_empty_array1#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_array1#{block})")
+ RUBY
+ end
+
+ def test_argument_forwarding
+ check_allocations(<<~RUBY)
+ def self.argument_forwarding(...); end
+
+ check_allocations(0, 0, "argument_forwarding(1, a: 2#{block})")
+ check_allocations(0, 0, "argument_forwarding(1, *empty_array, a: 2#{block})")
+ check_allocations(0, 1, "argument_forwarding(1, a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "argument_forwarding(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(1, **nil#{block})")
+ check_allocations(0, 0, "argument_forwarding(1, **empty_hash#{block})")
+ check_allocations(0, 0, "argument_forwarding(1, **hash1#{block})")
+ check_allocations(0, 0, "argument_forwarding(1, *empty_array, **hash1#{block})")
+ check_allocations(0, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "argument_forwarding(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(1, *empty_array#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(**nill#{block})")
+ check_allocations(0, 0, "argument_forwarding(*nil, **nill#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **nill#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **hash1#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(0, 1, "argument_forwarding(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(0, 1, "argument_forwarding(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "argument_forwarding(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(0, 1, "argument_forwarding(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **nil#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(*r2k_empty_array#{block})")
+ check_allocations(0, 0, "argument_forwarding(*r2k_array#{block})")
+ check_allocations(0, 0, "argument_forwarding(*r2k_empty_array1#{block})")
+ check_allocations(0, 0, "argument_forwarding(*r2k_array1#{block})")
+ RUBY
+ end
+
+ def test_nested_argument_forwarding
+ check_allocations(<<~RUBY)
+ def self.t(...) end
+ def self.argument_forwarding(...); t(...) end
+
+ check_allocations(0, 0, "argument_forwarding(1, a: 2#{block})")
+ check_allocations(0, 0, "argument_forwarding(1, *empty_array, a: 2#{block})")
+ check_allocations(0, 1, "argument_forwarding(1, a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "argument_forwarding(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(1, **nil#{block})")
+ check_allocations(0, 0, "argument_forwarding(1, **empty_hash#{block})")
+ check_allocations(0, 0, "argument_forwarding(1, **hash1#{block})")
+ check_allocations(0, 0, "argument_forwarding(1, *empty_array, **hash1#{block})")
+ check_allocations(0, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "argument_forwarding(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(1, *empty_array#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(**nill#{block})")
+ check_allocations(0, 0, "argument_forwarding(*nil, **nill#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **nill#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **hash1#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(0, 1, "argument_forwarding(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(0, 1, "argument_forwarding(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "argument_forwarding(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(0, 1, "argument_forwarding(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **nil#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(*r2k_empty_array#{block})")
+ check_allocations(0, 0, "argument_forwarding(*r2k_array#{block})")
+ check_allocations(0, 0, "argument_forwarding(*r2k_empty_array1#{block})")
+ check_allocations(0, 0, "argument_forwarding(*r2k_array1#{block})")
+ RUBY
+ end
+
+ def test_ruby2_keywords
+ check_allocations(<<~RUBY)
+ def self.r2k(*a#{block}); end
+ singleton_class.send(:ruby2_keywords, :r2k)
+
+ check_allocations(1, 1, "r2k(1, a: 2#{block})")
+ check_allocations(1, 1, "r2k(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "r2k(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "r2k(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "r2k(1, **nil#{block})")
+ check_allocations(1, 0, "r2k(1, **empty_hash#{block})")
+ check_allocations(1, 1, "r2k(1, **hash1#{block})")
+ check_allocations(1, 1, "r2k(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "r2k(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "r2k(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "r2k(1, *empty_array#{block})")
+ check_allocations(1, 0, "r2k(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "r2k(*array1, a: 2#{block})")
+
+ check_allocations(1, 0, "r2k(**nill#{block})")
+ check_allocations(1, 0, "r2k(*nil, **nill#{block})")
+ check_allocations(1, 0, "r2k(*array1, **nill#{block})")
+ check_allocations(1, 0, "r2k(*array1, **empty_hash#{block})")
+ check_allocations(1, 1, "r2k(*array1, **hash1#{block})")
+ check_allocations(1, 1, "r2k(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "r2k(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "r2k(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "r2k(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "r2k(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "r2k(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "r2k(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "r2k(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "r2k(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "r2k(*array1, **nil#{block})")
+
+ check_allocations(1, 0, "r2k(*r2k_empty_array#{block})")
+ unless defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled?
+ # YJIT may or may not allocate depending on arch?
+ check_allocations(1, 1, "r2k(*r2k_array#{block})")
+ check_allocations(1, 0, "r2k(*r2k_empty_array1#{block})")
+ check_allocations(1, 1, "r2k(*r2k_array1#{block})")
+ end
+ RUBY
+ end
+
+ 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
+ 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 - 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
+ check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1.0#{block})") # FLOAT
+ check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1.0r#{block})") # RATIONAL
+ check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1.0i#{block})") # IMAGINARY
+ check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 'a'#{block})") # STR
+ check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: :b#{block})") # SYM
+ check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: /a/#{block})") # REGX
+ check_allocations(0, 1, "keyword(*empty_array, a: self#{block})") # SELF
+ check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: nil#{block})") # NIL
+ check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: true#{block})") # TRUE
+ check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: false#{block})") # FALSE
+ 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
+
+ class WithBlock < self
+ def block
+ ', &block'
+ end
+ def only_block
+ '&block'
+ end
+ end
+ end
+
+ class ProcCall < MethodCall
+ def munge_checks(checks)
+ return checks if @no_munge
+ sub = rep = nil
+ checks.split("\n").map do |line|
+ case line
+ when "singleton_class.send(:ruby2_keywords, :r2k)"
+ "r2k.ruby2_keywords"
+ when /\Adef self.([a-z0-9_]+)\((.*)\);(.*)end\z/
+ sub = $1 + '('
+ rep = $1 + '.('
+ "#{$1} = #{$1} = proc{ |#{$2}| #{$3} }"
+ when /check_allocations/
+ line.gsub(sub, rep)
+ else
+ line
+ end
+ end.join("\n")
+ end
+
+ # Generic argument forwarding not supported in proc definitions
+ undef_method :test_argument_forwarding
+ undef_method :test_nested_argument_forwarding
+
+ # Proc anonymous arguments cannot be used directly
+ undef_method :test_nested_anonymous_splat_and_anonymous_keyword_splat_parameters
+
+ def test_no_array_allocation_with_splat_and_nonstatic_keywords
+ @no_munge = true
+
+ 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 - 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
+ check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1.0#{block})") # FLOAT
+ check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1.0r#{block})") # RATIONAL
+ check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1.0i#{block})") # IMAGINARY
+ check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 'a'#{block})") # STR
+ check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: :b#{block})") # SYM
+ check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: /a/#{block})") # REGX
+ check_allocations(0, 1, "keyword.(*empty_array, a: self#{block})") # SELF
+ check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: nil#{block})") # NIL
+ check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: true#{block})") # TRUE
+ check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: false#{block})") # FALSE
+ 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
+
+ class WithBlock < self
+ def block
+ ', &block'
+ end
+ def only_block
+ '&block'
+ end
+ end
+ end
+end
diff --git a/test/ruby/test_argf.rb b/test/ruby/test_argf.rb
index 311469aad9..55a06296aa 100644
--- a/test/ruby/test_argf.rb
+++ b/test/ruby/test_argf.rb
@@ -9,39 +9,23 @@ class TestArgf < Test::Unit::TestCase
def setup
@tmpdir = Dir.mktmpdir
@tmp_count = 0
- @t1 = make_tempfile0("argf-foo")
- @t1.binmode
- @t1.puts "1"
- @t1.puts "2"
- @t1.close
- @t2 = make_tempfile0("argf-bar")
- @t2.binmode
- @t2.puts "3"
- @t2.puts "4"
- @t2.close
- @t3 = make_tempfile0("argf-baz")
- @t3.binmode
- @t3.puts "5"
- @t3.puts "6"
- @t3.close
+ @t1 = make_tempfile("argf-foo", %w"1 2", binmode: true)
+ @t2 = make_tempfile("argf-bar", %w"3 4", binmode: true)
+ @t3 = make_tempfile("argf-baz", %w"5 6", binmode: true)
end
def teardown
FileUtils.rmtree(@tmpdir)
end
- def make_tempfile0(basename)
+ def make_tempfile(basename = "argf-qux", data = %w[foo bar baz], binmode: false)
@tmp_count += 1
- open("#{@tmpdir}/#{basename}-#{@tmp_count}", "w")
- end
-
- def make_tempfile
- t = make_tempfile0("argf-qux")
- t.puts "foo"
- t.puts "bar"
- t.puts "baz"
- t.close
- t
+ path = "#{@tmpdir}/#{basename}-#{@tmp_count}"
+ File.open(path, "w") do |f|
+ f.binmode if binmode
+ f.puts(*data)
+ f
+ end
end
def ruby(*args, external_encoding: Encoding::UTF_8)
@@ -143,6 +127,17 @@ class TestArgf < Test::Unit::TestCase
};
end
+ def test_lineno_after_shebang
+ expected = %w"1 1 1 2 2 2 3 3 1 4 4 2"
+ assert_in_out_err(["--enable=gems", "-", @t1.path, @t2.path], "#{<<~"{#"}\n#{<<~'};'}", expected)
+ #!/usr/bin/env ruby
+ {#
+ ARGF.each do |line|
+ puts [$., ARGF.lineno, ARGF.file.lineno]
+ end
+ };
+ end
+
def test_new_lineno_each
f = ARGF.class.new(@t1.path, @t2.path, @t3.path)
result = []
@@ -255,6 +250,26 @@ class TestArgf < Test::Unit::TestCase
assert_warning(/#{base}/) {argf.gets}
end
+ def test_inplace_nonascii
+ ext = Encoding.default_external or
+ omit "no default external encoding"
+ t = nil
+ ["\u{3042}", "\u{e9}"].any? do |n|
+ t = make_tempfile(n.encode(ext))
+ rescue Encoding::UndefinedConversionError
+ end
+ t or omit "no name to test"
+ assert_in_out_err(["-i.bak", "-", t.path],
+ "#{<<~"{#"}\n#{<<~'};'}")
+ {#
+ puts ARGF.gets.chomp + '.new'
+ puts ARGF.gets.chomp + '.new'
+ puts ARGF.gets.chomp + '.new'
+ };
+ assert_equal("foo.new\n""bar.new\n""baz.new\n", File.read(t.path))
+ assert_equal("foo\n""bar\n""baz\n", File.read(t.path + ".bak"))
+ end
+
def test_inplace_no_backup
t = make_tempfile
@@ -367,6 +382,21 @@ class TestArgf < Test::Unit::TestCase
assert_equal("foo", File.read(name+suffix))
end
+ def test_inplace_bug_17117
+ assert_in_out_err(["-", @t1.path], "#{<<~"{#"}#{<<~'};'}")
+ {#
+ #!/usr/bin/ruby -pi.bak
+ BEGIN {
+ GC.start
+ arr = []
+ 1000000.times { |x| arr << "fooo#{x}" }
+ }
+ puts "hello"
+ };
+ assert_equal("hello\n1\nhello\n2\n", File.read(@t1.path))
+ assert_equal("1\n2\n", File.read("#{@t1.path}.bak"))
+ end
+
def test_encoding
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
@@ -525,15 +555,11 @@ class TestArgf < Test::Unit::TestCase
end
end
- t1 = open("#{@tmpdir}/argf-hoge", "w")
- t1.binmode
- t1.puts "foo"
- t1.close
- t2 = open("#{@tmpdir}/argf-moge", "w")
- t2.binmode
- t2.puts "bar"
- t2.close
- ruby('-e', 'STDERR.reopen(STDOUT); ARGF.gets; ARGF.skip; p ARGF.eof?', t1.path, t2.path) do |f|
+ t1 = "#{@tmpdir}/argf-hoge"
+ t2 = "#{@tmpdir}/argf-moge"
+ File.binwrite(t1, "foo\n")
+ File.binwrite(t2, "bar\n")
+ ruby('-e', 'STDERR.reopen(STDOUT); ARGF.gets; ARGF.skip; p ARGF.eof?', t1, t2) do |f|
assert_equal(%w(false), f.read.split(/\n/))
end
end
@@ -547,7 +573,7 @@ class TestArgf < Test::Unit::TestCase
def test_read2
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- s = ""
+ s = +""
ARGF.read(8, s)
p s
};
@@ -558,7 +584,7 @@ class TestArgf < Test::Unit::TestCase
def test_read2_with_not_empty_buffer
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- s = "0123456789"
+ s = +"0123456789"
ARGF.read(8, s)
p s
};
@@ -571,7 +597,7 @@ class TestArgf < Test::Unit::TestCase
{#
nil while ARGF.gets
p ARGF.read
- p ARGF.read(0, "")
+ p ARGF.read(0, +"")
};
assert_equal("nil\n\"\"\n", f.read)
end
@@ -580,13 +606,13 @@ class TestArgf < Test::Unit::TestCase
def test_readpartial
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- s = ""
+ s = +""
begin
loop do
s << ARGF.readpartial(1)
- t = ""; ARGF.readpartial(1, t); s << t
+ t = +""; ARGF.readpartial(1, t); s << t
# not empty buffer
- u = "abcdef"; ARGF.readpartial(1, u); s << u
+ u = +"abcdef"; ARGF.readpartial(1, u); s << u
end
rescue EOFError
puts s
@@ -599,11 +625,11 @@ class TestArgf < Test::Unit::TestCase
def test_readpartial2
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}") do |f|
{#
- s = ""
+ s = +""
begin
loop do
s << ARGF.readpartial(1)
- t = ""; ARGF.readpartial(1, t); s << t
+ t = +""; ARGF.readpartial(1, t); s << t
end
rescue EOFError
$stdout.binmode
@@ -634,7 +660,7 @@ class TestArgf < Test::Unit::TestCase
def test_getc
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- s = ""
+ s = +""
while c = ARGF.getc
s << c
end
@@ -660,7 +686,7 @@ class TestArgf < Test::Unit::TestCase
def test_readchar
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- s = ""
+ s = +""
begin
while c = ARGF.readchar
s << c
@@ -705,6 +731,25 @@ class TestArgf < Test::Unit::TestCase
["\"a\\n\\n\"", "\"b\\n\""], [])
end
+ def test_each_line_chomp
+ assert_in_out_err(['-e', 'ARGF.each_line(chomp: false) {|para| p para}'], "a\nb\n",
+ ["\"a\\n\"", "\"b\\n\""], [])
+ assert_in_out_err(['-e', 'ARGF.each_line(chomp: true) {|para| p para}'], "a\nb\n",
+ ["\"a\"", "\"b\""], [])
+
+ t = make_tempfile
+ argf = ARGF.class.new(t.path)
+ lines = []
+ begin
+ argf.each_line(chomp: true) do |line|
+ lines << line
+ end
+ ensure
+ argf.close
+ end
+ assert_equal(%w[foo bar baz], lines)
+ end
+
def test_each_byte
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
@@ -719,7 +764,7 @@ class TestArgf < Test::Unit::TestCase
def test_each_char
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- s = ""
+ s = +""
ARGF.each_char {|c| s << c }
puts s
};
@@ -789,7 +834,7 @@ class TestArgf < Test::Unit::TestCase
def test_binmode
bug5268 = '[ruby-core:39234]'
- open(@t3.path, "wb") {|f| f.write "5\r\n6\r\n"}
+ File.binwrite(@t3.path, "5\r\n6\r\n")
ruby('-e', "ARGF.binmode; STDOUT.binmode; puts ARGF.read", @t1.path, @t2.path, @t3.path) do |f|
f.binmode
assert_equal("1\n2\n3\n4\n5\r\n6\r\n", f.read, bug5268)
@@ -798,7 +843,7 @@ class TestArgf < Test::Unit::TestCase
def test_textmode
bug5268 = '[ruby-core:39234]'
- open(@t3.path, "wb") {|f| f.write "5\r\n6\r\n"}
+ File.binwrite(@t3.path, "5\r\n6\r\n")
ruby('-e', "STDOUT.binmode; puts ARGF.read", @t1.path, @t2.path, @t3.path) do |f|
f.binmode
assert_equal("1\n2\n3\n4\n5\n6\n", f.read, bug5268)
@@ -951,53 +996,55 @@ class TestArgf < Test::Unit::TestCase
assert_nil(argf.gets, bug4274)
end
- def test_readlines_twice
- bug5952 = '[ruby-dev:45160]'
- assert_ruby_status(["-e", "2.times {STDIN.tty?; readlines}"], "", bug5952)
+ def test_readlines_chomp
+ t = make_tempfile
+ argf = ARGF.class.new(t.path)
+ begin
+ assert_equal(%w[foo bar baz], argf.readlines(chomp: true))
+ ensure
+ argf.close
+ end
+
+ assert_in_out_err(['-e', 'p readlines(chomp: true)'], "a\nb\n",
+ ["[\"a\", \"b\"]"], [])
end
- def test_lines
- ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
- {#
- $stderr = $stdout
- s = []
- ARGF.lines {|l| s << l }
- p s
- };
- assert_match(/deprecated/, f.gets)
- assert_equal("[\"1\\n\", \"2\\n\", \"3\\n\", \"4\\n\", \"5\\n\", \"6\\n\"]\n", f.read)
+ def test_readline_chomp
+ t = make_tempfile
+ argf = ARGF.class.new(t.path)
+ begin
+ assert_equal("foo", argf.readline(chomp: true))
+ ensure
+ argf.close
end
+
+ assert_in_out_err(['-e', 'p readline(chomp: true)'], "a\nb\n",
+ ["\"a\""], [])
end
- def test_bytes
- ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
- {#
- $stderr = $stdout
- print Marshal.dump(ARGF.bytes.to_a)
- };
- assert_match(/deprecated/, f.gets)
- assert_equal([49, 10, 50, 10, 51, 10, 52, 10, 53, 10, 54, 10], Marshal.load(f.read))
+ def test_gets_chomp
+ t = make_tempfile
+ argf = ARGF.class.new(t.path)
+ begin
+ assert_equal("foo", argf.gets(chomp: true))
+ ensure
+ argf.close
end
+
+ assert_in_out_err(['-e', 'p gets(chomp: true)'], "a\nb\n",
+ ["\"a\""], [])
end
- def test_chars
- ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
- {#
- $stderr = $stdout
- print [Marshal.dump(ARGF.chars.to_a)].pack('m')
- };
- assert_match(/deprecated/, f.gets)
- assert_equal(["1", "\n", "2", "\n", "3", "\n", "4", "\n", "5", "\n", "6", "\n"], Marshal.load(f.read.unpack('m').first))
- end
+ def test_readlines_twice
+ bug5952 = '[ruby-dev:45160]'
+ assert_ruby_status(["-e", "2.times {STDIN.tty?; readlines}"], "", bug5952)
end
- def test_codepoints
+ def test_each_codepoint
ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- $stderr = $stdout
- print Marshal.dump(ARGF.codepoints.to_a)
+ print Marshal.dump(ARGF.each_codepoint.to_a)
};
- assert_match(/deprecated/, f.gets)
assert_equal([49, 10, 50, 10, 51, 10, 52, 10, 53, 10, 54, 10], Marshal.load(f.read))
end
end
@@ -1006,7 +1053,7 @@ class TestArgf < Test::Unit::TestCase
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}") do |f|
{#
$stdout.sync = true
- :wait_readable == ARGF.read_nonblock(1, "", exception: false) or
+ :wait_readable == ARGF.read_nonblock(1, +"", exception: false) or
abort "did not return :wait_readable"
begin
@@ -1019,7 +1066,7 @@ class TestArgf < Test::Unit::TestCase
IO.select([ARGF]) == [[ARGF], [], []] or
abort 'did not awaken for readability (before byte)'
- buf = ''
+ buf = +''
buf.object_id == ARGF.read_nonblock(1, buf).object_id or
abort "read destination buffer failed"
print buf
@@ -1054,4 +1101,53 @@ class TestArgf < Test::Unit::TestCase
assert_raise(TypeError, bug11610) {gets}
};
end
+
+ def test_sized_read
+ s = "a"
+ [@t1, @t2, @t3].each { |t|
+ File.binwrite(t.path, s)
+ s = s.succ
+ }
+
+ ruby('-e', "print ARGF.read(3)", @t1.path, @t2.path, @t3.path) do |f|
+ assert_equal("abc", f.read)
+ end
+
+ argf = ARGF.class.new(@t1.path, @t2.path, @t3.path)
+ begin
+ assert_equal("abc", argf.read(3))
+ ensure
+ argf.close
+ end
+ end
+
+ def test_putc
+ t = make_tempfile("argf-#{__method__}", 'bar')
+ ruby('-pi-', '-e', "print ARGF.putc('x')", t.path) do |f|
+ end
+ assert_equal("xxbar\n", File.read(t.path))
+ end
+
+ def test_puts
+ t = make_tempfile("argf-#{__method__}", 'bar')
+ err = "#{@tmpdir}/errout"
+ ruby('-pi-', '-W2', '-e', "print ARGF.puts('foo')", t.path, {err: err}) do |f|
+ end
+ assert_equal("foo\nbar\n", File.read(t.path))
+ assert_empty File.read(err)
+ end
+
+ def test_print
+ t = make_tempfile("argf-#{__method__}", 'bar')
+ ruby('-pi-', '-e', "print ARGF.print('foo')", t.path) do |f|
+ end
+ assert_equal("foobar\n", File.read(t.path))
+ end
+
+ def test_printf
+ t = make_tempfile("argf-#{__method__}", 'bar')
+ ruby('-pi-', '-e', "print ARGF.printf('%s', 'foo')", t.path) do |f|
+ end
+ assert_equal("foobar\n", File.read(t.path))
+ end
end
diff --git a/test/ruby/test_arithmetic_sequence.rb b/test/ruby/test_arithmetic_sequence.rb
new file mode 100644
index 0000000000..5e2a825265
--- /dev/null
+++ b/test/ruby/test_arithmetic_sequence.rb
@@ -0,0 +1,491 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestArithmeticSequence < Test::Unit::TestCase
+ def test_new
+ assert_raise(NoMethodError) { Enumerator::ArithmeticSequence.new }
+ end
+
+ def test_allocate
+ assert_raise(TypeError) { Enumerator::ArithmeticSequence.allocate }
+ end
+
+ def test_begin
+ assert_equal(1, 1.step.begin)
+ assert_equal(1, 1.step(10).begin)
+ assert_equal(1, 1.step(to: 10).begin)
+ assert_equal(1, 1.step(nil).begin)
+ assert_equal(1, 1.step(to: nil).begin)
+ assert_equal(1, 1.step(by: 2).begin)
+ assert_equal(1, 1.step(by: -1).begin)
+ assert_equal(1, 1.step(by: nil).begin)
+ assert_equal(1, 1.step(10, 2).begin)
+ assert_equal(1, 1.step(10, by: 2).begin)
+ assert_equal(1, 1.step(to: 10, by: 2).begin)
+ assert_equal(10, 10.step(to: 1, by: -1).begin)
+ assert_equal(10, 10.step(to: 1, by: -2).begin)
+ assert_equal(10, 10.step(to: -1, by: -2).begin)
+ assert_equal(10.0, 10.0.step(to: -1.0, by: -2.0).begin)
+
+ assert_equal(3, (3..).step(2).begin)
+ assert_equal(4, (4...).step(7).begin)
+ assert_equal(nil, (..10).step(9).begin)
+ assert_equal(nil, (...11).step(5).begin)
+ end
+
+ def test_end
+ assert_equal(nil, 1.step.end)
+ assert_equal(10, 1.step(10).end)
+ assert_equal(10, 1.step(to: 10).end)
+ assert_equal(nil, 1.step(nil).end)
+ assert_equal(nil, 1.step(to: nil).end)
+ assert_equal(nil, 1.step(by: 2).end)
+ assert_equal(nil, 1.step(by: -1).end)
+ assert_equal(nil, 1.step(by: nil).end)
+ assert_equal(10, 1.step(10, 2).end)
+ assert_equal(10, 1.step(10, by: 2).end)
+ assert_equal(10, 1.step(to: 10, by: 2).end)
+ assert_equal(1, 10.step(to: 1, by: -1).end)
+ assert_equal(1, 10.step(to: 1, by: -2).end)
+ assert_equal(-1, 10.step(to: -1, by: -2).end)
+ assert_equal(-1.0, 10.0.step(to: -1.0, by: -2.0).end)
+
+ assert_equal(nil, (3..).step(2).end)
+ assert_equal(nil, (4...).step(7).end)
+ assert_equal(10, (..10).step(9).end)
+ assert_equal(11, (...11).step(5).end)
+ end
+
+ def test_exclude_end_p
+ assert_equal(false, 1.step.exclude_end?)
+ assert_equal(false, 1.step(10).exclude_end?)
+ assert_equal(false, 1.step(to: 10).exclude_end?)
+ assert_equal(false, 1.step(nil).exclude_end?)
+ assert_equal(false, 1.step(to: nil).exclude_end?)
+ assert_equal(false, 1.step(by: 2).exclude_end?)
+ assert_equal(false, 1.step(by: -1).exclude_end?)
+ assert_equal(false, 1.step(by: nil).exclude_end?)
+ assert_equal(false, 1.step(10, 2).exclude_end?)
+ assert_equal(false, 1.step(10, by: 2).exclude_end?)
+ assert_equal(false, 1.step(to: 10, by: 2).exclude_end?)
+ assert_equal(false, 10.step(to: 1, by: -1).exclude_end?)
+ assert_equal(false, 10.step(to: 1, by: -2).exclude_end?)
+ assert_equal(false, 10.step(to: -1, by: -2).exclude_end?)
+
+ assert_equal(false, (3..).step(2).exclude_end?)
+ assert_equal(true, (4...).step(7).exclude_end?)
+ assert_equal(false, (..10).step(9).exclude_end?)
+ assert_equal(true, (...11).step(5).exclude_end?)
+ end
+
+ def test_step
+ assert_equal(1, 1.step.step)
+ assert_equal(1, 1.step(10).step)
+ assert_equal(1, 1.step(to: 10).step)
+ assert_equal(1, 1.step(nil).step)
+ assert_equal(1, 1.step(to: nil).step)
+ assert_equal(2, 1.step(by: 2).step)
+ assert_equal(-1, 1.step(by: -1).step)
+ assert_equal(1, 1.step(by: nil).step)
+ assert_equal(2, 1.step(10, 2).step)
+ assert_equal(2, 1.step(10, by: 2).step)
+ assert_equal(2, 1.step(to: 10, by: 2).step)
+ assert_equal(-1, 10.step(to: 1, by: -1).step)
+ assert_equal(-2, 10.step(to: 1, by: -2).step)
+ assert_equal(-2, 10.step(to: -1, by: -2).step)
+ assert_equal(-2.0, 10.0.step(to: -1.0, by: -2.0).step)
+
+ assert_equal(2, (3..).step(2).step)
+ assert_equal(7, (4...).step(7).step)
+ assert_equal(9, (..10).step(9).step)
+ assert_equal(5, (...11).step(5).step)
+ end
+
+ def test_eq
+ seq = 1.step
+ assert_equal(seq, seq)
+ assert_equal(seq, 1.step)
+ assert_equal(seq, 1.step(nil))
+ end
+
+ def test_eqq
+ seq = 1.step
+ assert_operator(seq, :===, seq)
+ assert_operator(seq, :===, 1.step)
+ assert_operator(seq, :===, 1.step(nil))
+ end
+
+ def test_eql_p
+ seq = 1.step
+ assert_operator(seq, :eql?, seq)
+ assert_operator(seq, :eql?, 1.step)
+ assert_operator(seq, :eql?, 1.step(nil))
+ end
+
+ def test_hash
+ seq = 1.step
+ assert_equal(seq.hash, seq.hash)
+ assert_equal(seq.hash, 1.step.hash)
+ assert_equal(seq.hash, 1.step(nil).hash)
+ assert_kind_of(String, seq.hash.to_s)
+ end
+
+ def test_first
+ seq = 1.step
+ assert_equal(1, seq.first)
+ assert_equal([1], seq.first(1))
+ assert_equal([1, 2, 3], seq.first(3))
+
+ seq = 1.step(by: 2)
+ assert_equal(1, seq.first)
+ assert_equal([1], seq.first(1))
+ assert_equal([1, 3, 5], seq.first(3))
+
+ seq = 10.step(by: -2)
+ assert_equal(10, seq.first)
+ assert_equal([10], seq.first(1))
+ assert_equal([10, 8, 6], seq.first(3))
+
+ seq = 1.step(by: 4)
+ assert_equal([1, 5, 9], seq.first(3))
+
+ seq = 1.step(10, by: 4)
+ assert_equal([1, 5, 9], seq.first(5))
+
+ seq = 1.step(0)
+ assert_equal(nil, seq.first)
+ assert_equal([], seq.first(1))
+ assert_equal([], seq.first(3))
+
+ seq = 1.step(10, by: -1)
+ assert_equal(nil, seq.first)
+ assert_equal([], seq.first(1))
+ assert_equal([], seq.first(3))
+
+ seq = 10.0.step(-1.0, by: -2.0)
+ assert_equal(10.0, seq.first)
+ assert_equal([10.0], seq.first(1))
+ assert_equal([10.0, 8.0, 6.0], seq.first(3))
+
+ seq = (1..).step(2)
+ assert_equal(1, seq.first)
+ assert_equal([1], seq.first(1))
+ assert_equal([1, 3, 5], seq.first(3))
+
+ seq = (..10).step(2)
+ assert_equal(nil, seq.first)
+ assert_raise(TypeError) { seq.first(1) }
+ assert_raise(TypeError) { seq.first(3) }
+ end
+
+ def test_first_bug15518
+ bug15518 = '[Bug #15518]'
+ seq = (1 .. 10.0).step(1)
+ five_float_classes = Array.new(5) { Float }
+ assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518)
+ assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518)
+ seq = (1 .. Float::INFINITY).step(1)
+ assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518)
+ assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518)
+ seq = (1 .. Float::INFINITY).step(1r)
+ assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518)
+ assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518)
+ end
+
+ def test_last
+ seq = 1.step(10)
+ assert_equal(10, seq.last)
+ assert_equal([10], seq.last(1))
+ assert_equal([8, 9, 10], seq.last(3))
+
+ seq = 1.step(10, 2)
+ assert_equal(9, seq.last)
+ assert_equal([9], seq.last(1))
+ assert_equal([5, 7, 9], seq.last(3))
+
+ seq = 10.step(1, -2)
+ assert_equal(2, seq.last)
+ assert_equal([2], seq.last(1))
+ assert_equal([6, 4, 2], seq.last(3))
+
+ seq = 10.step(-1, -2)
+ assert_equal(0, seq.last)
+
+ seq = 1.step(10, 4)
+ assert_equal([1, 5, 9], seq.last(5))
+
+ seq = 10.step(1)
+ assert_equal(nil, seq.last)
+ assert_equal([], seq.last(1))
+ assert_equal([], seq.last(5))
+
+ seq = 1.step(10, -1)
+ assert_equal(nil, seq.last)
+ assert_equal([], seq.last(1))
+ assert_equal([], seq.last(5))
+
+ seq = (1..10).step
+ assert_equal(10, seq.last)
+ assert_equal([10], seq.last(1))
+ assert_equal([8, 9, 10], seq.last(3))
+
+ seq = (1...10).step
+ assert_equal(9, seq.last)
+ assert_equal([9], seq.last(1))
+ assert_equal([7, 8, 9], seq.last(3))
+
+ seq = 10.0.step(-3.0, by: -2.0)
+ assert_equal(-2.0, seq.last)
+ assert_equal([-2.0], seq.last(1))
+ assert_equal([2.0, 0.0, -2.0], seq.last(3))
+ end
+
+ def test_last_with_float
+ res = (1..3).step(2).last(2.0)
+ assert_equal([1, 3], res)
+ assert_instance_of Integer, res[0]
+ assert_instance_of Integer, res[1]
+
+ res = (1..3).step(2).last(5.0)
+ assert_equal([1, 3], res)
+ assert_instance_of Integer, res[0]
+ assert_instance_of Integer, res[1]
+ end
+
+ def test_last_with_rational
+ res = (1..3).step(2).last(2r)
+ assert_equal([1, 3], res)
+ assert_instance_of Integer, res[0]
+ assert_instance_of Integer, res[1]
+
+ res = (1..3).step(2).last(10/2r)
+ assert_equal([1, 3], res)
+ assert_instance_of Integer, res[0]
+ assert_instance_of Integer, res[1]
+ end
+
+ def test_last_bug17218
+ seq = (1.0997r .. 1.1r).step(0.0001r)
+ assert_equal(1.1r, seq.last, '[ruby-core:100312] [Bug #17218]')
+ end
+
+ def test_to_a
+ assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 1.step(10).to_a)
+ assert_equal([1, 3, 5, 7, 9], 1.step(10, 2).to_a)
+ assert_equal([1, 3, 5, 7, 9], (1..10).step(2).to_a)
+ assert_equal([10, 8, 6, 4, 2], 10.step(1, by: -2).to_a)
+ assert_equal([10, 8, 6, 4, 2], (10..1).step(-2).to_a)
+ assert_equal([10.0, 8.0, 6.0, 4.0, 2.0], (10.0..1.0).step(-2.0).to_a)
+ end
+
+ def test_to_a_bug15444
+ seq = ((1/10r)..(1/2r)).step(1/10r)
+ assert_num_equal_type([1/10r, 1/5r, 3/10r, 2/5r, 1/2r], seq.to_a,
+ '[ruby-core:90648] [Bug #15444]')
+ end
+
+ def test_to_a_bug17218
+ seq = (1.0997r .. 1.1r).step(0.0001r)
+ assert_equal([1.0997r, 1.0998r, 1.0999r, 1.1r], seq.to_a, '[ruby-core:100312] [Bug #17218]')
+ end
+
+ def test_slice
+ seq = 1.step(10, 2)
+ assert_equal([[1, 3, 5], [7, 9]], seq.each_slice(3).to_a)
+
+ seq = 10.step(1, -2)
+ assert_equal([[10, 8, 6], [4, 2]], seq.each_slice(3).to_a)
+ end
+
+ def test_cons
+ seq = 1.step(10, 2)
+ assert_equal([[1, 3, 5], [3, 5, 7], [5, 7, 9]], seq.each_cons(3).to_a)
+
+ seq = 10.step(1, -2)
+ assert_equal([[10, 8, 6], [8, 6, 4], [6, 4, 2]], seq.each_cons(3).to_a)
+ end
+
+ def test_with_index
+ seq = 1.step(6, 2)
+ assert_equal([[1, 0], [3, 1], [5, 2]], seq.with_index.to_a)
+ assert_equal([[1, 10], [3, 11], [5, 12]], seq.with_index(10).to_a)
+
+ seq = 10.step(5, -2)
+ assert_equal([[10, 0], [8, 1], [6, 2]], seq.with_index.to_a)
+ assert_equal([[10, 10], [8, 11], [6, 12]], seq.with_index(10).to_a)
+ end
+
+ def test_with_object
+ obj = [0, 1]
+ seq = 1.step(10, 2)
+ ret = seq.each_with_object(obj) do |i, memo|
+ memo[0] += i
+ memo[1] *= i
+ end
+ assert_same(obj, ret)
+ assert_equal([25, 945], ret)
+
+ obj = [0, 1]
+ seq = 10.step(1, -2)
+ ret = seq.each_with_object(obj) do |i, memo|
+ memo[0] += i
+ memo[1] *= i
+ end
+ assert_same(obj, ret)
+ assert_equal([30, 3840], ret)
+ end
+
+ def test_next
+ seq = 1.step(10, 2)
+ [1, 3, 5, 7, 9].each do |i|
+ assert_equal(i, seq.next)
+ end
+
+ seq = 10.step(1, -2)
+ [10, 8, 6, 4, 2].each do |i|
+ assert_equal(i, seq.next)
+ end
+ end
+
+ def test_next_bug15444
+ seq = ((1/10r)..(1/2r)).step(1/10r)
+ assert_equal(1/10r, seq.next, '[ruby-core:90648] [Bug #15444]')
+ end
+
+ def test_next_rewind
+ seq = 1.step(6, 2)
+ assert_equal(1, seq.next)
+ assert_equal(3, seq.next)
+ seq.rewind
+ assert_equal(1, seq.next)
+ assert_equal(3, seq.next)
+ assert_equal(5, seq.next)
+ assert_raise(StopIteration) { seq.next }
+
+ seq = 10.step(5, -2)
+ assert_equal(10, seq.next)
+ assert_equal(8, seq.next)
+ seq.rewind
+ assert_equal(10, seq.next)
+ assert_equal(8, seq.next)
+ assert_equal(6, seq.next)
+ assert_raise(StopIteration) { seq.next }
+ end
+
+ def test_next_after_stopiteration
+ seq = 1.step(2, 2)
+ assert_equal(1, seq.next)
+ assert_raise(StopIteration) { seq.next }
+ assert_raise(StopIteration) { seq.next }
+ seq.rewind
+ assert_equal(1, seq.next)
+ assert_raise(StopIteration) { seq.next }
+ assert_raise(StopIteration) { seq.next }
+ end
+
+ def test_stop_result
+ seq = 1.step(2, 2)
+ res = seq.each {}
+ assert_equal(1, seq.next)
+ exc = assert_raise(StopIteration) { seq.next }
+ assert_equal(res, exc.result)
+ end
+
+ def test_peek
+ seq = 1.step(2, 2)
+ assert_equal(1, seq.peek)
+ assert_equal(1, seq.peek)
+ assert_equal(1, seq.next)
+ assert_raise(StopIteration) { seq.peek }
+ assert_raise(StopIteration) { seq.peek }
+
+ seq = 10.step(9, -2)
+ assert_equal(10, seq.peek)
+ assert_equal(10, seq.peek)
+ assert_equal(10, seq.next)
+ assert_raise(StopIteration) { seq.peek }
+ assert_raise(StopIteration) { seq.peek }
+ end
+
+ def test_next_values
+ seq = 1.step(2, 2)
+ assert_equal([1], seq.next_values)
+ end
+
+ def test_peek_values
+ seq = 1.step(2, 2)
+ assert_equal([1], seq.peek_values)
+ end
+
+ def test_num_step_inspect
+ assert_equal('(1.step)', 1.step.inspect)
+ assert_equal('(1.step(10))', 1.step(10).inspect)
+ assert_equal('(1.step(10, 2))', 1.step(10, 2).inspect)
+ assert_equal('(1.step(10, by: 2))', 1.step(10, by: 2).inspect)
+ assert_equal('(1.step(by: 2))', 1.step(by: 2).inspect)
+ end
+
+ def test_range_step_inspect
+ assert_equal('((1..).step)', (1..).step.inspect)
+ assert_equal('((1..10).step)', (1..10).step.inspect)
+ assert_equal('((1..10).step(2))', (1..10).step(2).inspect)
+ end
+
+ def test_num_step_size
+ assert_equal(10, 1.step(10).size)
+ assert_equal(5, 1.step(10, 2).size)
+ assert_equal(4, 1.step(10, 3).size)
+ assert_equal(1, 1.step(10, 10).size)
+ assert_equal(0, 1.step(0).size)
+ assert_equal(Float::INFINITY, 1.step.size)
+
+ assert_equal(10, 10.step(1, -1).size)
+ assert_equal(5, 10.step(1, -2).size)
+ assert_equal(4, 10.step(1, -3).size)
+ assert_equal(1, 10.step(1, -10).size)
+ assert_equal(0, 1.step(2, -1).size)
+ assert_equal(Float::INFINITY, 1.step(by: -1).size)
+ end
+
+ def test_range_step_size
+ assert_equal(10, (1..10).step.size)
+ assert_equal(9, (1...10).step.size)
+ assert_equal(5, (1..10).step(2).size)
+ assert_equal(5, (1...10).step(2).size)
+ assert_equal(4, (1...9).step(2).size)
+ assert_equal(Float::INFINITY, (1..).step.size)
+
+ assert_equal(10, (10..1).step(-1).size)
+ assert_equal(9, (10...1).step(-1).size)
+ assert_equal(5, (10..1).step(-2).size)
+ assert_equal(5, (10...1).step(-2).size)
+ assert_equal(4, (10...2).step(-2).size)
+ assert_equal(Float::INFINITY, (1..).step(-1).size)
+ end
+
+ def assert_num_equal_type(ary1, ary2, message=nil)
+ assert_equal(ary1.length, ary2.length, message)
+ ary1.zip(ary2) do |e1, e2|
+ assert_equal(e1.class, e2.class, message)
+ if e1.is_a? Complex
+ assert_equal(e1.real, e2.real, message)
+ assert_equal(e1.imag, e2.imag, message)
+ else
+ assert_equal(e1, e2, message)
+ end
+ end
+ end
+
+ def test_complex
+ assert_num_equal_type([1, 1+1i, 1+2i], (1..).step(1i).take(3))
+ assert_num_equal_type([1, 1+1.0i, 1+2.0i], (1..).step(1.0i).take(3))
+ assert_num_equal_type([0.0, 0.0+1.0i, 0.0+2.0i], (0.0..).step(1.0i).take(3))
+ assert_num_equal_type([0.0+0.0i, 0.0+1.0i, 0.0+2.0i], (0.0i..).step(1.0i).take(3))
+ end
+
+ def test_sum
+ assert_equal([1, 3, 5, 7, 9].sum, (1..10).step(2).sum)
+ assert_equal([1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0].sum, (1.0..10.0).step(1.5).sum)
+ assert_equal([1/2r, 1r, 3/2r, 2, 5/2r, 3, 7/2r, 4].sum, ((1/2r)...(9/2r)).step(1/2r).sum)
+ end
+end
diff --git a/test/ruby/test_arity.rb b/test/ruby/test_arity.rb
index b98248f603..bd26d5f0f5 100644
--- a/test/ruby/test_arity.rb
+++ b/test/ruby/test_arity.rb
@@ -2,7 +2,7 @@
require 'test/unit'
class TestArity < Test::Unit::TestCase
- def err_mess(method_proc = nil, argc = 0)
+ def assert_arity(expected, method_proc = nil, argc = 0)
args = (1..argc).to_a
assert_raise_with_message(ArgumentError, /wrong number of arguments \(.*\b(\d+)\b.* (\d\S*?)\)/) do
case method_proc
@@ -14,7 +14,7 @@ class TestArity < Test::Unit::TestCase
method_proc.call(*args)
end
end
- [$1, $2]
+ assert_equal expected, [$1, $2]
end
def a
@@ -36,22 +36,22 @@ class TestArity < Test::Unit::TestCase
end
def test_method_err_mess
- assert_equal %w[1 0], err_mess(:a, 1)
- assert_equal %w[10 7..9], err_mess(:b, 10)
- assert_equal %w[2 3+], err_mess(:c, 2)
- assert_equal %w[2 1], err_mess(:d, 2)
- assert_equal %w[0 1], err_mess(:d, 0)
- assert_equal %w[2 1], err_mess(:e, 2)
- assert_equal %w[0 1], err_mess(:e, 0)
- assert_equal %w[1 2+], err_mess(:f, 1)
+ assert_arity(%w[1 0], :a, 1)
+ assert_arity(%w[10 7..9], :b, 10)
+ assert_arity(%w[2 3+], :c, 2)
+ assert_arity(%w[2 1], :d, 2)
+ assert_arity(%w[0 1], :d, 0)
+ assert_arity(%w[2 1], :e, 2)
+ assert_arity(%w[0 1], :e, 0)
+ assert_arity(%w[1 2+], :f, 1)
end
def test_proc_err_mess
- assert_equal %w[0 1..2], err_mess(->(b, c=42){}, 0)
- assert_equal %w[1 2+], err_mess(->(a, b, c=42, *d){}, 1)
- assert_equal %w[3 4+], err_mess(->(a, b, *c, d, e){}, 3)
- assert_equal %w[3 1..2], err_mess(->(b, c=42){}, 3)
- assert_equal %w[1 0], err_mess(->(&block){}, 1)
+ assert_arity(%w[0 1..2], ->(b, c=42){}, 0)
+ assert_arity(%w[1 2+], ->(a, b, c=42, *d){}, 1)
+ assert_arity(%w[3 4+], ->(a, b, *c, d, e){}, 3)
+ assert_arity(%w[3 1..2], ->(b, c=42){}, 3)
+ assert_arity(%w[1 0], ->(&block){}, 1)
# Double checking:
p = Proc.new{|b, c=42| :ok}
assert_equal :ok, p.call(1, 2, 3)
@@ -59,12 +59,11 @@ class TestArity < Test::Unit::TestCase
end
def test_message_change_issue_6085
- assert_equal %w[3 1..2], err_mess{ SignalException.new(1, "", nil) }
- assert_equal %w[1 0], err_mess{ Hash.new(1){} }
- assert_equal %w[3 1..2], err_mess{ Module.send :define_method, 1, 2, 3 }
- assert_equal %w[1 2], err_mess{ "".sub!(//) }
- assert_equal %w[0 1..2], err_mess{ "".sub!{} }
- assert_equal %w[0 1+], err_mess{ exec }
- assert_equal %w[0 1+], err_mess{ Struct.new }
+ assert_arity(%w[3 1..2]) { SignalException.new(1, "", nil) }
+ assert_arity(%w[1 0]) { Hash.new(1){} }
+ assert_arity(%w[3 1..2]) { Module.send :define_method, 1, 2, 3 }
+ assert_arity(%w[1 2]) { "".sub!(//) }
+ assert_arity(%w[0 1..2]) { "".sub!{} }
+ assert_arity(%w[0 1+]) { exec }
end
end
diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb
index 3212ed3aca..04e15b6d87 100644
--- a/test/ruby/test_array.rb
+++ b/test/ruby/test_array.rb
@@ -7,7 +7,6 @@ require "rbconfig/sizeof"
class TestArray < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
@cls = Array
end
@@ -15,6 +14,11 @@ class TestArray < Test::Unit::TestCase
$VERBOSE = @verbose
end
+ def assert_equal_instance(x, y, *msg)
+ assert_equal(x, y, *msg)
+ assert_instance_of(x.class, y)
+ end
+
def test_percent_i
assert_equal([:foo, :bar], %i[foo bar])
assert_equal([:"\"foo"], %i["foo])
@@ -41,6 +45,9 @@ class TestArray < Test::Unit::TestCase
assert_equal(2, x[2])
assert_equal([1, 2, 3], x[1..3])
assert_equal([1, 2, 3], x[1,3])
+ assert_equal([3, 4, 5], x[3..])
+ assert_equal([0, 1, 2], x[..2])
+ assert_equal([0, 1], x[...2])
x[0, 2] = 10
assert_equal([10, 2, 3, 4, 5], x)
@@ -111,6 +118,9 @@ class TestArray < Test::Unit::TestCase
assert_equal('1', (x * 1).join(":"))
assert_equal('', (x * 0).join(":"))
+ assert_instance_of(Array, (@cls[] * 5))
+ assert_instance_of(Array, (@cls[1] * 5))
+
*x = *(1..7).to_a
assert_equal(7, x.size)
assert_equal([1, 2, 3, 4, 5, 6, 7], x)
@@ -145,14 +155,17 @@ class TestArray < Test::Unit::TestCase
assert_equal(1, x.first)
assert_equal([1], x.first(1))
assert_equal([1, 2, 3], x.first(3))
+ assert_raise_with_message(ArgumentError, /0\.\.1/) {x.first(1, 2)}
assert_equal(5, x.last)
assert_equal([5], x.last(1))
assert_equal([3, 4, 5], x.last(3))
+ assert_raise_with_message(ArgumentError, /0\.\.1/) {x.last(1, 2)}
assert_equal(1, x.shift)
assert_equal([2, 3, 4], x.shift(3))
assert_equal([5], x)
+ assert_raise_with_message(ArgumentError, /0\.\.1/) {x.first(1, 2)}
assert_equal([2, 3, 4, 5], x.unshift(2, 3, 4))
assert_equal([1, 2, 3, 4, 5], x.unshift(1))
@@ -161,6 +174,7 @@ class TestArray < Test::Unit::TestCase
assert_equal(5, x.pop)
assert_equal([3, 4], x.pop(2))
assert_equal([1, 2], x)
+ assert_raise_with_message(ArgumentError, /0\.\.1/) {x.pop(1, 2)}
assert_equal([1, 2, 3, 4], x.push(3, 4))
assert_equal([1, 2, 3, 4, 5], x.push(5))
@@ -170,6 +184,7 @@ class TestArray < Test::Unit::TestCase
def test_find_all_0
assert_respond_to([], :find_all)
assert_respond_to([], :select) # Alias
+ assert_respond_to([], :filter) # Alias
assert_equal([], [].find_all{ |obj| obj == "foo"})
x = ["foo", "bar", "baz", "baz", 1, 2, 3, 3, 4]
@@ -198,6 +213,8 @@ class TestArray < Test::Unit::TestCase
assert_equal([0, 1, 2, 13, 4, 5], [0, 1, 2, 3, 4, 5].fill(3...4){|i| i+10})
assert_equal([0, 1, 12, 13, 14, 5], [0, 1, 2, 3, 4, 5].fill(2..-2){|i| i+10})
assert_equal([0, 1, 12, 13, 4, 5], [0, 1, 2, 3, 4, 5].fill(2...-2){|i| i+10})
+ assert_equal([0, 1, 2, 13, 14, 15], [0, 1, 2, 3, 4, 5].fill(3..){|i| i+10})
+ assert_equal([0, 1, 2, 13, 14, 15], [0, 1, 2, 3, 4, 5].fill(3...){|i| i+10})
end
# From rubicon
@@ -231,6 +248,23 @@ class TestArray < Test::Unit::TestCase
assert_equal(@cls[], @cls[ 1, 2, 3 ]*64 & @cls[ 4, 5, 6 ]*64)
end
+ def test_intersection
+ assert_equal(@cls[1, 2], @cls[1, 2, 3].intersection(@cls[1, 2]))
+ assert_equal(@cls[ ], @cls[1].intersection(@cls[ ]))
+ assert_equal(@cls[ ], @cls[ ].intersection(@cls[1]))
+ assert_equal(@cls[1], @cls[1, 2, 3].intersection(@cls[1, 2], @cls[1]))
+ assert_equal(@cls[ ], @cls[1, 2, 3].intersection(@cls[1, 2], @cls[3]))
+ assert_equal(@cls[ ], @cls[1, 2, 3].intersection(@cls[4, 5, 6]))
+ end
+
+ def test_intersection_big_array
+ assert_equal(@cls[1, 2], (@cls[1, 2, 3] * 64).intersection(@cls[1, 2] * 64))
+ assert_equal(@cls[ ], (@cls[1] * 64).intersection(@cls[ ]))
+ assert_equal(@cls[ ], @cls[ ].intersection(@cls[1] * 64))
+ assert_equal(@cls[1], (@cls[1, 2, 3] * 64).intersection((@cls[1, 2] * 64), (@cls[1] * 64)))
+ assert_equal(@cls[ ], (@cls[1, 2, 3] * 64).intersection(@cls[4, 5, 6] * 64))
+ end
+
def test_MUL # '*'
assert_equal(@cls[], @cls[]*3)
assert_equal(@cls[1, 1, 1], @cls[1]*3)
@@ -254,29 +288,39 @@ class TestArray < Test::Unit::TestCase
def test_MINUS # '-'
assert_equal(@cls[], @cls[1] - @cls[1])
assert_equal(@cls[1], @cls[1, 2, 3, 4, 5] - @cls[2, 3, 4, 5])
- # Ruby 1.8 feature change
- #assert_equal(@cls[1], @cls[1, 2, 1, 3, 1, 4, 1, 5] - @cls[2, 3, 4, 5])
assert_equal(@cls[1, 1, 1, 1], @cls[1, 2, 1, 3, 1, 4, 1, 5] - @cls[2, 3, 4, 5])
- a = @cls[]
- 1000.times { a << 1 }
- assert_equal(1000, a.length)
- #assert_equal(@cls[1], a - @cls[2])
- assert_equal(@cls[1] * 1000, a - @cls[2])
- #assert_equal(@cls[1], @cls[1, 2, 1] - @cls[2])
assert_equal(@cls[1, 1], @cls[1, 2, 1] - @cls[2])
assert_equal(@cls[1, 2, 3], @cls[1, 2, 3] - @cls[4, 5, 6])
end
def test_MINUS_big_array # '-'
assert_equal(@cls[1]*64, @cls[1, 2, 3, 4, 5]*64 - @cls[2, 3, 4, 5]*64)
- # Ruby 1.8 feature change
- #assert_equal(@cls[1], @cls[1, 2, 1, 3, 1, 4, 1, 5]*64 - @cls[2, 3, 4, 5]*64)
assert_equal(@cls[1, 1, 1, 1]*64, @cls[1, 2, 1, 3, 1, 4, 1, 5]*64 - @cls[2, 3, 4, 5]*64)
a = @cls[]
1000.times { a << 1 }
assert_equal(1000, a.length)
- #assert_equal(@cls[1], a - @cls[2])
- assert_equal(@cls[1] * 1000, a - @cls[2])
+ assert_equal(@cls[1] * 1000, a - @cls[2])
+ end
+
+ def test_difference
+ assert_equal(@cls[], @cls[1].difference(@cls[1]))
+ assert_equal(@cls[1], @cls[1, 2, 3, 4, 5].difference(@cls[2, 3, 4, 5]))
+ assert_equal(@cls[1, 1], @cls[1, 2, 1].difference(@cls[2]))
+ assert_equal(@cls[1, 1, 1, 1], @cls[1, 2, 1, 3, 1, 4, 1, 5].difference(@cls[2, 3, 4, 5]))
+ assert_equal(@cls[], @cls[1, 2, 3, 4].difference(@cls[1], @cls[2], @cls[3], @cls[4]))
+ a = [1]
+ assert_equal(@cls[1], a.difference(@cls[2], @cls[2]))
+ assert_equal(@cls[], a.difference(@cls[1]))
+ assert_equal(@cls[1], a)
+ end
+
+ def test_difference_big_array
+ assert_equal(@cls[1]*64, (@cls[1, 2, 3, 4, 5] * 64).difference(@cls[2, 3, 4] * 64, @cls[3, 5] * 64))
+ assert_equal(@cls[1, 1, 1, 1]*64, (@cls[1, 2, 1, 3, 1, 4, 1, 5] * 64).difference(@cls[2, 3, 4, 5] * 64))
+ a = @cls[1] * 1000
+ assert_equal(@cls[1] * 1000, a.difference(@cls[2], @cls[2]))
+ assert_equal(@cls[], a.difference(@cls[1]))
+ assert_equal(@cls[1] * 1000, a)
end
def test_LSHIFT # '<<'
@@ -352,12 +396,15 @@ class TestArray < Test::Unit::TestCase
assert_equal(@cls[99], a[-2..-2])
assert_equal(@cls[10, 11, 12], a[9..11])
+ assert_equal(@cls[98, 99, 100], a[97..])
+ assert_equal(@cls[1, 2, 3], a[..2])
+ assert_equal(@cls[1, 2], a[...2])
assert_equal(@cls[10, 11, 12], a[-91..-89])
+ assert_equal(@cls[98, 99, 100], a[-3..])
+ assert_equal(@cls[1, 2, 3], a[..-98])
+ assert_equal(@cls[1, 2], a[...-98])
assert_nil(a[10, -3])
- # Ruby 1.8 feature change:
- # Array#[size..x] returns [] instead of nil.
- #assert_nil(a[10..7])
assert_equal [], a[10..7]
assert_raise(TypeError) {a['cat']}
@@ -413,33 +460,41 @@ class TestArray < Test::Unit::TestCase
assert_equal(b, a[10..19] = b)
assert_equal(@cls[*(0..9).to_a] + b + @cls[*(20..99).to_a], a)
- # Ruby 1.8 feature change:
- # assigning nil does not remove elements.
-=begin
a = @cls[*(0..99).to_a]
assert_equal(nil, a[0,1] = nil)
- assert_equal(@cls[*(1..99).to_a], a)
+ assert_equal(@cls[nil] + @cls[*(1..99).to_a], a)
a = @cls[*(0..99).to_a]
assert_equal(nil, a[10,10] = nil)
- assert_equal(@cls[*(0..9).to_a] + @cls[*(20..99).to_a], a)
+ assert_equal(@cls[*(0..9).to_a] + @cls[nil] + @cls[*(20..99).to_a], a)
a = @cls[*(0..99).to_a]
assert_equal(nil, a[-1, 1] = nil)
- assert_equal(@cls[*(0..98).to_a], a)
+ assert_equal(@cls[*(0..98).to_a] + @cls[nil], a)
a = @cls[*(0..99).to_a]
assert_equal(nil, a[-10, 10] = nil)
- assert_equal(@cls[*(0..89).to_a], a)
+ assert_equal(@cls[*(0..89).to_a] + @cls[nil], a)
a = @cls[*(0..99).to_a]
assert_equal(nil, a[0,1000] = nil)
- assert_equal(@cls[] , a)
+ assert_equal(@cls[nil] , a)
a = @cls[*(0..99).to_a]
assert_equal(nil, a[10..19] = nil)
- assert_equal(@cls[*(0..9).to_a] + @cls[*(20..99).to_a], a)
-=end
+ assert_equal(@cls[*(0..9).to_a] + @cls[nil] + @cls[*(20..99).to_a], a)
+
+ a = @cls[*(0..99).to_a]
+ assert_equal(nil, a[10..] = nil)
+ assert_equal(@cls[*(0..9).to_a] + @cls[nil], a)
+
+ a = @cls[*(0..99).to_a]
+ assert_equal(nil, a[..10] = nil)
+ assert_equal(@cls[nil] + @cls[*(11..99).to_a], a)
+
+ a = @cls[*(0..99).to_a]
+ assert_equal(nil, a[...10] = nil)
+ assert_equal(@cls[nil] + @cls[*(10..99).to_a], a)
a = @cls[1, 2, 3]
a[1, 0] = a
@@ -474,14 +529,19 @@ class TestArray < Test::Unit::TestCase
end
def test_assoc
+ def (a4 = Object.new).to_ary
+ %w( pork porcine )
+ end
+
a1 = @cls[*%w( cat feline )]
a2 = @cls[*%w( dog canine )]
a3 = @cls[*%w( mule asinine )]
- a = @cls[ a1, a2, a3 ]
+ a = @cls[ a1, a2, a3, a4 ]
assert_equal(a1, a.assoc('cat'))
assert_equal(a3, a.assoc('mule'))
+ assert_equal(%w( pork porcine ), a.assoc("pork"))
assert_equal(nil, a.assoc('asinine'))
assert_equal(nil, a.assoc('wombat'))
assert_equal(nil, a.assoc(1..2))
@@ -508,18 +568,14 @@ class TestArray < Test::Unit::TestCase
end
def test_clone
- for taint in [ false, true ]
- for frozen in [ false, true ]
- a = @cls[*(0..99).to_a]
- a.taint if taint
- a.freeze if frozen
- b = a.clone
-
- assert_equal(a, b)
- assert_not_equal(a.__id__, b.__id__)
- assert_equal(a.frozen?, b.frozen?)
- assert_equal(a.tainted?, b.tainted?)
- end
+ for frozen in [ false, true ]
+ a = @cls[*(0..99).to_a]
+ a.freeze if frozen
+ b = a.clone
+
+ assert_equal(a, b)
+ assert_not_equal(a.__id__, b.__id__)
+ assert_equal(a.frozen?, b.frozen?)
end
end
@@ -530,12 +586,11 @@ class TestArray < Test::Unit::TestCase
assert_equal([], @cls[].collect { 99 })
- # Ruby 1.9 feature change:
- # Enumerable#collect without block returns an Enumerator.
- #assert_equal([1, 2, 3], @cls[1, 2, 3].collect)
assert_kind_of Enumerator, @cls[1, 2, 3].collect
- assert_equal([[1, 2, 3]], [[1, 2, 3]].collect(&->(a, b, c) {[a, b, c]}))
+ assert_raise(ArgumentError) {
+ assert_equal([[1, 2, 3]], [[1, 2, 3]].collect(&->(a, b, c) {[a, b, c]}))
+ }
end
# also update map!
@@ -602,8 +657,17 @@ class TestArray < Test::Unit::TestCase
b.concat(b, b)
assert_equal([4, 5, 4, 5, 4, 5], b)
- assert_raise(TypeError) { [0].concat(:foo) }
- assert_raise(FrozenError) { [0].freeze.concat(:foo) }
+ assert_raise(TypeError) { @cls[0].concat(:foo) }
+ assert_raise(FrozenError) { @cls[0].freeze.concat(:foo) }
+
+ a = @cls[nil]
+ def (x = Object.new).to_ary
+ ary = Array.new(2)
+ ary << [] << [] << :ok
+ end
+ EnvUtil.under_gc_stress {a.concat(x)}
+ GC.start
+ assert_equal(:ok, a.last)
end
def test_count
@@ -611,7 +675,7 @@ class TestArray < Test::Unit::TestCase
assert_equal(5, a.count)
assert_equal(2, a.count(1))
assert_equal(3, a.count {|x| x % 2 == 1 })
- assert_equal(2, a.count(1) {|x| x % 2 == 1 })
+ assert_equal(2, assert_warning(/given block not used/) {a.count(1) {|x| x % 2 == 1 }})
assert_raise(ArgumentError) { a.count(0, 1) }
bug8654 = '[ruby-core:56072]'
@@ -704,21 +768,26 @@ class TestArray < Test::Unit::TestCase
a = @cls[ 5, 6, 7, 8, 9, 10 ]
assert_equal(9, a.delete_if {|i| break i if i > 8; i < 7})
assert_equal(@cls[7, 8, 9, 10], a, bug2545)
+
+ assert_raise(FrozenError) do
+ a = @cls[1, 2, 3, 42]
+ a.delete_if do
+ a.freeze
+ true
+ end
+ end
+ assert_equal(@cls[1, 2, 3, 42], a)
end
def test_dup
- for taint in [ false, true ]
- for frozen in [ false, true ]
- a = @cls[*(0..99).to_a]
- a.taint if taint
- a.freeze if frozen
- b = a.dup
-
- assert_equal(a, b)
- assert_not_equal(a.__id__, b.__id__)
- assert_equal(false, b.frozen?)
- assert_equal(a.tainted?, b.tainted?)
- end
+ for frozen in [ false, true ]
+ a = @cls[*(0..99).to_a]
+ a.freeze if frozen
+ b = a.dup
+
+ assert_equal(a, b)
+ assert_not_equal(a.__id__, b.__id__)
+ assert_equal(false, b.frozen?)
end
end
@@ -734,7 +803,7 @@ class TestArray < Test::Unit::TestCase
a = @cls[]
i = 0
a.each { |e|
- assert_equal(a[i], e)
+ assert(false, "Never get here")
i += 1
}
assert_equal(0, i)
@@ -754,7 +823,7 @@ class TestArray < Test::Unit::TestCase
a = @cls[]
i = 0
a.each_index { |ind|
- assert_equal(i, ind)
+ assert(false, "Never get here")
i += 1
}
assert_equal(0, i)
@@ -803,14 +872,14 @@ class TestArray < Test::Unit::TestCase
a2 = @cls[ 5, 6 ]
a3 = @cls[ 4, a2 ]
a4 = @cls[ a1, a3 ]
- assert_equal(@cls[1, 2, 3, 4, 5, 6], a4.flatten)
- assert_equal(@cls[ a1, a3], a4)
+ assert_equal_instance([1, 2, 3, 4, 5, 6], a4.flatten)
+ assert_equal_instance(@cls[ a1, a3], a4)
a5 = @cls[ a1, @cls[], a3 ]
- assert_equal(@cls[1, 2, 3, 4, 5, 6], a5.flatten)
- assert_equal(@cls[1, 2, 3, 4, [5, 6]], a5.flatten(1))
- assert_equal(@cls[], @cls[].flatten)
- assert_equal(@cls[],
+ assert_equal_instance([1, 2, 3, 4, 5, 6], a5.flatten)
+ assert_equal_instance([1, 2, 3, 4, [5, 6]], a5.flatten(1))
+ assert_equal_instance([], @cls[].flatten)
+ assert_equal_instance([],
@cls[@cls[@cls[@cls[],@cls[]],@cls[@cls[]],@cls[]],@cls[@cls[@cls[]]]].flatten)
end
@@ -818,13 +887,6 @@ class TestArray < Test::Unit::TestCase
assert_raise(TypeError, "[ruby-dev:31197]") { [[]].flatten("") }
end
- def test_flatten_taint
- a6 = @cls[[1, 2], 3]
- a6.taint
- a7 = a6.flatten
- assert_equal(true, a7.tainted?)
- end
-
def test_flatten_level0
a8 = @cls[[1, 2], 3]
a9 = a8.flatten(0)
@@ -854,6 +916,17 @@ class TestArray < Test::Unit::TestCase
assert_raise(NoMethodError, bug12738) { a.flatten.m }
end
+ def test_flatten_recursive
+ a = []
+ a << a
+ assert_raise(ArgumentError) { a.flatten }
+ b = [1]; c = [2, b]; b << c
+ assert_raise(ArgumentError) { b.flatten }
+
+ assert_equal([1, 2, b], b.flatten(1))
+ assert_equal([1, 2, 1, 2, 1, c], b.flatten(4))
+ end
+
def test_flatten!
a1 = @cls[ 1, 2, 3]
a2 = @cls[ 5, 6 ]
@@ -1041,6 +1114,46 @@ class TestArray < Test::Unit::TestCase
assert_not_include(a, [1,2])
end
+ def test_monkey_patch_include?
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30)
+ begin;
+ $-w = false
+ class Array
+ alias :old_include? :include?
+ def include? x
+ return true if x == :always
+ old_include?(x)
+ end
+ end
+ def test
+ a, c, always = :a, :c, :always
+ [
+ [:a, :b].include?(a),
+ [:a, :b].include?(c),
+ [:a, :b].include?(always),
+ ]
+ end
+ v = test
+ class Array
+ alias :include? :old_include?
+ end
+ assert_equal [true, false, true], v
+ end;
+ end
+
+ def test_intersect?
+ a = @cls[ 1, 2, 3]
+ assert_send([a, :intersect?, [3]])
+ assert_not_send([a, :intersect?, [4]])
+ assert_not_send([a, :intersect?, []])
+ end
+
+ def test_intersect_big_array
+ assert_send([@cls[ 1, 4, 5 ]*64, :intersect?, @cls[ 1, 2, 3 ]*64])
+ assert_not_send([@cls[ 1, 2, 3 ]*64, :intersect?, @cls[ 4, 5, 6 ]*64])
+ assert_not_send([@cls[], :intersect?, @cls[ 1, 2, 3 ]*64])
+ end
+
def test_index
a = @cls[ 'cat', 99, /a/, 99, @cls[ 1, 2, 3] ]
assert_equal(0, a.index('cat'))
@@ -1049,7 +1162,7 @@ class TestArray < Test::Unit::TestCase
assert_nil(a.index('ca'))
assert_nil(a.index([1,2]))
- assert_equal(1, a.index(99) {|x| x == 'cat' })
+ assert_equal(1, assert_warn(/given block not used/) {a.index(99) {|x| x == 'cat' }})
end
def test_values_at
@@ -1060,56 +1173,42 @@ class TestArray < Test::Unit::TestCase
end
def test_join
- $, = ""
+ assert_deprecated_warning {$, = ""}
a = @cls[]
- assert_equal("", a.join)
+ assert_equal("", assert_deprecated_warn(/non-nil value/) {a.join})
assert_equal("", a.join(','))
- assert_equal(Encoding::US_ASCII, a.join.encoding)
+ assert_equal(Encoding::US_ASCII, assert_deprecated_warn(/non-nil value/) {a.join}.encoding)
- $, = ""
+ assert_deprecated_warning {$, = ""}
a = @cls[1, 2]
- assert_equal("12", a.join)
- assert_equal("12", a.join(nil))
+ assert_equal("12", assert_deprecated_warn(/non-nil value/) {a.join})
+ assert_equal("12", assert_deprecated_warn(/non-nil value/) {a.join(nil)})
assert_equal("1,2", a.join(','))
- $, = ""
+ assert_deprecated_warning {$, = ""}
a = @cls[1, 2, 3]
- assert_equal("123", a.join)
- assert_equal("123", a.join(nil))
+ assert_equal("123", assert_deprecated_warn(/non-nil value/) {a.join})
+ assert_equal("123", assert_deprecated_warn(/non-nil value/) {a.join(nil)})
assert_equal("1,2,3", a.join(','))
- $, = ":"
+ assert_deprecated_warning {$, = ":"}
a = @cls[1, 2, 3]
- assert_equal("1:2:3", a.join)
- assert_equal("1:2:3", a.join(nil))
+ assert_equal("1:2:3", assert_deprecated_warn(/non-nil value/) {a.join})
+ assert_equal("1:2:3", assert_deprecated_warn(/non-nil value/) {a.join(nil)})
assert_equal("1,2,3", a.join(','))
- $, = ""
- a = @cls[1, 2, 3]
- a.taint
- s = a.join
- assert_equal(true, s.tainted?)
-
- bug5902 = '[ruby-core:42161]'
- sep = ":".taint
-
- s = @cls[].join(sep)
- assert_equal(false, s.tainted?, bug5902)
- s = @cls[1].join(sep)
- assert_equal(false, s.tainted?, bug5902)
- s = @cls[1, 2].join(sep)
- assert_equal(true, s.tainted?, bug5902)
+ assert_deprecated_warning {$, = ""}
e = ''.force_encoding('EUC-JP')
u = ''.force_encoding('UTF-8')
- assert_equal(Encoding::US_ASCII, [[]].join.encoding)
- assert_equal(Encoding::US_ASCII, [1, [u]].join.encoding)
- assert_equal(Encoding::UTF_8, [u, [e]].join.encoding)
- assert_equal(Encoding::UTF_8, [u, [1]].join.encoding)
- assert_equal(Encoding::UTF_8, [Struct.new(:to_str).new(u)].join.encoding)
+ assert_equal(Encoding::US_ASCII, assert_deprecated_warn(/non-nil value/) {[[]].join}.encoding)
+ assert_equal(Encoding::US_ASCII, assert_deprecated_warn(/non-nil value/) {[1, [u]].join}.encoding)
+ assert_equal(Encoding::UTF_8, assert_deprecated_warn(/non-nil value/) {[u, [e]].join}.encoding)
+ assert_equal(Encoding::UTF_8, assert_deprecated_warn(/non-nil value/) {[u, [1]].join}.encoding)
+ assert_equal(Encoding::UTF_8, assert_deprecated_warn(/non-nil value/) {[Struct.new(:to_str).new(u)].join}.encoding)
bug5379 = '[ruby-core:39776]'
- assert_equal(Encoding::US_ASCII, [[], u, nil].join.encoding, bug5379)
- assert_equal(Encoding::UTF_8, [[], "\u3042", nil].join.encoding, bug5379)
+ assert_equal(Encoding::US_ASCII, assert_deprecated_warn(/non-nil value/) {[[], u, nil].join}.encoding, bug5379)
+ assert_equal(Encoding::UTF_8, assert_deprecated_warn(/non-nil value/) {[[], "\u3042", nil].join}.encoding, bug5379)
ensure
$, = nil
end
@@ -1143,6 +1242,17 @@ class TestArray < Test::Unit::TestCase
assert_equal(@cls[], a)
end
+ def test_pack_format_mutation
+ ary = [Object.new]
+ fmt = "c" * 0x20000
+ class << ary[0]; self end.send(:define_method, :to_int) {
+ fmt.replace ""
+ 1
+ }
+ e = assert_raise(RuntimeError) { ary.pack(fmt) }
+ assert_equal "format string modified", e.message
+ end
+
def test_pack
a = @cls[*%w( cat wombat x yy)]
assert_equal("catwomx yy ", a.pack("A3A3A3A3"))
@@ -1199,32 +1309,13 @@ 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
+ n = [ 65, 66, 67 ]
+ str = "a" * 100
+ assert_equal("aaaABC", n.pack("@3ccc", buffer: str.dup), "[Bug #19116]")
end
def test_pop
@@ -1245,13 +1336,32 @@ 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))
assert_equal(@cls[1, 2, 3, 4, 5, nil], a.push(nil))
- # Ruby 1.8 feature:
- # Array#push accepts any number of arguments.
- #assert_raise(ArgumentError, "a.push()") { a.push() }
a.push
assert_equal @cls[1, 2, 3, 4, 5, nil], a
a.push 6, 7
@@ -1259,13 +1369,17 @@ class TestArray < Test::Unit::TestCase
end
def test_rassoc
+ def (a4 = Object.new).to_ary
+ %w( pork porcine )
+ end
a1 = @cls[*%w( cat feline )]
a2 = @cls[*%w( dog canine )]
a3 = @cls[*%w( mule asinine )]
- a = @cls[ a1, a2, a3 ]
+ a = @cls[ a1, a2, a3, a4 ]
assert_equal(a1, a.rassoc('feline'))
assert_equal(a3, a.rassoc('asinine'))
+ assert_equal(%w( pork porcine ), a.rassoc("porcine"))
assert_equal(nil, a.rassoc('dog'))
assert_equal(nil, a.rassoc('mule'))
assert_equal(nil, a.rassoc(1..2))
@@ -1289,6 +1403,15 @@ class TestArray < Test::Unit::TestCase
a = @cls[ 5, 6, 7, 8, 9, 10 ]
assert_equal(9, a.reject! {|i| break i if i > 8; i < 7})
assert_equal(@cls[7, 8, 9, 10], a, bug2545)
+
+ assert_raise(FrozenError) do
+ a = @cls[1, 2, 3, 42]
+ a.reject! do
+ a.freeze
+ true
+ end
+ end
+ assert_equal(@cls[1, 2, 3, 42], a)
end
def test_shared_array_reject!
@@ -1366,6 +1489,14 @@ class TestArray < Test::Unit::TestCase
assert_raise(FrozenError) { fa.replace(42) }
end
+ def test_replace_wb_variable_width_alloc
+ small_embed = []
+ 4.times { GC.start } # age small_embed
+ large_embed = [1, 2, 3, 4, 5, Array.new] # new young object
+ small_embed.replace(large_embed) # adds old to young reference
+ GC.verify_internal_consistency
+ end
+
def test_reverse
a = @cls[*%w( dog cat bee ant )]
assert_equal(@cls[*%w(ant bee cat dog)], a.reverse)
@@ -1377,9 +1508,6 @@ class TestArray < Test::Unit::TestCase
a = @cls[*%w( dog cat bee ant )]
assert_equal(@cls[*%w(ant bee cat dog)], a.reverse!)
assert_equal(@cls[*%w(ant bee cat dog)], a)
- # Ruby 1.8 feature change:
- # Array#reverse always returns self.
- #assert_nil(@cls[].reverse!)
assert_equal @cls[], @cls[].reverse!
end
@@ -1409,7 +1537,17 @@ class TestArray < Test::Unit::TestCase
assert_nil(a.rindex('ca'))
assert_nil(a.rindex([1,2]))
- assert_equal(3, a.rindex(99) {|x| x == [1,2,3] })
+ assert_equal(3, assert_warning(/given block not used/) {a.rindex(99) {|x| x == [1,2,3] }})
+
+ bug15951 = "[Bug #15951]"
+ o2 = Object.new
+ def o2.==(other)
+ other.replace([]) if Array === other
+ false
+ end
+ a = Array.new(10)
+ a.fill(o2)
+ assert_nil(a.rindex(a), bug15951)
end
def test_shift
@@ -1439,35 +1577,173 @@ class TestArray < Test::Unit::TestCase
assert_equal(1, a.slice(-100))
assert_nil(a.slice(-101))
- assert_equal(@cls[1], a.slice(0,1))
- assert_equal(@cls[100], a.slice(99,1))
- assert_equal(@cls[], a.slice(100,1))
- assert_equal(@cls[100], a.slice(99,100))
- assert_equal(@cls[100], a.slice(-1,1))
- assert_equal(@cls[99], a.slice(-2,1))
+ assert_equal_instance([1], a.slice(0,1))
+ assert_equal_instance([100], a.slice(99,1))
+ assert_equal_instance([], a.slice(100,1))
+ assert_equal_instance([100], a.slice(99,100))
+ assert_equal_instance([100], a.slice(-1,1))
+ assert_equal_instance([99], a.slice(-2,1))
- assert_equal(@cls[10, 11, 12], a.slice(9, 3))
- assert_equal(@cls[10, 11, 12], a.slice(-91, 3))
+ assert_equal_instance([10, 11, 12], a.slice(9, 3))
+ assert_equal_instance([10, 11, 12], a.slice(-91, 3))
assert_nil(a.slice(-101, 2))
- assert_equal(@cls[1], a.slice(0..0))
- assert_equal(@cls[100], a.slice(99..99))
- assert_equal(@cls[], a.slice(100..100))
- assert_equal(@cls[100], a.slice(99..200))
- assert_equal(@cls[100], a.slice(-1..-1))
- assert_equal(@cls[99], a.slice(-2..-2))
+ assert_equal_instance([1], a.slice(0..0))
+ assert_equal_instance([100], a.slice(99..99))
+ assert_equal_instance([], a.slice(100..100))
+ assert_equal_instance([100], a.slice(99..200))
+ assert_equal_instance([100], a.slice(-1..-1))
+ assert_equal_instance([99], a.slice(-2..-2))
+
+ assert_equal_instance([10, 11, 12], a.slice(9..11))
+ assert_equal_instance([98, 99, 100], a.slice(97..))
+ assert_equal_instance([10, 11, 12], a.slice(-91..-89))
+ assert_equal_instance([10, 11, 12], a.slice(-91..-89))
+
+ assert_equal_instance([5, 8, 11], a.slice((4..12)%3))
+ assert_equal_instance([95, 97, 99], a.slice((94..)%2))
+
+ # [0] [1] [2] [3] [4] [5] [6] [7]
+ # ary = [ 1 2 3 4 5 6 7 8 ... ]
+ # (0) (1) (2) <- (..7) % 3
+ # (2) (1) (0) <- (7..) % -3
+ assert_equal_instance([1, 4, 7], a.slice((..7)%3))
+ assert_equal_instance([8, 5, 2], a.slice((7..)% -3))
+
+ # [-98] [-97] [-96] [-95] [-94] [-93] [-92] [-91] [-90]
+ # ary = [ ... 3 4 5 6 7 8 9 10 11 ... ]
+ # (0) (1) (2) <- (-98..-90) % 3
+ # (2) (1) (0) <- (-90..-98) % -3
+ assert_equal_instance([3, 6, 9], a.slice((-98..-90)%3))
+ assert_equal_instance([11, 8, 5], a.slice((-90..-98)% -3))
+
+ # [ 48] [ 49] [ 50] [ 51] [ 52] [ 53]
+ # [-52] [-51] [-50] [-49] [-48] [-47]
+ # ary = [ ... 49 50 51 52 53 54 ... ]
+ # (0) (1) (2) <- (48..-47) % 2
+ # (2) (1) (0) <- (-47..48) % -2
+ assert_equal_instance([49, 51, 53], a.slice((48..-47)%2))
+ assert_equal_instance([54, 52, 50], a.slice((-47..48)% -2))
+
+ idx = ((3..90) % 2).to_a
+ assert_equal_instance(a.values_at(*idx), a.slice((3..90)%2))
+ idx = 90.step(3, -2).to_a
+ assert_equal_instance(a.values_at(*idx), a.slice((90 .. 3)% -2))
- assert_equal(@cls[10, 11, 12], a.slice(9..11))
- assert_equal(@cls[10, 11, 12], a.slice(-91..-89))
+ a = [0, 1, 2, 3, 4, 5]
+ assert_equal([2, 1, 0], a.slice((2..).step(-1)))
+ assert_equal([2, 0], a.slice((2..).step(-2)))
+ assert_equal([2], a.slice((2..).step(-3)))
+ assert_equal([2], a.slice((2..).step(-4)))
+
+ assert_equal([3, 2, 1, 0], a.slice((-3..).step(-1)))
+ assert_equal([3, 1], a.slice((-3..).step(-2)))
+ assert_equal([3, 0], a.slice((-3..).step(-3)))
+ assert_equal([3], a.slice((-3..).step(-4)))
+ assert_equal([3], a.slice((-3..).step(-5)))
+
+ assert_equal([5, 4, 3, 2, 1, 0], a.slice((..0).step(-1)))
+ assert_equal([5, 3, 1], a.slice((..0).step(-2)))
+ assert_equal([5, 2], a.slice((..0).step(-3)))
+ assert_equal([5, 1], a.slice((..0).step(-4)))
+ assert_equal([5, 0], a.slice((..0).step(-5)))
+ assert_equal([5], a.slice((..0).step(-6)))
+ assert_equal([5], a.slice((..0).step(-7)))
+
+ assert_equal([5, 4, 3, 2, 1], a.slice((...0).step(-1)))
+ assert_equal([5, 3, 1], a.slice((...0).step(-2)))
+ assert_equal([5, 2], a.slice((...0).step(-3)))
+ assert_equal([5, 1], a.slice((...0).step(-4)))
+ assert_equal([5], a.slice((...0).step(-5)))
+ assert_equal([5], a.slice((...0).step(-6)))
+
+ assert_equal([5, 4, 3, 2], a.slice((...1).step(-1)))
+ assert_equal([5, 3], a.slice((...1).step(-2)))
+ assert_equal([5, 2], a.slice((...1).step(-3)))
+ assert_equal([5], a.slice((...1).step(-4)))
+ assert_equal([5], a.slice((...1).step(-5)))
+
+ assert_equal([5, 4, 3, 2, 1], a.slice((..-5).step(-1)))
+ assert_equal([5, 3, 1], a.slice((..-5).step(-2)))
+ assert_equal([5, 2], a.slice((..-5).step(-3)))
+ assert_equal([5, 1], a.slice((..-5).step(-4)))
+ assert_equal([5], a.slice((..-5).step(-5)))
+ assert_equal([5], a.slice((..-5).step(-6)))
+
+ assert_equal([5, 4, 3, 2], a.slice((...-5).step(-1)))
+ assert_equal([5, 3], a.slice((...-5).step(-2)))
+ assert_equal([5, 2], a.slice((...-5).step(-3)))
+ assert_equal([5], a.slice((...-5).step(-4)))
+ assert_equal([5], a.slice((...-5).step(-5)))
+
+ assert_equal([4, 3, 2, 1], a.slice((4..1).step(-1)))
+ assert_equal([4, 2], a.slice((4..1).step(-2)))
+ assert_equal([4, 1], a.slice((4..1).step(-3)))
+ assert_equal([4], a.slice((4..1).step(-4)))
+ assert_equal([4], a.slice((4..1).step(-5)))
+
+ assert_equal([4, 3, 2], a.slice((4...1).step(-1)))
+ assert_equal([4, 2], a.slice((4...1).step(-2)))
+ assert_equal([4], a.slice((4...1).step(-3)))
+ assert_equal([4], a.slice((4...1).step(-4)))
+
+ assert_equal([4, 3, 2, 1], a.slice((-2..1).step(-1)))
+ assert_equal([4, 2], a.slice((-2..1).step(-2)))
+ assert_equal([4, 1], a.slice((-2..1).step(-3)))
+ assert_equal([4], a.slice((-2..1).step(-4)))
+ assert_equal([4], a.slice((-2..1).step(-5)))
+
+ assert_equal([4, 3, 2], a.slice((-2...1).step(-1)))
+ assert_equal([4, 2], a.slice((-2...1).step(-2)))
+ assert_equal([4], a.slice((-2...1).step(-3)))
+ assert_equal([4], a.slice((-2...1).step(-4)))
+
+ assert_equal([4, 3, 2, 1], a.slice((4..-5).step(-1)))
+ assert_equal([4, 2], a.slice((4..-5).step(-2)))
+ assert_equal([4, 1], a.slice((4..-5).step(-3)))
+ assert_equal([4], a.slice((4..-5).step(-4)))
+ assert_equal([4], a.slice((4..-5).step(-5)))
+
+ assert_equal([4, 3, 2], a.slice((4...-5).step(-1)))
+ assert_equal([4, 2], a.slice((4...-5).step(-2)))
+ assert_equal([4], a.slice((4...-5).step(-3)))
+ assert_equal([4], a.slice((4...-5).step(-4)))
+
+ assert_equal([4, 3, 2, 1], a.slice((-2..-5).step(-1)))
+ assert_equal([4, 2], a.slice((-2..-5).step(-2)))
+ assert_equal([4, 1], a.slice((-2..-5).step(-3)))
+ assert_equal([4], a.slice((-2..-5).step(-4)))
+ assert_equal([4], a.slice((-2..-5).step(-5)))
+
+ assert_equal([4, 3, 2], a.slice((-2...-5).step(-1)))
+ assert_equal([4, 2], a.slice((-2...-5).step(-2)))
+ assert_equal([4], a.slice((-2...-5).step(-3)))
+ assert_equal([4], a.slice((-2...-5).step(-4)))
+ end
+
+ def test_slice_out_of_range
+ a = @cls[*(1..100).to_a]
assert_nil(a.slice(-101..-1))
+ assert_nil(a.slice(-101..))
+
+ assert_raise_with_message(RangeError, "((-101..-1).%(2)) out of range") { a.slice((-101..-1)%2) }
+ assert_raise_with_message(RangeError, "((-101..).%(2)) out of range") { a.slice((-101..)%2) }
assert_nil(a.slice(10, -3))
- # Ruby 1.8 feature change:
- # Array#slice[size..x] always returns [].
- #assert_nil(a.slice(10..7))
assert_equal @cls[], a.slice(10..7)
+
+ assert_equal([100], a.slice(-1, 1_000_000_000))
+ end
+
+ def test_slice_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress { assert_equal([1, 2, 3, 4, 5], (0..10).to_a[1, 5]) }
+ EnvUtil.under_gc_compact_stress do
+ a = [0, 1, 2, 3, 4, 5]
+ assert_equal([2, 1, 0], a.slice((2..).step(-1)))
+ end
end
def test_slice!
@@ -1480,15 +1756,18 @@ class TestArray < Test::Unit::TestCase
assert_equal(@cls[1, 2, 3, 5], a)
a = @cls[1, 2, 3, 4, 5]
- assert_equal(@cls[3,4], a.slice!(2,2))
+ s = a.slice!(2,2)
+ assert_equal_instance([3,4], s)
assert_equal(@cls[1, 2, 5], a)
a = @cls[1, 2, 3, 4, 5]
- assert_equal(@cls[4,5], a.slice!(-2,2))
+ s = a.slice!(-2,2)
+ assert_equal_instance([4,5], s)
assert_equal(@cls[1, 2, 3], a)
a = @cls[1, 2, 3, 4, 5]
- assert_equal(@cls[3,4], a.slice!(2..3))
+ s = a.slice!(2..3)
+ assert_equal_instance([3,4], s)
assert_equal(@cls[1, 2, 5], a)
a = @cls[1, 2, 3, 4, 5]
@@ -1507,10 +1786,27 @@ class TestArray < Test::Unit::TestCase
assert_equal(nil, a.slice!(-6,2))
assert_equal(@cls[1, 2, 3, 4, 5], a)
+ assert_equal("[2, 3]", [1,2,3].slice!(1,10000).inspect, "moved from btest/knownbug")
+
assert_raise(ArgumentError) { @cls[1].slice! }
assert_raise(ArgumentError) { @cls[1].slice!(0, 0, 0) }
end
+ def test_slice_out_of_range!
+ a = @cls[*(1..100).to_a]
+
+ assert_nil(a.clone.slice!(-101..-1))
+ assert_nil(a.clone.slice!(-101..))
+
+ # assert_raise_with_message(RangeError, "((-101..-1).%(2)) out of range") { a.clone.slice!((-101..-1)%2) }
+ # assert_raise_with_message(RangeError, "((-101..).%(2)) out of range") { a.clone.slice!((-101..)%2) }
+
+ assert_nil(a.clone.slice!(10, -3))
+ assert_equal @cls[], a.clone.slice!(10..7)
+
+ assert_equal([100], a.clone.slice!(-1, 1_000_000_000))
+ end
+
def test_sort
a = @cls[ 4, 1, 2, 3 ]
assert_equal(@cls[1, 2, 3, 4], a.sort)
@@ -1550,6 +1846,37 @@ class TestArray < Test::Unit::TestCase
assert_equal([1, 2, 3, 4], a)
end
+ def test_freeze_inside_sort!
+ array = [1, 2, 3, 4, 5]
+ frozen_array = nil
+ assert_raise(FrozenError) do
+ count = 0
+ array.sort! do |a, b|
+ array.freeze if (count += 1) == 6
+ frozen_array ||= array.map.to_a if array.frozen?
+ b <=> a
+ end
+ end
+ assert_equal(frozen_array, array)
+
+ object = Object.new
+ array = [1, 2, 3, 4, 5]
+ object.define_singleton_method(:>){|_| array.freeze; true}
+ assert_raise(FrozenError) do
+ array.sort! do |a, b|
+ object
+ end
+ end
+
+ object = Object.new
+ array = [object, object]
+ object.define_singleton_method(:>){|_| array.freeze; true}
+ object.define_singleton_method(:<=>){|o| object}
+ assert_raise(FrozenError) do
+ array.sort!
+ end
+ end
+
def test_sort_with_callcc
need_continuation
n = 1000
@@ -1570,13 +1897,21 @@ class TestArray < Test::Unit::TestCase
end
def test_sort_with_replace
- xary = (1..100).to_a
- 100.times do
- ary = (1..100).to_a
- ary.sort! {|a,b| ary.replace(xary); a <=> b}
- GC.start
- assert_equal(xary, ary, '[ruby-dev:34732]')
- end
+ bug = '[ruby-core:34732]'
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30)
+ bug = "#{bug}"
+ begin;
+ xary = (1..100).to_a
+ 100.times do
+ ary = (1..100).to_a
+ ary.sort! {|a,b| ary.replace(xary); a <=> b}
+ GC.start
+ assert_equal(xary, ary, '[ruby-dev:34732]')
+ end
+ assert_nothing_raised(SystemStackError, bug) do
+ assert_equal(:ok, Array.new(100_000, nil).permutation {break :ok})
+ end
+ end;
end
def test_sort_bang_with_freeze
@@ -1605,6 +1940,13 @@ class TestArray < Test::Unit::TestCase
TEST
end
+ def test_sort_uncomparable
+ assert_raise(ArgumentError) {[1, Float::NAN].sort}
+ assert_raise(ArgumentError) {[1.0, Float::NAN].sort}
+ assert_raise(ArgumentError) {[Float::NAN, 1].sort}
+ assert_raise(ArgumentError) {[Float::NAN, 1.0].sort}
+ end
+
def test_to_a
a = @cls[ 1, 2, 3 ]
a_id = a.__id__
@@ -1632,38 +1974,40 @@ class TestArray < Test::Unit::TestCase
def o.to_ary
foo_bar()
end
- assert_match(/foo_bar/, assert_raise(NoMethodError) {a.concat(o)}.message)
+ assert_raise_with_message(NoMethodError, /foo_bar/) {a.concat(o)}
end
def test_to_s
- $, = ""
+ assert_deprecated_warning {$, = ""}
a = @cls[]
assert_equal("[]", a.to_s)
- $, = ""
+ assert_deprecated_warning {$, = ""}
a = @cls[1, 2]
assert_equal("[1, 2]", a.to_s)
- $, = ""
+ assert_deprecated_warning {$, = ""}
a = @cls[1, 2, 3]
assert_equal("[1, 2, 3]", a.to_s)
- $, = ":"
+ assert_deprecated_warning {$, = ""}
a = @cls[1, 2, 3]
assert_equal("[1, 2, 3]", a.to_s)
ensure
$, = nil
end
+ StubToH = [
+ [:key, :value],
+ Object.new.tap do |kvp|
+ def kvp.to_ary
+ [:obtained, :via_to_ary]
+ end
+ end,
+ ]
+
def test_to_h
- kvp = Object.new
- def kvp.to_ary
- [:obtained, :via_to_ary]
- end
- array = [
- [:key, :value],
- kvp,
- ]
+ array = StubToH
assert_equal({key: :value, obtained: :via_to_ary}, array.to_h)
e = assert_raise(TypeError) {
@@ -1678,11 +2022,34 @@ class TestArray < Test::Unit::TestCase
assert_equal "wrong array length at 2 (expected 2, was 1)", e.message
end
+ def test_to_h_block
+ array = StubToH
+ assert_equal({"key" => "value", "obtained" => "via_to_ary"},
+ array.to_h {|k, v| [k.to_s, v.to_s]})
+
+ assert_equal({first_one: :ok, not_ok: :ng},
+ [[:first_one, :ok], :not_ok].to_h {|k, v| [k, v || :ng]})
+
+ e = assert_raise(TypeError) {
+ [[:first_one, :ok], :not_ok].to_h {|k, v| v ? [k, v] : k}
+ }
+ assert_equal "wrong element type Symbol at 1 (expected array)", e.message
+ array = [1]
+ k = eval("class C\u{1f5ff}; self; end").new
+ assert_raise_with_message(TypeError, /C\u{1f5ff}/) {array.to_h {k}}
+ e = assert_raise(ArgumentError) {
+ [[:first_one, :ok], [1, 2], [:not_ok]].to_h {|kv| kv}
+ }
+ assert_equal "wrong array length at 2 (expected 2, was 1)", e.message
+ end
+
def test_min
+ assert_equal(3, [3].min)
assert_equal(1, [1, 2, 3, 1, 2].min)
assert_equal(3, [1, 2, 3, 1, 2].min {|a,b| b <=> a })
cond = ->((a, ia), (b, ib)) { (b <=> a).nonzero? or ia <=> ib }
assert_equal([3, 2], [1, 2, 3, 1, 2].each_with_index.min(&cond))
+ assert_equal(1.0, [3.0, 1.0, 2.0].min)
ary = %w(albatross dog horse)
assert_equal("albatross", ary.min)
assert_equal("dog", ary.min {|a,b| a.length <=> b.length })
@@ -1700,11 +2067,20 @@ class TestArray < Test::Unit::TestCase
assert_same(obj, [obj, 1.0].min)
end
+ def test_min_uncomparable
+ assert_raise(ArgumentError) {[1, Float::NAN].min}
+ assert_raise(ArgumentError) {[1.0, Float::NAN].min}
+ assert_raise(ArgumentError) {[Float::NAN, 1].min}
+ assert_raise(ArgumentError) {[Float::NAN, 1.0].min}
+ end
+
def test_max
+ assert_equal(1, [1].max)
assert_equal(3, [1, 2, 3, 1, 2].max)
assert_equal(1, [1, 2, 3, 1, 2].max {|a,b| b <=> a })
cond = ->((a, ia), (b, ib)) { (b <=> a).nonzero? or ia <=> ib }
assert_equal([1, 3], [1, 2, 3, 1, 2].each_with_index.max(&cond))
+ assert_equal(3.0, [1.0, 3.0, 2.0].max)
ary = %w(albatross dog horse)
assert_equal("horse", ary.max)
assert_equal("albatross", ary.max {|a,b| a.length <=> b.length })
@@ -1721,6 +2097,33 @@ class TestArray < Test::Unit::TestCase
assert_same(obj, [obj, 1.0].max)
end
+ def test_max_uncomparable
+ assert_raise(ArgumentError) {[1, Float::NAN].max}
+ assert_raise(ArgumentError) {[1.0, Float::NAN].max}
+ assert_raise(ArgumentError) {[Float::NAN, 1].max}
+ assert_raise(ArgumentError) {[Float::NAN, 1.0].max}
+ end
+
+ def test_minmax
+ assert_equal([3, 3], [3].minmax)
+ assert_equal([1, 3], [1, 2, 3, 1, 2].minmax)
+ assert_equal([3, 1], [1, 2, 3, 1, 2].minmax {|a,b| b <=> a })
+ cond = ->((a, ia), (b, ib)) { (b <=> a).nonzero? or ia <=> ib }
+ assert_equal([[3, 2], [1, 3]], [1, 2, 3, 1, 2].each_with_index.minmax(&cond))
+ ary = %w(albatross dog horse)
+ assert_equal(["albatross", "horse"], ary.minmax)
+ assert_equal(["dog", "albatross"], ary.minmax {|a,b| a.length <=> b.length })
+ assert_equal([1, 3], [3,2,1].minmax)
+
+ class << (obj = Object.new)
+ def <=>(x) 1 <=> x end
+ def coerce(x) [x, 1] end
+ end
+ ary = [obj, 1.0].minmax
+ assert_same(obj, ary[0])
+ assert_equal(obj, ary[1])
+ end
+
def test_uniq
a = []
b = a.uniq
@@ -1769,6 +2172,27 @@ class TestArray < Test::Unit::TestCase
ary = [bug9340, bug9340.dup, bug9340.dup]
assert_equal 1, ary.uniq.size
assert_same bug9340, ary.uniq[0]
+
+ sc = Class.new(@cls)
+ a = sc[]
+ b = a.dup
+ assert_equal_instance([], a.uniq)
+ assert_equal(b, a)
+
+ a = sc[1]
+ b = a.dup
+ assert_equal_instance([1], a.uniq)
+ assert_equal(b, a)
+
+ a = sc[1, 1]
+ b = a.dup
+ assert_equal_instance([1], a.uniq)
+ assert_equal(b, a)
+
+ a = sc[1, 1]
+ b = a.dup
+ assert_equal_instance([1], a.uniq{|x| x})
+ assert_equal(b, a)
end
def test_uniq_with_block
@@ -1965,6 +2389,44 @@ class TestArray < Test::Unit::TestCase
assert_equal((1..128).to_a, b)
end
+ def test_union
+ assert_equal(@cls[], @cls[].union(@cls[]))
+ assert_equal(@cls[1], @cls[1].union(@cls[]))
+ assert_equal(@cls[1], @cls[].union(@cls[1]))
+ assert_equal(@cls[1], @cls[].union(@cls[], @cls[1]))
+ assert_equal(@cls[1], @cls[1].union(@cls[1]))
+ assert_equal(@cls[1], @cls[1].union(@cls[1], @cls[1], @cls[1]))
+
+ assert_equal(@cls[1,2], @cls[1].union(@cls[2]))
+ assert_equal(@cls[1,2], @cls[1, 1].union(@cls[2, 2]))
+ assert_equal(@cls[1,2], @cls[1, 2].union(@cls[1, 2]))
+ assert_equal(@cls[1,2], @cls[1, 1].union(@cls[1, 1], @cls[1, 2], @cls[2, 1], @cls[2, 2, 2]))
+
+ a = %w(a b c)
+ b = %w(a b c d e)
+ c = a.union(b)
+ assert_equal(c, b)
+ assert_not_same(c, b)
+ assert_equal(%w(a b c), a)
+ assert_equal(%w(a b c d e), b)
+ assert(a.none?(&:frozen?))
+ assert(b.none?(&:frozen?))
+ assert(c.none?(&:frozen?))
+ end
+
+ def test_union_big_array
+ assert_equal(@cls[1,2], (@cls[1]*64).union(@cls[2]*64))
+ assert_equal(@cls[1,2,3], (@cls[1, 2]*64).union(@cls[1, 2]*64, @cls[3]*60))
+
+ a = (1..64).to_a
+ b = (1..128).to_a
+ c = a | b
+ assert_equal(c, b)
+ assert_not_same(c, b)
+ assert_equal((1..64).to_a, a)
+ assert_equal((1..128).to_a, b)
+ end
+
def test_combination
a = @cls[]
assert_equal(1, a.combination(0).size)
@@ -2144,23 +2606,23 @@ class TestArray < Test::Unit::TestCase
end
def test_take
- assert_equal([1,2,3], [1,2,3,4,5,0].take(3))
+ assert_equal_instance([1,2,3], @cls[1,2,3,4,5,0].take(3))
assert_raise(ArgumentError, '[ruby-dev:34123]') { [1,2].take(-1) }
- assert_equal([1,2], [1,2].take(1000000000), '[ruby-dev:34123]')
+ assert_equal_instance([1,2], @cls[1,2].take(1000000000), '[ruby-dev:34123]')
end
def test_take_while
- assert_equal([1,2], [1,2,3,4,5,0].take_while {|i| i < 3 })
+ assert_equal_instance([1,2], @cls[1,2,3,4,5,0].take_while {|i| i < 3 })
end
def test_drop
- assert_equal([4,5,0], [1,2,3,4,5,0].drop(3))
+ assert_equal_instance([4,5,0], @cls[1,2,3,4,5,0].drop(3))
assert_raise(ArgumentError, '[ruby-dev:34123]') { [1,2].drop(-1) }
- assert_equal([], [1,2].drop(1000000000), '[ruby-dev:34123]')
+ assert_equal_instance([], @cls[1,2].drop(1000000000), '[ruby-dev:34123]')
end
def test_drop_while
- assert_equal([3,4,5,0], [1,2,3,4,5,0].drop_while {|i| i < 3 })
+ assert_equal_instance([3,4,5,0], @cls[1,2,3,4,5,0].drop_while {|i| i < 3 })
end
LONGP = [127, 63, 31, 15, 7].map {|x| 2**x-1 }.find do |x|
@@ -2185,13 +2647,13 @@ class TestArray < Test::Unit::TestCase
def test_initialize
assert_nothing_raised { [].instance_eval { initialize } }
- assert_nothing_raised { Array.new { } }
+ assert_warning(/given block not used/) { Array.new { } }
assert_equal([1, 2, 3], Array.new([1, 2, 3]))
assert_raise(ArgumentError) { Array.new(-1, 1) }
assert_raise(ArgumentError) { Array.new(LONGP, 1) }
assert_equal([1, 1, 1], Array.new(3, 1))
assert_equal([1, 1, 1], Array.new(3) { 1 })
- assert_equal([1, 1, 1], Array.new(3, 1) { 1 })
+ assert_equal([1, 1, 1], assert_warning(/block supersedes default value argument/) {Array.new(3, 1) { 1 }})
end
def test_aset_error
@@ -2199,6 +2661,7 @@ class TestArray < Test::Unit::TestCase
assert_raise(IndexError) { [0][LONGP] = 2 }
assert_raise(IndexError) { [0][(LONGP + 1) / 2 - 1] = 2 }
assert_raise(IndexError) { [0][LONGP..-1] = 2 }
+ assert_raise(IndexError) { [0][LONGP..] = 2 }
a = [0]
a[2] = 4
assert_equal([0, nil, 4], a)
@@ -2206,6 +2669,9 @@ class TestArray < Test::Unit::TestCase
assert_raise(ArgumentError) { [0].freeze[0, 0, 0] = 0 }
assert_raise(TypeError) { [0][:foo] = 0 }
assert_raise(FrozenError) { [0].freeze[:foo] = 0 }
+
+ # [Bug #17271]
+ assert_raise_with_message(RangeError, "-7.. out of range") { [*0..5][-7..] = 1 }
end
def test_first2
@@ -2236,16 +2702,29 @@ class TestArray < Test::Unit::TestCase
def test_aref
assert_raise(ArgumentError) { [][0, 0, 0] }
+ assert_raise(ArgumentError) { @cls[][0, 0, 0] }
end
def test_fetch
- assert_equal(1, [].fetch(0, 0) { 1 })
+ assert_equal(1, assert_warning(/block supersedes default value argument/) {[].fetch(0, 0) { 1 }})
assert_equal(1, [0, 1].fetch(-1))
assert_raise(IndexError) { [0, 1].fetch(2) }
assert_raise(IndexError) { [0, 1].fetch(-3) }
assert_equal(2, [0, 1].fetch(2, 2))
end
+ def test_fetch_values
+ ary = @cls[1, 2, 3]
+ assert_equal([], ary.fetch_values())
+ assert_equal([1], ary.fetch_values(0))
+ assert_equal([3, 1, 3], ary.fetch_values(2, 0, -1))
+ assert_raise(TypeError) {ary.fetch_values("")}
+ assert_raise(IndexError) {ary.fetch_values(10)}
+ assert_raise(IndexError) {ary.fetch_values(-20)}
+ assert_equal(["10 not found"], ary.fetch_values(10) {|i| "#{i} not found"})
+ assert_equal(["10 not found", 3], ary.fetch_values(10, 2) {|i| "#{i} not found"})
+ end
+
def test_index2
a = [0, 1, 2]
assert_equal(a, a.index.to_a)
@@ -2308,6 +2787,27 @@ class TestArray < Test::Unit::TestCase
assert_equal("12345", [1,[2,[3,4],5]].join)
end
+ def test_join_recheck_elements_type
+ x = Struct.new(:ary).new
+ def x.to_str
+ ary[2] = [0, 1, 2]
+ "z"
+ end
+ (x.ary = ["a", "b", "c", x])
+ assert_equal("ab012z", x.ary.join(""))
+ end
+
+ def test_join_recheck_array_length
+ x = Struct.new(:ary).new
+ def x.to_str
+ ary.clear
+ ary[0] = "b"
+ "z"
+ end
+ x.ary = Array.new(1023) {"a"*1} << x
+ assert_equal("b", x.ary.join(""))
+ end
+
def test_to_a2
klass = Class.new(Array)
a = klass.new.to_a
@@ -2356,6 +2856,15 @@ class TestArray < Test::Unit::TestCase
a = @cls[ 1, 2, 3, 4, 5 ]
a.select! {|i| a.clear if i == 5; false }
assert_equal(0, a.size, bug13053)
+
+ assert_raise(FrozenError) do
+ a = @cls[1, 2, 3, 42]
+ a.select! do
+ a.freeze
+ false
+ end
+ end
+ assert_equal(@cls[1, 2, 3, 42], a)
end
# also select!
@@ -2371,6 +2880,34 @@ class TestArray < Test::Unit::TestCase
a = @cls[ 1, 2, 3, 4, 5 ]
assert_equal(a, a.keep_if { |i| i > 3 })
assert_equal(@cls[4, 5], a)
+
+ assert_raise(FrozenError) do
+ a = @cls[1, 2, 3, 42]
+ a.keep_if do
+ a.freeze
+ false
+ end
+ end
+ assert_equal(@cls[1, 2, 3, 42], a)
+ end
+
+ def test_filter
+ assert_equal([0, 2], [0, 1, 2, 3].filter {|x| x % 2 == 0 })
+ end
+
+ # alias for select!
+ def test_filter!
+ a = @cls[ 1, 2, 3, 4, 5 ]
+ assert_equal(nil, a.filter! { true })
+ assert_equal(@cls[1, 2, 3, 4, 5], a)
+
+ a = @cls[ 1, 2, 3, 4, 5 ]
+ assert_equal(a, a.filter! { false })
+ assert_equal(@cls[], a)
+
+ a = @cls[ 1, 2, 3, 4, 5 ]
+ assert_equal(a, a.filter! { |i| i > 3 })
+ assert_equal(@cls[4, 5], a)
end
def test_delete2
@@ -2417,13 +2954,24 @@ class TestArray < Test::Unit::TestCase
def test_zip_bug
bug8153 = "ruby-core:53650"
- r = 1..1
+ r = [1]
def r.respond_to?(*)
super
end
assert_equal [[42, 1]], [42].zip(r), bug8153
end
+ def test_zip_with_enumerator
+ bug17814 = "ruby-core:103513"
+
+ step = 0.step
+ e = Enumerator.produce { step.next }
+ a = %w(a b c)
+ assert_equal([["a", 0], ["b", 1], ["c", 2]], a.zip(e), bug17814)
+ assert_equal([["a", 3], ["b", 4], ["c", 5]], a.zip(e), bug17814)
+ assert_equal([["a", 6], ["b", 7], ["c", 8]], a.zip(e), bug17814)
+ end
+
def test_transpose
assert_equal([[1, :a], [2, :b], [3, :c]],
[[1, 2, 3], [:a, :b, :c]].transpose)
@@ -2450,25 +2998,21 @@ class TestArray < Test::Unit::TestCase
assert_not_equal([0, 1, 2], [0, 1, 3])
end
- A = Array.new(3, &:to_s)
- B = A.dup
-
def test_equal_resize
+ $test_equal_resize_a = Array.new(3, &:to_s)
+ $test_equal_resize_b = $test_equal_resize_a.dup
o = Object.new
def o.==(o)
- A.clear
- B.clear
+ $test_equal_resize_a.clear
+ $test_equal_resize_b.clear
true
end
- A[1] = o
- assert_equal(A, B)
+ $test_equal_resize_a[1] = o
+ assert_equal($test_equal_resize_a, $test_equal_resize_b)
end
def test_flatten_error
a = []
- a << a
- assert_raise(ArgumentError) { a.flatten }
-
f = [].freeze
assert_raise(ArgumentError) { a.flatten!(1, 2) }
assert_raise(TypeError) { a.flatten!(:foo) }
@@ -2497,40 +3041,35 @@ class TestArray < Test::Unit::TestCase
end
end
- def test_shuffle_random
- gen = proc do
- 10000000
- end
- class << gen
- alias rand call
- end
+ def test_shuffle_random_out_of_range
+ gen = random_generator {10000000}
assert_raise(RangeError) {
[*0..2].shuffle(random: gen)
}
+ gen = random_generator {-1}
+ assert_raise(RangeError) {
+ [*0..2].shuffle(random: gen)
+ }
+ end
+ def test_shuffle_random_clobbering
ary = (0...10000).to_a
- gen = proc do
+ gen = random_generator do
ary.replace([])
0.5
end
- class << gen
- alias rand call
- end
assert_raise(RuntimeError) {ary.shuffle!(random: gen)}
+ end
- zero = Object.new
- def zero.to_int
- 0
- end
- gen_to_int = proc do |max|
- zero
- end
- class << gen_to_int
- alias rand call
- end
+ def test_shuffle_random_zero
+ zero = Struct.new(:to_int).new(0)
+ gen_to_int = random_generator {|max| zero}
ary = (0...10000).to_a
assert_equal(ary.rotate, ary.shuffle(random: gen_to_int))
+ end
+ def test_shuffle_random_invalid_generator
+ ary = (0...10).to_a
assert_raise(NoMethodError) {
ary.shuffle(random: Object.new)
}
@@ -2547,7 +3086,9 @@ class TestArray < Test::Unit::TestCase
assert_include([0, 1, 2], sample)
}
end
+ end
+ def test_sample_statistics
srand(0)
a = (1..18).to_a
(0..20).each do |n|
@@ -2564,9 +3105,13 @@ class TestArray < Test::Unit::TestCase
end
assert_operator(h.values.min * 2, :>=, h.values.max) if n != 0
end
+ end
+ def test_sample_invalid_argument
assert_raise(ArgumentError, '[ruby-core:23374]') {[1, 2].sample(-1)}
+ end
+ def test_sample_random_srand0
gen = Random.new(0)
srand(0)
a = (1..18).to_a
@@ -2575,28 +3120,22 @@ class TestArray < Test::Unit::TestCase
assert_equal(a.sample(n), a.sample(n, random: gen), "#{i}/#{n}")
end
end
+ end
+ def test_sample_unknown_keyword
assert_raise_with_message(ArgumentError, /unknown keyword/) do
[0, 1, 2].sample(xawqij: "a")
end
end
- def test_sample_random
+ def test_sample_random_generator
ary = (0...10000).to_a
assert_raise(ArgumentError) {ary.sample(1, 2, random: nil)}
- gen0 = proc do |max|
- max/2
- end
- class << gen0
- alias rand call
- end
- gen1 = proc do |max|
+ gen0 = random_generator {|max| max/2}
+ gen1 = random_generator do |max|
ary.replace([])
max/2
end
- class << gen1
- alias rand call
- end
assert_equal(5000, ary.sample(random: gen0))
assert_nil(ary.sample(random: gen1))
assert_equal([], ary)
@@ -2624,20 +3163,28 @@ class TestArray < Test::Unit::TestCase
assert_equal([5000, 0, 5001, 2, 5002, 4, 5003, 6, 5004, 8, 5005], ary.sample(11, random: gen0))
ary.sample(11, random: gen1) # implementation detail, may change in the future
assert_equal([], ary)
+ end
- half = Object.new
- def half.to_int
- 5000
- end
- gen_to_int = proc do |max|
- half
- end
- class << gen_to_int
- alias rand call
- end
+ def test_sample_random_generator_half
+ half = Struct.new(:to_int).new(5000)
+ gen_to_int = random_generator {|max| half}
ary = (0...10000).to_a
assert_equal(5000, ary.sample(random: gen_to_int))
+ end
+ def test_sample_random_out_of_range
+ gen = random_generator {10000000}
+ assert_raise(RangeError) {
+ [*0..2].sample(random: gen)
+ }
+ gen = random_generator {-1}
+ assert_raise(RangeError) {
+ [*0..2].sample(random: gen)
+ }
+ end
+
+ def test_sample_random_invalid_generator
+ ary = (0..10).to_a
assert_raise(NoMethodError) {
ary.sample(random: Object.new)
}
@@ -2703,22 +3250,6 @@ class TestArray < Test::Unit::TestCase
end
end
- class Array2 < Array
- end
-
- def test_array_subclass
- assert_equal(Array2, Array2[1,2,3].uniq.class, "[ruby-dev:34581]")
- assert_equal(Array2, Array2[1,2][0,1].class) # embedded
- assert_equal(Array2, Array2[*(1..100)][1..99].class) #not embedded
- end
-
- def test_inspect
- a = @cls[1, 2, 3]
- a.taint
- s = a.inspect
- assert_equal(true, s.tainted?)
- end
-
def test_initialize2
a = [1] * 1000
a.instance_eval { initialize }
@@ -2853,6 +3384,8 @@ class TestArray < Test::Unit::TestCase
assert_equal(nil, a.bsearch {|x| 1 * (2**100) })
assert_equal(nil, a.bsearch {|x| (-1) * (2**100) })
+ assert_equal(4, a.bsearch {|x| (4 - x).to_r })
+
assert_include([4, 7], a.bsearch {|x| (2**100).coerce((1 - x / 4) * (2**100)).first })
end
@@ -2888,6 +3421,8 @@ class TestArray < Test::Unit::TestCase
assert_equal(nil, a.bsearch_index {|x| 1 * (2**100) })
assert_equal(nil, a.bsearch_index {|x| (-1) * (2**100) })
+ assert_equal(1, a.bsearch_index {|x| (4 - x).to_r })
+
assert_include([1, 2], a.bsearch_index {|x| (2**100).coerce((1 - x / 4) * (2**100)).first })
end
@@ -2911,7 +3446,7 @@ class TestArray < Test::Unit::TestCase
end
EOS
rescue Timeout::Error => e
- skip e.message
+ omit e.message
end
end
@@ -2922,7 +3457,7 @@ class TestArray < Test::Unit::TestCase
Bug11235 = '[ruby-dev:49043] [Bug #11235]'
def test_push_over_ary_max
- assert_separately(['-', ARY_MAX.to_s, Bug11235], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30)
+ assert_separately(['-', ARY_MAX.to_s, Bug11235], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 120)
begin;
a = Array.new(ARGV[0].to_i)
assert_raise(IndexError, ARGV[1]) {0x1000.times {a.push(1)}}
@@ -2977,6 +3512,17 @@ class TestArray < Test::Unit::TestCase
assert_typed_equal(e, v, Complex, msg)
end
+ def test_shrink_shared_array
+ assert_normal_exit(<<~'RUBY', '[Feature #20589]')
+ array = []
+ # Make sure the array is allocated
+ 10.times { |i| array << i }
+ # Simulate a C extension using OBJ_FREEZE
+ Object.instance_method(:freeze).bind_call(array)
+ array.dup
+ RUBY
+ end
+
def test_sum
assert_int_equal(0, [].sum)
assert_int_equal(3, [3].sum)
@@ -3043,10 +3589,72 @@ class TestArray < Test::Unit::TestCase
assert_raise(TypeError) {[1].sum("")}
end
+ def test_big_array_literal_with_kwsplat
+ lit = "["
+ 10000.times { lit << "{}," }
+ lit << "**{}]"
+
+ assert_equal(10000, eval(lit).size)
+ end
+
+ def test_array_safely_modified_by_sort_block
+ var_0 = (1..70).to_a
+ var_0.sort! do |var_0_block_129, var_1_block_129|
+ var_0.pop
+ var_1_block_129 <=> var_0_block_129
+ end.shift(3)
+ 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)
EnvUtil.suppress_warning {require 'continuation'}
end
+ omit 'requires callcc support' unless respond_to?(:callcc, true)
+ end
+
+ def random_generator(&block)
+ class << block
+ alias rand call
+ end
+ block
+ end
+end
+
+class TestArraySubclass < TestArray
+ def setup
+ @verbose = $VERBOSE
+ @cls = Class.new(Array)
+ end
+
+ def test_to_a
+ a = @cls[ 1, 2, 3 ]
+ a_id = a.__id__
+ assert_equal_instance([1, 2, 3], a.to_a)
+ assert_not_equal(a_id, a.to_a.__id__)
+ end
+
+ def test_array_subclass
+ assert_equal(Array, @cls[1,2,3].uniq.class, "[ruby-dev:34581]")
+ assert_equal(Array, @cls[1,2][0,1].class) # embedded
+ assert_equal(Array, @cls[*(1..100)][1..99].class) #not embedded
end
end
diff --git a/test/ruby/test_assignment.rb b/test/ruby/test_assignment.rb
index 45c4d6e058..3d0e773c82 100644
--- a/test/ruby/test_assignment.rb
+++ b/test/ruby/test_assignment.rb
@@ -81,6 +81,162 @@ class TestAssignment < Test::Unit::TestCase
a,b,*c = [*[1,2]]; assert_equal([1,2,[]], [a,b,c])
end
+ def test_massign_order
+ order = []
+ define_singleton_method(:x1){order << :x1; self}
+ define_singleton_method(:y1){order << :y1; self}
+ define_singleton_method(:z=){|x| order << [:z=, x]}
+ define_singleton_method(:x2){order << :x2; self}
+ define_singleton_method(:x3){order << :x3; self}
+ define_singleton_method(:x4){order << :x4; self}
+ define_singleton_method(:x5=){|x| order << [:x5=, x]; self}
+ define_singleton_method(:[]=){|*args| order << [:[]=, *args]}
+ define_singleton_method(:r1){order << :r1; :r1}
+ define_singleton_method(:r2){order << :r2; :r2}
+
+ x1.y1.z, x2[1, 2, 3], self[4] = r1, 6, r2
+ assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, 6], [:[]=, 4, :r2]], order)
+ order.clear
+
+ x1.y1.z, *x2[1, 2, 3], self[4] = r1, 6, 7, r2
+ assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2]], order)
+ order.clear
+
+ x1.y1.z, *x2[1, 2, 3], x3[4] = r1, 6, 7, r2
+ assert_equal([:x1, :y1, :x2, :x3, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2]], order)
+ order.clear
+
+ x1.y1.z, *x2[1, 2, 3], x3[4], x4.x5 = r1, 6, 7, r2, 8
+ assert_equal([:x1, :y1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order)
+ order.clear
+
+ (x1.y1.z, x2.x5), _a = [r1, r2], 7
+ assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:x5=, :r2]], order)
+ order.clear
+
+ (x1.y1.z, x1.x5), *x2[1, 2, 3] = [r1, 5], 6, 7, r2, 8
+ assert_equal([:x1, :y1, :x1, :x2, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7, :r2, 8]]], order)
+ order.clear
+
+ *x2[1, 2, 3], (x3[4], x4.x5) = 6, 7, [r2, 8]
+ assert_equal([:x2, :x3, :x4, :r2, [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order)
+ order.clear
+
+ (x1.y1.z, x1.x5), *x2[1, 2, 3], x3[4], x4.x5 = [r1, 5], 6, 7, r2, 8
+ assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order)
+ order.clear
+
+ (x1.y1.z, x1.x5), *x2[1, 2, 3], (x3[4], x4.x5) = [r1, 5], 6, 7, [r2, 8]
+ assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order)
+ order.clear
+
+ ((x1.y1.z, x1.x5), _a), *x2[1, 2, 3], ((x3[4], x4.x5), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11]
+ assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order)
+ order.clear
+
+ ((x1.y1.z, *x1.x5), _a), *x2[1, 2, 3], ((*x3[4], x4.x5), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11]
+ assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, [5]], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, [:r2]], [:x5=, 8]], order)
+ order.clear
+ end
+
+ def test_massign_const_order
+ order = []
+
+ test_mod_class = Class.new(Module) do
+ define_method(:x1){order << :x1; self}
+ define_method(:y1){order << :y1; self}
+ define_method(:x2){order << :x2; self}
+ define_method(:x3){order << :x3; self}
+ define_method(:x4){order << :x4; self}
+ define_method(:[]){|*args| order << [:[], *args]; self}
+ define_method(:r1){order << :r1; :r1}
+ define_method(:r2){order << :r2; :r2}
+
+ define_method(:constant_values) do
+ h = {}
+ constants.each do |sym|
+ h[sym] = const_get(sym)
+ end
+ h
+ end
+
+ define_singleton_method(:run) do |code|
+ m = new
+ m.instance_eval(code)
+ ret = [order.dup, m.constant_values]
+ order.clear
+ ret
+ end
+ end
+
+ ord, constants = test_mod_class.run(
+ "x1.y1::A, x2[1, 2, 3]::B, self[4]::C = r1, 6, r2"
+ )
+ assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], [:[], 4], :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>6, :C=>:r2}, constants)
+
+ ord, constants = test_mod_class.run(
+ "x1.y1::A, *x2[1, 2, 3]::B, self[4]::C = r1, 6, 7, r2"
+ )
+ assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], [:[], 4], :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>[6, 7], :C=>:r2}, constants)
+
+ ord, constants = test_mod_class.run(
+ "x1.y1::A, *x2[1, 2, 3]::B, x3[4]::C = r1, 6, 7, r2"
+ )
+ assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>[6, 7], :C=>:r2}, constants)
+
+
+ ord, constants = test_mod_class.run(
+ "x1.y1::A, *x2[1, 2, 3]::B, x3[4]::C, x4::D = r1, 6, 7, r2, 8"
+ )
+ assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>[6, 7], :C=>:r2, :D=>8}, constants)
+
+ ord, constants = test_mod_class.run(
+ "(x1.y1::A, x2::B), _a = [r1, r2], 7"
+ )
+ assert_equal([:x1, :y1, :x2, :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>:r2}, constants)
+
+ ord, constants = test_mod_class.run(
+ "(x1.y1::A, x1::B), *x2[1, 2, 3]::C = [r1, 5], 6, 7, r2, 8"
+ )
+ assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>5, :C=>[6, 7, :r2, 8]}, constants)
+
+ ord, constants = test_mod_class.run(
+ "*x2[1, 2, 3]::A, (x3[4]::B, x4::C) = 6, 7, [r2, 8]"
+ )
+ assert_equal([:x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r2], ord)
+ assert_equal({:A=>[6, 7], :B=>:r2, :C=>8}, constants)
+
+ ord, constants = test_mod_class.run(
+ "(x1.y1::A, x1::B), *x2[1, 2, 3]::C, x3[4]::D, x4::E = [r1, 5], 6, 7, r2, 8"
+ )
+ assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>:r2, :E=>8}, constants)
+
+ ord, constants = test_mod_class.run(
+ "(x1.y1::A, x1::B), *x2[1, 2, 3]::C, (x3[4]::D, x4::E) = [r1, 5], 6, 7, [r2, 8]"
+ )
+ assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>:r2, :E=>8}, constants)
+
+ ord, constants = test_mod_class.run(
+ "((x1.y1::A, x1::B), _a), *x2[1, 2, 3]::C, ((x3[4]::D, x4::E), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11]"
+ )
+ assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>:r2, :E=>8}, constants)
+
+ ord, constants = test_mod_class.run(
+ "((x1.y1::A, x1::B), _a), *x2[1, 2, 3]::C, ((*x3[4]::D, x4::E), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11]"
+ )
+ assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>[:r2], :E=>8}, constants)
+ end
+
def test_massign_splat
a,b,*c = *[]; assert_equal([nil,nil,[]], [a,b,c])
a,b,*c = *[1]; assert_equal([1,nil,[]], [a,b,c])
@@ -92,6 +248,21 @@ class TestAssignment < Test::Unit::TestCase
a,b,*c = *[*[1,2]]; assert_equal([1,2,[]], [a,b,c])
end
+ def test_massign_optimized_literal_bug_21012
+ a = []
+ def a.[]=(*args)
+ push args
+ end
+ a["a", "b"], = 1
+ a["a", 10], = 2
+ assert_equal [["a", "b", 1], ["a", 10, 2]], a
+ end
+
+ def test_assign_rescue
+ a = raise rescue 2; assert_equal(2, a)
+ a, b = raise rescue [3,4]; assert_equal([3, 4], [a, b])
+ end
+
def test_assign_abbreviated
bug2050 = '[ruby-core:25629]'
a = Hash.new {[]}
@@ -114,32 +285,32 @@ class TestAssignment < Test::Unit::TestCase
def []=(i, a); 42; end
end
- assert_raise(NoMethodError) {
+ assert_raise(NoMethodError, bug11096) {
o.instance_eval {o.foo = 1}
}
- assert_nothing_raised(NoMethodError) {
+ assert_nothing_raised(NoMethodError, bug11096) {
assert_equal(1, o.instance_eval {self.foo = 1})
}
- assert_raise(NoMethodError) {
+ assert_raise(NoMethodError, bug11096) {
o.instance_eval {o[0] = 1}
}
- assert_nothing_raised(NoMethodError) {
+ assert_nothing_raised(NoMethodError, bug11096) {
assert_equal(1, o.instance_eval {self[0] = 1})
}
- assert_raise(NoMethodError, bug11096) {
- assert_equal(43, o.instance_eval {self.foo += 1})
+ assert_nothing_raised(NoMethodError, bug11096) {
+ o.instance_eval {self.foo += 1}
}
- assert_raise(NoMethodError, bug11096) {
- assert_equal(1, o.instance_eval {self.foo &&= 1})
+ assert_nothing_raised(NoMethodError, bug11096) {
+ o.instance_eval {self.foo &&= 1}
}
- assert_raise(NoMethodError, bug11096) {
- assert_equal(43, o.instance_eval {self[0] += 1})
+ assert_nothing_raised(NoMethodError, bug11096) {
+ o.instance_eval {self[0] += 1}
}
- assert_raise(NoMethodError, bug11096) {
- assert_equal(1, o.instance_eval {self[0] &&= 1})
+ assert_nothing_raised(NoMethodError, bug11096) {
+ o.instance_eval {self[0] &&= 1}
}
end
@@ -451,7 +622,7 @@ class TestAssignment < Test::Unit::TestCase
assert(defined?(a))
assert_nil(a)
- # multiple asignment
+ # multiple assignment
a, b = 1, 2
assert_equal 1, a
assert_equal 2, b
@@ -480,11 +651,10 @@ class TestAssignment < Test::Unit::TestCase
assert_equal 1, a
assert_equal [2, 3], b
- # not supported yet
- #a, *b, c = 1, 2, 3, 4
- #assert_equal 1, a
- #assert_equal [2,3], b
- #assert_equal 4, c
+ a, *b, c = 1, 2, 3, 4
+ assert_equal 1, a
+ assert_equal [2,3], b
+ assert_equal 4, c
a = 1, 2
assert_equal [1, 2], a
@@ -557,6 +727,16 @@ class TestAssignment < Test::Unit::TestCase
result = eval("if (a, b = MyObj.new); [a, b]; end", nil, __FILE__, __LINE__)
assert_equal [[1,2],[3,4]], result
end
+
+ def test_const_assign_order
+ assert_raise(RuntimeError) do
+ eval('raise("recv")::C = raise(ArgumentError, "bar")')
+ end
+
+ assert_raise(RuntimeError) do
+ eval('m = 1; m::C = raise("bar")')
+ end
+ end
end
require_relative 'sentence'
diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb
new file mode 100644
index 0000000000..22ccbfb604
--- /dev/null
+++ b/test/ruby/test_ast.rb
@@ -0,0 +1,1753 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'tempfile'
+require 'pp'
+require_relative '../lib/parser_support'
+
+class RubyVM
+ module AbstractSyntaxTree
+ class Node
+ class CodePosition
+ include Comparable
+ attr_reader :lineno, :column
+ def initialize(lineno, column)
+ @lineno = lineno
+ @column = column
+ end
+
+ def <=>(other)
+ case
+ when lineno < other.lineno
+ -1
+ when lineno == other.lineno
+ column <=> other.column
+ when lineno > other.lineno
+ 1
+ end
+ end
+ end
+
+ def beg_pos
+ CodePosition.new(first_lineno, first_column)
+ end
+
+ def end_pos
+ CodePosition.new(last_lineno, last_column)
+ end
+
+ alias to_s inspect
+ end
+ end
+end
+
+class TestAst < Test::Unit::TestCase
+ class Helper
+ attr_reader :errors
+
+ def initialize(path, src: nil)
+ @path = path
+ @errors = []
+ @debug = false
+ @ast = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse(src) } if src
+ end
+
+ def validate_range
+ @errors = []
+ validate_range0(ast)
+
+ @errors.empty?
+ end
+
+ def validate_not_cared
+ @errors = []
+ validate_not_cared0(ast)
+
+ @errors.empty?
+ end
+
+ def ast
+ return @ast if defined?(@ast)
+ @ast = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse_file(@path) }
+ end
+
+ private
+
+ def validate_range0(node)
+ beg_pos, end_pos = node.beg_pos, node.end_pos
+ children = node.children.grep(RubyVM::AbstractSyntaxTree::Node)
+
+ return true if children.empty?
+ # These NODE_D* has NODE_LIST as nd_next->nd_next whose last locations
+ # we can not update when item is appended.
+ return true if [:DSTR, :DXSTR, :DREGX, :DSYM].include? node.type
+
+ min = children.map(&:beg_pos).min
+ max = children.map(&:end_pos).max
+
+ unless beg_pos <= min
+ @errors << { type: :min_validation_error, min: min, beg_pos: beg_pos, node: node }
+ end
+
+ unless max <= end_pos
+ @errors << { type: :max_validation_error, max: max, end_pos: end_pos, node: node }
+ end
+
+ p "#{node} => #{children}" if @debug
+
+ children.each do |child|
+ p child if @debug
+ validate_range0(child)
+ end
+ end
+
+ def validate_not_cared0(node)
+ beg_pos, end_pos = node.beg_pos, node.end_pos
+ children = node.children.grep(RubyVM::AbstractSyntaxTree::Node)
+
+ @errors << { type: :first_lineno, node: node } if beg_pos.lineno == 0
+ @errors << { type: :first_column, node: node } if beg_pos.column == -1
+ @errors << { type: :last_lineno, node: node } if end_pos.lineno == 0
+ @errors << { type: :last_column, node: node } if end_pos.column == -1
+
+ children.each {|c| validate_not_cared0(c) }
+ end
+ end
+
+ SRCDIR = File.expand_path("../../..", __FILE__)
+
+ Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
+ define_method("test_ranges:#{path}") do
+ helper = Helper.new("#{SRCDIR}/#{path}")
+ helper.validate_range
+
+ assert_equal([], helper.errors)
+ end
+ end
+
+ Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
+ define_method("test_not_cared:#{path}") do
+ helper = Helper.new("#{SRCDIR}/#{path}")
+ helper.validate_not_cared
+
+ assert_equal([], helper.errors)
+ end
+ end
+
+ Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
+ define_method("test_all_tokens:#{path}") do
+ 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
+
+ assert_equal(source_bytes, tokens_bytes)
+
+ (tokens.count - 1).times do |i|
+ token_0 = tokens[i]
+ token_1 = tokens[i + 1]
+ end_pos = token_0.last[2..3]
+ beg_pos = token_1.last[0..1]
+
+ if end_pos[0] == beg_pos[0]
+ # When both tokens are same line, column should be consecutives
+ assert_equal(beg_pos[1], end_pos[1], "#{token_0}. #{token_1}")
+ else
+ # Line should be next
+ assert_equal(beg_pos[0], end_pos[0] + 1, "#{token_0}. #{token_1}")
+ # It should be on the beginning of the line
+ assert_equal(0, beg_pos[1], "#{token_0}. #{token_1}")
+ end
+ end
+ end
+ end
+
+ private def parse(src)
+ EnvUtil.suppress_warning {
+ RubyVM::AbstractSyntaxTree.parse(src)
+ }
+ end
+
+ def test_allocate
+ assert_raise(TypeError) {RubyVM::AbstractSyntaxTree::Node.allocate}
+ end
+
+ def test_parse_argument_error
+ assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(0)}
+ assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(nil)}
+ assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(false)}
+ assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(true)}
+ assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(:foo)}
+ end
+
+ def test_column_with_long_heredoc_identifier
+ term = "A"*257
+ ast = parse("<<-#{term}\n""ddddddd\n#{term}\n")
+ node = ast.children[2]
+ assert_equal(:STR, node.type)
+ assert_equal(0, node.first_column)
+ end
+
+ def test_column_of_heredoc
+ node = parse("<<-SRC\nddddddd\nSRC\n").children[2]
+ assert_equal(:STR, node.type)
+ assert_equal(0, node.first_column)
+ assert_equal(6, node.last_column)
+
+ node = parse("<<SRC\nddddddd\nSRC\n").children[2]
+ assert_equal(:STR, node.type)
+ assert_equal(0, node.first_column)
+ assert_equal(5, node.last_column)
+ end
+
+ def test_parse_raises_syntax_error
+ assert_raise_with_message(SyntaxError, /\bend\b/) do
+ RubyVM::AbstractSyntaxTree.parse("end")
+ end
+ end
+
+ def test_parse_file_raises_syntax_error
+ Tempfile.create(%w"test_ast .rb") do |f|
+ f.puts "end"
+ f.close
+ assert_raise_with_message(SyntaxError, /\bend\b/) do
+ RubyVM::AbstractSyntaxTree.parse_file(f.path)
+ end
+ end
+ end
+
+ def assert_parse(code, warning: '')
+ node = assert_warning(warning) {RubyVM::AbstractSyntaxTree.parse(code)}
+ assert_kind_of(RubyVM::AbstractSyntaxTree::Node, node, code)
+ end
+
+ def assert_invalid_parse(msg, code)
+ assert_raise_with_message(SyntaxError, msg, code) do
+ RubyVM::AbstractSyntaxTree.parse(code)
+ end
+ end
+
+ def test_invalid_exit
+ [
+ "break",
+ "break true",
+ "next",
+ "next true",
+ "redo",
+ ].each do |code, *args|
+ msg = /Invalid #{code[/\A\w+/]}/
+ assert_parse("while false; #{code}; end")
+ assert_parse("until true; #{code}; end")
+ assert_parse("begin #{code}; end while false")
+ assert_parse("begin #{code}; end until true")
+ assert_parse("->{#{code}}")
+ assert_parse("->{class X; #{code}; end}")
+ assert_invalid_parse(msg, "#{code}")
+ assert_invalid_parse(msg, "def m; #{code}; end")
+ assert_invalid_parse(msg, "begin; #{code}; end")
+ assert_parse("END {#{code}}")
+
+ assert_parse("!defined?(#{code})")
+ assert_parse("def m; defined?(#{code}); end")
+ assert_parse("!begin; defined?(#{code}); end")
+
+ next if code.include?(" ")
+ assert_parse("!defined? #{code}")
+ assert_parse("def m; defined? #{code}; end")
+ assert_parse("!begin; defined? #{code}; end")
+ end
+ end
+
+ def test_invalid_retry
+ msg = /Invalid retry/
+ assert_invalid_parse(msg, "retry")
+ assert_invalid_parse(msg, "def m; retry; end")
+ assert_invalid_parse(msg, "begin retry; end")
+ assert_parse("begin rescue; retry; end")
+ assert_invalid_parse(msg, "begin rescue; else; retry; end")
+ assert_invalid_parse(msg, "begin rescue; ensure; retry; end")
+ assert_parse("nil rescue retry")
+ assert_invalid_parse(msg, "END {retry}")
+ assert_invalid_parse(msg, "begin rescue; END {retry}; end")
+
+ assert_parse("!defined?(retry)")
+ assert_parse("def m; defined?(retry); end")
+ assert_parse("!begin defined?(retry); end")
+ assert_parse("begin rescue; else; defined?(retry); end")
+ assert_parse("begin rescue; ensure; p defined?(retry); end")
+ assert_parse("END {defined?(retry)}")
+ assert_parse("begin rescue; END {defined?(retry)}; end")
+ assert_parse("!defined? retry")
+
+ assert_parse("def m; defined? retry; end")
+ assert_parse("!begin defined? retry; end")
+ assert_parse("begin rescue; else; defined? retry; end")
+ assert_parse("begin rescue; ensure; p defined? retry; end")
+ assert_parse("END {defined? retry}")
+ assert_parse("begin rescue; END {defined? retry}; end")
+
+ assert_parse("#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ def foo
+ begin
+ yield
+ rescue StandardError => e
+ begin
+ puts "hi"
+ retry
+ rescue
+ retry unless e
+ raise e
+ else
+ retry
+ ensure
+ retry
+ end
+ end
+ end
+ end;
+ end
+
+ def test_invalid_yield
+ msg = /Invalid yield/
+ assert_invalid_parse(msg, "yield")
+ assert_invalid_parse(msg, "class C; yield; end")
+ assert_invalid_parse(msg, "BEGIN {yield}")
+ assert_invalid_parse(msg, "END {yield}")
+ assert_invalid_parse(msg, "-> {yield}")
+
+ assert_invalid_parse(msg, "yield true")
+ assert_invalid_parse(msg, "class C; yield true; end")
+ assert_invalid_parse(msg, "BEGIN {yield true}")
+ assert_invalid_parse(msg, "END {yield true}")
+ assert_invalid_parse(msg, "-> {yield true}")
+
+ assert_parse("!defined?(yield)")
+ assert_parse("class C; defined?(yield); end")
+ assert_parse("BEGIN {defined?(yield)}")
+ assert_parse("END {defined?(yield)}")
+
+ assert_parse("!defined?(yield true)")
+ assert_parse("class C; defined?(yield true); end")
+ assert_parse("BEGIN {defined?(yield true)}")
+ assert_parse("END {defined?(yield true)}")
+
+ assert_parse("!defined? yield")
+ assert_parse("class C; defined? yield; end")
+ assert_parse("BEGIN {defined? yield}")
+ 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?
+
+ exception = begin
+ raise
+ rescue => e
+ e
+ end
+ loc = exception.backtrace_locations.first
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
+ node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true)
+
+ 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]'
+
+ assert_raise(TypeError, bug19262) { RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(1) }
+ end
+
+ def test_of_proc_and_method
+ omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess?
+
+ proc = Proc.new { 1 + 2 }
+ method = self.method(__method__)
+
+ node_proc = RubyVM::AbstractSyntaxTree.of(proc)
+ node_method = RubyVM::AbstractSyntaxTree.of(method)
+
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_proc)
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_method)
+
+ Tempfile.create(%w"test_of .rb") do |tmp|
+ tmp.print "#{<<-"begin;"}\n#{<<-'end;'}"
+ begin;
+ SCRIPT_LINES__ = {}
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(proc {|x| x}))
+ end;
+ tmp.close
+ assert_separately(["-", tmp.path], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ load ARGV[0]
+ assert_empty(SCRIPT_LINES__)
+ end;
+ end
+ end
+
+ def sample_backtrace_location
+ [caller_locations(0).first, __LINE__]
+ end
+
+ def test_of_backtrace_location
+ omit if ParserSupport.prism_enabled?
+
+ backtrace_location, lineno = sample_backtrace_location
+ node = RubyVM::AbstractSyntaxTree.of(backtrace_location)
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node)
+ assert_equal(lineno, node.first_lineno)
+ end
+
+ def test_of_error
+ assert_raise(TypeError) { RubyVM::AbstractSyntaxTree.of("1 + 2") }
+ end
+
+ def test_of_proc_and_method_under_eval
+ omit if ParserSupport.prism_enabled?
+
+ keep_script_lines_back = RubyVM.keep_script_lines
+ RubyVM.keep_script_lines = false
+
+ method = self.method(eval("def example_method_#{$$}; end"))
+ assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
+
+ method = self.method(eval("def self.example_singleton_method_#{$$}; end"))
+ assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
+
+ method = eval("proc{}")
+ assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
+
+ method = self.method(eval("singleton_class.define_method(:example_define_method_#{$$}){}"))
+ assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
+
+ method = self.method(eval("define_singleton_method(:example_dsm_#{$$}){}"))
+ assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
+
+ method = eval("Class.new{def example_method; end}.instance_method(:example_method)")
+ assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
+
+ method = eval("Class.new{def example_method; end}.instance_method(:example_method)")
+ assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
+
+ ensure
+ RubyVM.keep_script_lines = keep_script_lines_back
+ end
+
+ def test_of_proc_and_method_under_eval_with_keep_script_lines
+ omit if ParserSupport.prism_enabled?
+ pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO
+
+ keep_script_lines_back = RubyVM.keep_script_lines
+ RubyVM.keep_script_lines = true
+
+ method = self.method(eval("def example_method_#{$$}_with_keep_script_lines; end"))
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ method = self.method(eval("def self.example_singleton_method_#{$$}_with_keep_script_lines; end"))
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ method = eval("proc{}")
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ method = self.method(eval("singleton_class.define_method(:example_define_method_#{$$}_with_keep_script_lines){}"))
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ method = self.method(eval("define_singleton_method(:example_dsm_#{$$}_with_keep_script_lines){}"))
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ method = eval("Class.new{def example_method_with_keep_script_lines; end}.instance_method(:example_method_with_keep_script_lines)")
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ method = eval("Class.new{def example_method_with_keep_script_lines; end}.instance_method(:example_method_with_keep_script_lines)")
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ ensure
+ RubyVM.keep_script_lines = keep_script_lines_back
+ end
+
+ def test_of_backtrace_location_under_eval
+ omit if ParserSupport.prism_enabled?
+
+ keep_script_lines_back = RubyVM.keep_script_lines
+ RubyVM.keep_script_lines = false
+
+ m = Module.new do
+ eval(<<-END, nil, __FILE__, __LINE__)
+ def self.sample_backtrace_location
+ caller_locations(0).first
+ end
+ END
+ end
+ backtrace_location = m.sample_backtrace_location
+ assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(backtrace_location) }
+
+ ensure
+ RubyVM.keep_script_lines = keep_script_lines_back
+ end
+
+ def test_of_backtrace_location_under_eval_with_keep_script_lines
+ omit if ParserSupport.prism_enabled?
+ pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO
+
+ keep_script_lines_back = RubyVM.keep_script_lines
+ RubyVM.keep_script_lines = true
+
+ m = Module.new do
+ eval(<<-END, nil, __FILE__, __LINE__)
+ def self.sample_backtrace_location
+ caller_locations(0).first
+ end
+ END
+ end
+ backtrace_location = m.sample_backtrace_location
+ node = RubyVM::AbstractSyntaxTree.of(backtrace_location)
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node)
+ assert_equal(2, node.first_lineno)
+
+ ensure
+ RubyVM.keep_script_lines = keep_script_lines_back
+ end
+
+ def test_of_c_method
+ c = Class.new { attr_reader :foo }
+ assert_nil(RubyVM::AbstractSyntaxTree.of(c.instance_method(:foo)))
+ end
+
+ def test_scope_local_variables
+ node = RubyVM::AbstractSyntaxTree.parse("_x = 0")
+ lv, _, body = *node.children
+ assert_equal([:_x], lv)
+ assert_equal(:LASGN, body.type)
+ end
+
+ def test_call
+ node = RubyVM::AbstractSyntaxTree.parse("nil.foo")
+ _, _, body = *node.children
+ assert_equal(:CALL, body.type)
+ recv, mid, args = body.children
+ assert_equal(:NIL, recv.type)
+ assert_equal(:foo, mid)
+ assert_nil(args)
+ end
+
+ def test_fcall
+ node = RubyVM::AbstractSyntaxTree.parse("foo()")
+ _, _, body = *node.children
+ assert_equal(:FCALL, body.type)
+ mid, args = body.children
+ assert_equal(:foo, mid)
+ assert_nil(args)
+ end
+
+ def test_vcall
+ node = RubyVM::AbstractSyntaxTree.parse("foo")
+ _, _, body = *node.children
+ assert_equal(:VCALL, body.type)
+ mid, args = body.children
+ assert_equal(:foo, mid)
+ assert_nil(args)
+ end
+
+ def test_defn
+ node = RubyVM::AbstractSyntaxTree.parse("def a; end")
+ _, _, body = *node.children
+ assert_equal(:DEFN, body.type)
+ mid, defn = body.children
+ assert_equal(:a, mid)
+ assert_equal(:SCOPE, defn.type)
+ _, args, = defn.children
+ assert_equal(:ARGS, args.type)
+ end
+
+ def test_defn_endless
+ node = RubyVM::AbstractSyntaxTree.parse("def a = nil")
+ _, _, body = *node.children
+ assert_equal(:DEFN, body.type)
+ mid, defn = body.children
+ assert_equal(:a, mid)
+ assert_equal(:SCOPE, defn.type)
+ _, args, = defn.children
+ assert_equal(:ARGS, args.type)
+ end
+
+ def test_defs
+ node = RubyVM::AbstractSyntaxTree.parse("def a.b; end")
+ _, _, body = *node.children
+ assert_equal(:DEFS, body.type)
+ recv, mid, defn = body.children
+ assert_equal(:VCALL, recv.type)
+ assert_equal(:b, mid)
+ assert_equal(:SCOPE, defn.type)
+ _, args, = defn.children
+ assert_equal(:ARGS, args.type)
+ end
+
+ def test_defs_endless
+ node = RubyVM::AbstractSyntaxTree.parse("def a.b = nil")
+ _, _, body = *node.children
+ assert_equal(:DEFS, body.type)
+ recv, mid, defn = body.children
+ assert_equal(:VCALL, recv.type)
+ assert_equal(:b, mid)
+ assert_equal(:SCOPE, defn.type)
+ _, args, = defn.children
+ assert_equal(:ARGS, args.type)
+ end
+
+ def test_dstr
+ node = parse('"foo#{1}bar"')
+ _, _, body = *node.children
+ assert_equal(:DSTR, body.type)
+ head, body = body.children
+ assert_equal("foo", head)
+ assert_equal(:EVSTR, body.type)
+ body, = body.children
+ assert_equal(:INTEGER, body.type)
+ assert_equal([1], body.children)
+ end
+
+ def test_while
+ node = RubyVM::AbstractSyntaxTree.parse('1 while qux')
+ _, _, body = *node.children
+ assert_equal(:WHILE, body.type)
+ type1 = body.children[2]
+ node = RubyVM::AbstractSyntaxTree.parse('begin 1 end while qux')
+ _, _, body = *node.children
+ assert_equal(:WHILE, body.type)
+ type2 = body.children[2]
+ assert_not_equal(type1, type2)
+ end
+
+ def test_until
+ node = RubyVM::AbstractSyntaxTree.parse('1 until qux')
+ _, _, body = *node.children
+ assert_equal(:UNTIL, body.type)
+ type1 = body.children[2]
+ node = RubyVM::AbstractSyntaxTree.parse('begin 1 end until qux')
+ _, _, body = *node.children
+ assert_equal(:UNTIL, body.type)
+ type2 = body.children[2]
+ assert_not_equal(type1, type2)
+ end
+
+ def test_rest_arg
+ rest_arg = lambda do |arg_str|
+ node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
+ node = node.children.last.children.last.children[1].children[-4]
+ end
+
+ assert_equal(nil, rest_arg.call(''))
+ assert_equal(:r, rest_arg.call('*r'))
+ assert_equal(:r, rest_arg.call('a, *r'))
+ assert_equal(:*, rest_arg.call('*'))
+ assert_equal(:*, rest_arg.call('a, *'))
+ end
+
+ def test_block_arg
+ block_arg = lambda do |arg_str|
+ node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
+ node = node.children.last.children.last.children[1].children[-1]
+ end
+
+ assert_equal(nil, block_arg.call(''))
+ assert_equal(:block, block_arg.call('&block'))
+ assert_equal(:&, block_arg.call('&'))
+ end
+
+ def test_keyword_rest
+ kwrest = lambda do |arg_str|
+ node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
+ node = node.children.last.children.last.children[1].children[-2]
+ node ? node.children : node
+ end
+
+ assert_equal(nil, kwrest.call(''))
+ assert_equal([:**], kwrest.call('**'))
+ assert_equal(false, kwrest.call('**nil'))
+ assert_equal([:a], kwrest.call('**a'))
+ end
+
+ def test_argument_forwarding
+ forwarding = lambda do |arg_str|
+ node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
+ node = node.children.last.children.last.children[1]
+ node ? [node.children[-4], node.children[-2]&.children, node.children[-1]] : []
+ end
+
+ assert_equal([:*, [:**], :&], forwarding.call('...'))
+ end
+
+ def test_ranges_numbered_parameter
+ helper = Helper.new(__FILE__, src: "1.times {_1}")
+ helper.validate_range
+ assert_equal([], helper.errors)
+ end
+
+ def test_op_asgn2
+ node = RubyVM::AbstractSyntaxTree.parse("struct.field += foo")
+ _, _, body = *node.children
+ assert_equal(:OP_ASGN2, body.type)
+ recv, _, mid, op, value = body.children
+ assert_equal(:VCALL, recv.type)
+ assert_equal(:field, mid)
+ assert_equal(:+, op)
+ assert_equal(:VCALL, value.type)
+ end
+
+ def test_args
+ rest = 6
+ node = RubyVM::AbstractSyntaxTree.parse("proc { |a| }")
+ _, args = *node.children.last.children[1].children
+ assert_equal(nil, args.children[rest])
+
+ node = RubyVM::AbstractSyntaxTree.parse("proc { |a,| }")
+ _, args = *node.children.last.children[1].children
+ assert_equal(:NODE_SPECIAL_EXCESSIVE_COMMA, args.children[rest])
+
+ node = RubyVM::AbstractSyntaxTree.parse("proc { |*a| }")
+ _, args = *node.children.last.children[1].children
+ assert_equal(:a, args.children[rest])
+ end
+
+ def test_return
+ assert_ast_eqaul(<<~STR, <<~EXP)
+ def m(a)
+ return a
+ end
+ STR
+ (SCOPE@1:0-3:3
+ tbl: []
+ args: nil
+ body:
+ (DEFN@1:0-3:3
+ mid: :m
+ body:
+ (SCOPE@1:0-3:3
+ tbl: [:a]
+ args:
+ (ARGS@1:6-1:7
+ pre_num: 1
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (RETURN@2:2-2:10 (LVAR@2:9-2:10 :a)))))
+ EXP
+ end
+
+ def test_keep_script_lines_for_parse
+ node = RubyVM::AbstractSyntaxTree.parse(<<~END, keep_script_lines: true)
+1.times do
+ 2.times do
+ end
+end
+__END__
+dummy
+ END
+
+ expected = [
+ "1.times do\n",
+ " 2.times do\n",
+ " end\n",
+ "end\n",
+ "__END__\n",
+ ]
+ assert_equal(expected, node.script_lines)
+
+ expected =
+ "1.times do\n" +
+ " 2.times do\n" +
+ " end\n" +
+ "end"
+ assert_equal(expected, node.source)
+
+ expected =
+ "do\n" +
+ " 2.times do\n" +
+ " end\n" +
+ "end"
+ assert_equal(expected, node.children.last.children.last.source)
+
+ expected =
+ "2.times do\n" +
+ " end"
+ assert_equal(expected, node.children.last.children.last.children.last.source)
+ end
+
+ def test_keep_script_lines_for_of
+ omit if ParserSupport.prism_enabled?
+
+ proc = Proc.new { 1 + 2 }
+ method = self.method(__method__)
+
+ node_proc = RubyVM::AbstractSyntaxTree.of(proc, keep_script_lines: true)
+ node_method = RubyVM::AbstractSyntaxTree.of(method, keep_script_lines: true)
+
+ 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
+
+ def test_keep_script_lines_for_of_with_existing_SCRIPT_LINES__that_has__FILE__as_a_key
+ omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess?
+
+ # This test confirms that the bug that previously occurred because of
+ # `AbstractSyntaxTree.of`s unnecessary dependence on SCRIPT_LINES__ does not reproduce.
+ # The bug occurred only if SCRIPT_LINES__ included __FILE__ as a key.
+ lines = [
+ "SCRIPT_LINES__ = {__FILE__ => []}",
+ "puts RubyVM::AbstractSyntaxTree.of(->{ 1 + 2 }, keep_script_lines: true).script_lines",
+ "p SCRIPT_LINES__"
+ ]
+ test_stdout = lines + ['{"-e" => []}']
+ assert_in_out_err(["-e", lines.join("\n")], "", test_stdout, [])
+ end
+
+ def test_source_with_multibyte_characters
+ ast = RubyVM::AbstractSyntaxTree.parse(%{a("\u00a7");b("\u00a9")}, keep_script_lines: true)
+ a_fcall, b_fcall = ast.children[2].children
+
+ assert_equal(%{a("\u00a7")}, a_fcall.source)
+ assert_equal(%{b("\u00a9")}, b_fcall.source)
+ end
+
+ def test_keep_tokens_for_parse
+ node = RubyVM::AbstractSyntaxTree.parse(<<~END, keep_tokens: true)
+ 1.times do
+ end
+ __END__
+ dummy
+ END
+
+ expected = [
+ [:tINTEGER, "1"],
+ [:".", "."],
+ [:tIDENTIFIER, "times"],
+ [:tSP, " "],
+ [:keyword_do, "do"],
+ [:tIGNORED_NL, "\n"],
+ [:keyword_end, "end"],
+ [:nl, "\n"],
+ ]
+ assert_equal(expected, node.all_tokens.map { [_2, _3]})
+ end
+
+ def test_keep_tokens_unexpected_backslash
+ assert_raise_with_message(SyntaxError, /unexpected backslash/) do
+ RubyVM::AbstractSyntaxTree.parse("\\", keep_tokens: true)
+ end
+ end
+
+ def test_encoding_with_keep_script_lines
+ # Stop a warning "possibly useless use of a literal in void context"
+ verbose_bak, $VERBOSE = $VERBOSE, nil
+
+ enc = Encoding::EUC_JP
+ code = "__ENCODING__".encode(enc)
+
+ assert_equal(enc, eval(code))
+
+ node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: false)
+ assert_equal(enc, node.children[2].children[0])
+
+ node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: true)
+ assert_equal(enc, node.children[2].children[0])
+
+ ensure
+ $VERBOSE = verbose_bak
+ end
+
+ def test_e_option
+ 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"],
+ "", [":DEFN"], [])
+ end
+
+ def test_error_tolerant
+ verbose_bak, $VERBOSE = $VERBOSE, false
+ node = RubyVM::AbstractSyntaxTree.parse(<<~STR, error_tolerant: true)
+ class A
+ def m
+ if;
+ a = 10
+ end
+ end
+ STR
+ assert_nil($!)
+
+ assert_equal(:SCOPE, node.type)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+
+ def test_error_tolerant_end_is_short_for_method_define
+ assert_error_tolerant(<<~STR, <<~EXP)
+ def m
+ m2
+ STR
+ (SCOPE@1:0-2:4
+ tbl: []
+ args: nil
+ body:
+ (DEFN@1:0-2:4
+ mid: :m
+ body:
+ (SCOPE@1:0-2:4
+ tbl: []
+ args:
+ (ARGS@1:5-1:5
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (VCALL@2:2-2:4 :m2))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_singleton_method_define
+ assert_error_tolerant(<<~STR, <<~EXP)
+ def obj.m
+ m2
+ STR
+ (SCOPE@1:0-2:4
+ tbl: []
+ args: nil
+ body:
+ (DEFS@1:0-2:4 (VCALL@1:4-1:7 :obj) :m
+ (SCOPE@1:0-2:4
+ tbl: []
+ args:
+ (ARGS@1:9-1:9
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (VCALL@2:2-2:4 :m2))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_begin
+ assert_error_tolerant(<<~STR, <<~EXP)
+ begin
+ a = 1
+ STR
+ (SCOPE@1:0-2:7 tbl: [:a] args: nil body: (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1)))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_if
+ assert_error_tolerant(<<~STR, <<~EXP)
+ if cond
+ a = 1
+ STR
+ (SCOPE@1:0-2:7
+ tbl: [:a]
+ args: nil
+ body:
+ (IF@1:0-2:7 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
+ nil))
+ EXP
+
+ assert_error_tolerant(<<~STR, <<~EXP)
+ if cond
+ a = 1
+ else
+ STR
+ (SCOPE@1:0-3:4
+ tbl: [:a]
+ args: nil
+ body:
+ (IF@1:0-3:4 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
+ (BEGIN@3:4-3:4 nil)))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_unless
+ assert_error_tolerant(<<~STR, <<~EXP)
+ unless cond
+ a = 1
+ STR
+ (SCOPE@1:0-2:7
+ tbl: [:a]
+ args: nil
+ body:
+ (UNLESS@1:0-2:7 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
+ nil))
+ EXP
+
+ assert_error_tolerant(<<~STR, <<~EXP)
+ unless cond
+ a = 1
+ else
+ STR
+ (SCOPE@1:0-3:4
+ tbl: [:a]
+ args: nil
+ body:
+ (UNLESS@1:0-3:4 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
+ (BEGIN@3:4-3:4 nil)))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_while
+ assert_error_tolerant(<<~STR, <<~EXP)
+ while true
+ m
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body: (WHILE@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_until
+ assert_error_tolerant(<<~STR, <<~EXP)
+ until true
+ m
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body: (UNTIL@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_case
+ assert_error_tolerant(<<~STR, <<~EXP)
+ case a
+ when 1
+ STR
+ (SCOPE@1:0-2:6
+ tbl: []
+ args: nil
+ body:
+ (CASE@1:0-2:6 (VCALL@1:5-1:6 :a)
+ (WHEN@2:0-2:6 (LIST@2:5-2:6 (INTEGER@2:5-2:6 1) nil) (BEGIN@2:6-2:6 nil)
+ nil)))
+ EXP
+
+
+ assert_error_tolerant(<<~STR, <<~EXP)
+ case
+ when a == 1
+ STR
+ (SCOPE@1:0-2:11
+ tbl: []
+ args: nil
+ body:
+ (CASE2@1:0-2:11 nil
+ (WHEN@2:0-2:11
+ (LIST@2:5-2:11
+ (OPCALL@2:5-2:11 (VCALL@2:5-2:6 :a) :==
+ (LIST@2:10-2:11 (INTEGER@2:10-2:11 1) nil)) nil)
+ (BEGIN@2:11-2:11 nil) nil)))
+ EXP
+
+
+ assert_error_tolerant(<<~STR, <<~EXP)
+ case a
+ in {a: String}
+ STR
+ (SCOPE@1:0-2:14
+ tbl: []
+ args: nil
+ body:
+ (CASE3@1:0-2:14 (VCALL@1:5-1:6 :a)
+ (IN@2:0-2:14
+ (HSHPTN@2:4-2:13
+ const: nil
+ kw:
+ (HASH@2:4-2:13
+ (LIST@2:4-2:13 (SYM@2:4-2:6 :a) (CONST@2:7-2:13 :String) nil))
+ kwrest: nil) (BEGIN@2:14-2:14 nil) nil)))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_for
+ assert_error_tolerant(<<~STR, <<~EXP)
+ for i in ary
+ m
+ STR
+ (SCOPE@1:0-2:3
+ tbl: [:i]
+ args: nil
+ body:
+ (FOR@1:0-2:3 (VCALL@1:9-1:12 :ary)
+ (SCOPE@1:0-2:3
+ tbl: [nil]
+ args:
+ (ARGS@1:4-1:5
+ pre_num: 1
+ pre_init: (LASGN@1:4-1:5 :i (DVAR@1:4-1:5 nil))
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (VCALL@2:2-2:3 :m))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_class
+ assert_error_tolerant(<<~STR, <<~EXP)
+ class C
+ STR
+ (SCOPE@1:0-1:7
+ tbl: []
+ args: nil
+ body:
+ (CLASS@1:0-1:7 (COLON2@1:6-1:7 nil :C) nil
+ (SCOPE@1:0-1:7 tbl: [] args: nil body: (BEGIN@1:7-1:7 nil))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_module
+ assert_error_tolerant(<<~STR, <<~EXP)
+ module M
+ STR
+ (SCOPE@1:0-1:8
+ tbl: []
+ args: nil
+ body:
+ (MODULE@1:0-1:8 (COLON2@1:7-1:8 nil :M)
+ (SCOPE@1:0-1:8 tbl: [] args: nil body: (BEGIN@1:8-1:8 nil))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_do
+ assert_error_tolerant(<<~STR, <<~EXP)
+ m do
+ a
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body:
+ (ITER@1:0-2:3 (FCALL@1:0-1:1 :m nil)
+ (SCOPE@1:2-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_do_block
+ assert_error_tolerant(<<~STR, <<~EXP)
+ m 1 do
+ a
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body:
+ (ITER@1:0-2:3 (FCALL@1:0-1:3 :m (LIST@1:2-1:3 (INTEGER@1:2-1:3 1) nil))
+ (SCOPE@1:4-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_do_LAMBDA
+ assert_error_tolerant(<<~STR, <<~EXP)
+ -> do
+ a
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body:
+ (LAMBDA@1:0-2:3
+ (SCOPE@1:0-2:3
+ tbl: []
+ args:
+ (ARGS@1:2-1:2
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (VCALL@2:2-2:3 :a))))
+ EXP
+ end
+
+ def test_error_tolerant_treat_end_as_keyword_based_on_indent
+ assert_error_tolerant(<<~STR, <<~EXP)
+ module Z
+ class Foo
+ foo.
+ end
+
+ def bar
+ end
+ end
+ STR
+ (SCOPE@1:0-8:3
+ tbl: []
+ args: nil
+ body:
+ (MODULE@1:0-8:3 (COLON2@1:7-1:8 nil :Z)
+ (SCOPE@1:0-8:3
+ tbl: []
+ args: nil
+ body:
+ (BLOCK@1:8-7:5 (BEGIN@1:8-1:8 nil)
+ (CLASS@2:2-4:5 (COLON2@2:8-2:11 nil :Foo) nil
+ (SCOPE@2:2-4:5
+ tbl: []
+ args: nil
+ body: (BLOCK@2:11-4:5 (BEGIN@2:11-2:11 nil) (ERROR@3:4-4:5))))
+ (DEFN@6:2-7:5
+ mid: :bar
+ body:
+ (SCOPE@6:2-7:5
+ tbl: []
+ args:
+ (ARGS@6:9-6:9
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: nil))))))
+ EXP
+ end
+
+ def test_error_tolerant_expr_value_can_be_error
+ assert_error_tolerant(<<~STR, <<~EXP)
+ def m
+ if
+ end
+ STR
+ (SCOPE@1:0-3:3
+ tbl: []
+ args: nil
+ body:
+ (DEFN@1:0-3:3
+ mid: :m
+ body:
+ (SCOPE@1:0-3:3
+ tbl: []
+ args:
+ (ARGS@1:5-1:5
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (IF@2:2-3:3 (ERROR@3:0-3:3) nil nil))))
+ EXP
+ end
+
+ def test_error_tolerant_unexpected_backslash
+ node = assert_error_tolerant("\\", <<~EXP, keep_tokens: true)
+ (SCOPE@1:0-1:1 tbl: [] args: nil body: (ERROR@1:0-1:1))
+ EXP
+ assert_equal([[0, :backslash, "\\", [1, 0, 1, 1]]], node.children.last.tokens)
+ end
+
+ def test_with_bom
+ assert_error_tolerant("\u{feff}nil", <<~EXP)
+ (SCOPE@1:0-1:3 tbl: [] args: nil body: (NIL@1:0-1:3))
+ EXP
+ end
+
+ def test_unused_block_local_variable
+ assert_warning('') do
+ RubyVM::AbstractSyntaxTree.parse(%{->(; foo) {}})
+ end
+ end
+
+ def test_memory_leak
+ assert_no_memory_leak([], "#{<<~"begin;"}", "\n#{<<~'end;'}", rss: true)
+ begin;
+ 1_000_000.times do
+ eval("")
+ end
+ end;
+ end
+
+ def test_locations
+ begin
+ verbose_bak, $VERBOSE = $VERBOSE, false
+ node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ locations = node.locations
+
+ assert_equal(RubyVM::AbstractSyntaxTree::Location, locations[0].class)
+ end
+
+ private
+
+ def assert_error_tolerant(src, expected, keep_tokens: false)
+ assert_ast_eqaul(src, expected, error_tolerant: true, keep_tokens: keep_tokens)
+ end
+
+ def assert_ast_eqaul(src, expected, **options)
+ begin
+ verbose_bak, $VERBOSE = $VERBOSE, false
+ node = RubyVM::AbstractSyntaxTree.parse(src, **options)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ assert_nil($!)
+ str = ""
+ PP.pp(node, str, 80)
+ assert_equal(expected, str)
+ node
+ end
+
+ class TestLocation < Test::Unit::TestCase
+ def test_lineno_and_column
+ node = ast_parse("1 + 2")
+ assert_locations(node.locations, [[1, 0, 1, 5]])
+ end
+
+ def test_alias_locations
+ node = ast_parse("alias foo bar")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
+ end
+
+ def test_and_locations
+ node = ast_parse("1 and 2")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 2, 1, 5]])
+
+ node = ast_parse("1 && 2")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]])
+ end
+
+ def test_block_pass_locations
+ node = ast_parse("foo(&bar)")
+ assert_locations(node.children[-1].children[-1].locations, [[1, 4, 1, 8], [1, 4, 1, 5]])
+
+ node = ast_parse("def a(&); b(&) end")
+ assert_locations(node.children[-1].children[-1].children[-1].children[-1].children[-1].locations, [[1, 12, 1, 13], [1, 12, 1, 13]])
+ end
+
+ def test_break_locations
+ node = ast_parse("loop { break 1 }")
+ assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 14], [1, 7, 1, 12]])
+ end
+
+ def test_case_locations
+ node = ast_parse("case a; when 1; end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 19], [1, 0, 1, 4], [1, 16, 1, 19]])
+ end
+
+ def test_case2_locations
+ node = ast_parse("case; when 1; end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 4], [1, 14, 1, 17]])
+ end
+
+ def test_case3_locations
+ node = ast_parse("case a; in 1; end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 4], [1, 14, 1, 17]])
+ end
+
+ def test_class_locations
+ node = ast_parse("class A end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 11], [1, 0, 1, 5], nil, [1, 8, 1, 11]])
+
+ node = ast_parse("class A < B; end")
+ 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]])
+
+ node = ast_parse("foo(1..2)")
+ assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 8], [1, 5, 1, 7]])
+
+ node = ast_parse("foo(1..2, 3)")
+ assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 8], [1, 5, 1, 7]])
+
+ node = ast_parse("foo(..2)")
+ assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 7], [1, 4, 1, 6]])
+ end
+
+ def test_dot3_locations
+ node = ast_parse("1...2")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 5], [1, 1, 1, 4]])
+
+ node = ast_parse("foo(1...2)")
+ assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 9], [1, 5, 1, 8]])
+
+ node = ast_parse("foo(1...2, 3)")
+ assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 9], [1, 5, 1, 8]])
+
+ node = ast_parse("foo(...2)")
+ assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 8], [1, 4, 1, 7]])
+ end
+
+ def test_evstr_locations
+ node = ast_parse('"#{foo}"')
+ assert_locations(node.children[-1].children[1].locations, [[1, 0, 1, 8], [1, 1, 1, 3], [1, 6, 1, 7]])
+
+ node = ast_parse('"#$1"')
+ assert_locations(node.children[-1].children[1].locations, [[1, 0, 1, 5], [1, 1, 1, 2], nil])
+ end
+
+ def test_flip2_locations
+ node = ast_parse("if 'a'..'z'; foo; end")
+ assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 11], [1, 6, 1, 8]])
+
+ node = ast_parse('if 1..5; foo; end')
+ assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 7], [1, 4, 1, 6]])
+ end
+
+ def test_flip3_locations
+ node = ast_parse("if 'a'...('z'); foo; end")
+ assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 14], [1, 6, 1, 9]])
+
+ node = ast_parse('if 1...5; foo; end')
+ assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 8], [1, 4, 1, 7]])
+ end
+
+ def test_for_locations
+ node = ast_parse("for a in b; end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 15], [1, 0, 1, 3], [1, 6, 1, 8], nil, [1, 12, 1, 15]])
+
+ node = ast_parse("for a in b do; end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 18], [1, 0, 1, 3], [1, 6, 1, 8], [1, 11, 1, 13], [1, 15, 1, 18]])
+ end
+
+ def test_lambda_locations
+ node = ast_parse("-> (a, b) { foo }")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 2], [1, 10, 1, 11], [1, 16, 1, 17]])
+
+ node = ast_parse("-> (a, b) do foo end")
+ 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]])
+
+ node = ast_parse("1 if 2")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4], nil, nil])
+
+ node = ast_parse("if 1; elsif 2; else end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 23], [1, 0, 1, 2], [1, 4, 1, 5], [1, 20, 1, 23]])
+ assert_locations(node.children[-1].children[-1].locations, [[1, 6, 1, 19], [1, 6, 1, 11], [1, 13, 1, 14], [1, 20, 1, 23]])
+
+ node = ast_parse("true ? 1 : 2")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 12], nil, [1, 9, 1, 10], nil])
+
+ node = ast_parse("case a; in b if c; end")
+ 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]])
+ end
+
+ def test_op_asgn1_locations
+ node = ast_parse("ary[1] += foo")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 13], nil, [1, 3, 1, 4], [1, 5, 1, 6], [1, 7, 1, 9]])
+
+ node = ast_parse("ary[1, 2] += foo")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 16], nil, [1, 3, 1, 4], [1, 8, 1, 9], [1, 10, 1, 12]])
+ end
+
+ def test_or_locations
+ node = ast_parse("1 or 2")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]])
+
+ node = ast_parse("1 || 2")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]])
+ end
+
+ def test_op_asgn2_locations
+ node = ast_parse("a.b += 1")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 1, 1, 2], [1, 2, 1, 3], [1, 4, 1, 6]])
+
+ node = ast_parse("A::B.c += d")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 11], [1, 4, 1, 5], [1, 5, 1, 6], [1, 7, 1, 9]])
+
+ node = ast_parse("a = b.c += d")
+ assert_locations(node.children[-1].children[-1].locations, [[1, 4, 1, 12], [1, 5, 1, 6], [1, 6, 1, 7], [1, 8, 1, 10]])
+
+ node = ast_parse("a = A::B.c += d")
+ assert_locations(node.children[-1].children[-1].locations, [[1, 4, 1, 15], [1, 8, 1, 9], [1, 9, 1, 10], [1, 11, 1, 13]])
+ end
+
+ def test_postexe_locations
+ node = ast_parse("END { }")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 3], [1, 4, 1, 5], [1, 7, 1, 8]])
+
+ node = ast_parse("END { 1 }")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 0, 1, 3], [1, 4, 1, 5], [1, 8, 1, 9]])
+ end
+
+ def test_redo_locations
+ node = ast_parse("loop { redo }")
+ assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 11], [1, 7, 1, 11]])
+ end
+
+ def test_regx_locations
+ node = ast_parse("/foo/")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 5], [1, 0, 1, 1], [1, 1, 1, 4], [1, 4, 1, 5]])
+
+ node = ast_parse("/foo/i")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 0, 1, 1], [1, 1, 1, 4], [1, 4, 1, 6]])
+ end
+
+ def test_return_locations
+ node = ast_parse("return 1")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 6]])
+
+ node = ast_parse("return")
+ 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]])
+
+ node = ast_parse("a = *1, 2")
+ assert_locations(node.children[-1].children[1].children[0].locations, [[1, 4, 1, 6], [1, 4, 1, 5]])
+
+ node = ast_parse("case a; when *1; end")
+ assert_locations(node.children[-1].children[1].children[0].locations, [[1, 13, 1, 15], [1, 13, 1, 14]])
+
+ node = ast_parse("case a; when *1, 2; end")
+ assert_locations(node.children[-1].children[1].children[0].children[0].locations, [[1, 13, 1, 15], [1, 13, 1, 14]])
+ end
+
+ def test_super_locations
+ node = ast_parse("super 1")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 0, 1, 5], nil, nil])
+
+ node = ast_parse("super(1)")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 5], [1, 5, 1, 6], [1, 7, 1, 8]])
+ end
+
+ def test_unless_locations
+ node = ast_parse("unless cond then 1 else 2 end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 29], [1, 0, 1, 6], [1, 12, 1, 16], [1, 26, 1, 29]])
+
+ node = ast_parse("1 unless 2")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 10], [1, 2, 1, 8], nil, nil])
+ end
+
+ def test_undef_locations
+ node = ast_parse("undef foo")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 0, 1, 5]])
+
+ node = ast_parse("undef foo, bar")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 14], [1, 0, 1, 5]])
+ end
+
+ def test_valias_locations
+ node = ast_parse("alias $foo $bar")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 15], [1, 0, 1, 5]])
+
+ node = ast_parse("alias $foo $&")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
+
+ node = ast_parse("alias $foo $`")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
+
+ node = ast_parse("alias $foo $'")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
+
+ node = ast_parse("alias $foo $+")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
+ end
+
+ def test_when_locations
+ node = ast_parse("case a; when 1 then 2; end")
+ assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 22], [1, 8, 1, 12], [1, 15, 1, 19]])
+ end
+
+ def test_while_locations
+ node = ast_parse("while cond do 1 end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 19], [1, 0, 1, 5], [1, 16, 1, 19]])
+
+ node = ast_parse("1 while 2")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 2, 1, 7], nil])
+ end
+
+ def test_until_locations
+ node = ast_parse("until cond do 1 end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 19], [1, 0, 1, 5], [1, 16, 1, 19]])
+
+ node = ast_parse("1 until 2")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 2, 1, 7], nil])
+ end
+
+ def test_yield_locations
+ node = ast_parse("def foo; yield end")
+ assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 14], [1, 9, 1, 14], nil, nil])
+
+ node = ast_parse("def foo; yield() end")
+ assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 16], [1, 9, 1, 14], [1, 14, 1, 15], [1, 15, 1, 16]])
+
+ node = ast_parse("def foo; yield 1, 2 end")
+ assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 19], [1, 9, 1, 14], nil, nil])
+
+ node = ast_parse("def foo; yield(1, 2) end")
+ assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 20], [1, 9, 1, 14], [1, 14, 1, 15], [1, 19, 1, 20]])
+ end
+
+ private
+ def ast_parse(src, **options)
+ begin
+ verbose_bak, $VERBOSE = $VERBOSE, nil
+ RubyVM::AbstractSyntaxTree.parse(src, **options)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ end
+
+ def assert_locations(locations, expected)
+ ary = locations.map {|loc| loc && [loc.first_lineno, loc.first_column, loc.last_lineno, loc.last_column] }
+
+ assert_equal(ary, expected)
+ end
+ end
+end
diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb
index 3095052a81..82bf2d9d2c 100644
--- a/test/ruby/test_autoload.rb
+++ b/test/ruby/test_autoload.rb
@@ -42,8 +42,10 @@ p Foo::Bar
require 'tmpdir'
Dir.mktmpdir('autoload') {|tmpdir|
tmpfile = tmpdir + '/foo.rb'
+ tmpfile2 = tmpdir + '/bar.rb'
a = Module.new do
autoload :X, tmpfile
+ autoload :Y, tmpfile2
end
b = Module.new do
include a
@@ -52,10 +54,38 @@ p Foo::Bar
assert_equal(true, b.const_defined?(:X))
assert_equal(tmpfile, a.autoload?(:X), bug4565)
assert_equal(tmpfile, b.autoload?(:X), bug4565)
+ assert_equal(tmpfile, a.autoload?(:X, false))
+ assert_equal(tmpfile, a.autoload?(:X, nil))
+ assert_nil(b.autoload?(:X, false))
+ assert_nil(b.autoload?(:X, nil))
+ assert_equal(true, a.const_defined?("Y"))
+ assert_equal(true, b.const_defined?("Y"))
+ assert_equal(tmpfile2, a.autoload?("Y"))
+ assert_equal(tmpfile2, b.autoload?("Y"))
}
end
+ def test_autoload_p_with_static_extensions
+ require 'rbconfig'
+ omit unless RbConfig::CONFIG['EXTSTATIC'] == 'static'
+ begin
+ require 'fcntl.so'
+ rescue LoadError
+ omit('fcntl not included in the build')
+ end
+
+ assert_separately(['--disable-all'], <<~RUBY)
+ autoload :Fcntl, 'fcntl.so'
+
+ assert_equal('fcntl.so', autoload?(:Fcntl))
+ assert(Object.const_defined?(:Fcntl))
+ assert_equal('constant', defined?(Fcntl), '[Bug #19115]')
+ RUBY
+ end
+
def test_autoload_with_unqualified_file_name # [ruby-core:69206]
+ Object.send(:remove_const, :A) if Object.const_defined?(:A)
+
lp = $LOAD_PATH.dup
lf = $LOADED_FEATURES.dup
@@ -66,12 +96,12 @@ p Foo::Bar
eval <<-END
class ::Object
module A
- autoload :C, 'b'
+ autoload :C, 'test-ruby-core-69206'
end
end
END
- File.open('b.rb', 'w') {|file| file.puts 'module A; class C; end; end'}
+ File.write("test-ruby-core-69206.rb", 'module A; class C; end; end')
assert_kind_of Class, ::A::C
end
}
@@ -138,6 +168,7 @@ p Foo::Bar
end
def test_nameerror_when_autoload_did_not_define_the_constant
+ verbose_bak, $VERBOSE = $VERBOSE, nil
Tempfile.create(['autoload', '.rb']) {|file|
file.puts ''
file.close
@@ -150,6 +181,8 @@ p Foo::Bar
remove_autoload_constant
end
}
+ ensure
+ $VERBOSE = verbose_bak
end
def test_override_autoload
@@ -191,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
@@ -203,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
@@ -216,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
@@ -234,20 +280,25 @@ 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
end
def test_bug_13526
+ # Skip this on macOS 10.13 because of the following error:
+ # http://rubyci.s3.amazonaws.com/osx1013/ruby-master/log/20231011T014505Z.fail.html.gz
+ require "rbconfig"
+
script = File.join(__dir__, 'bug-13526.rb')
assert_ruby_status([script], '', '[ruby-core:81016] [Bug #13526]')
end
def test_autoload_private_constant
Dir.mktmpdir('autoload') do |tmpdir|
- File.write(tmpdir+"/zzz.rb", "#{<<~"begin;"}\n#{<<~'end;'}")
+ File.write(tmpdir+"/test-bug-14469.rb", "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
class AutoloadTest
ZZZ = :ZZZ
@@ -258,7 +309,7 @@ p Foo::Bar
bug = '[ruby-core:85516] [Bug #14469]'
begin;
class AutoloadTest
- autoload :ZZZ, "zzz.rb"
+ autoload :ZZZ, "test-bug-14469.rb"
end
assert_raise(NameError, bug) {AutoloadTest::ZZZ}
end;
@@ -267,7 +318,7 @@ p Foo::Bar
def test_autoload_deprecate_constant
Dir.mktmpdir('autoload') do |tmpdir|
- File.write(tmpdir+"/zzz.rb", "#{<<~"begin;"}\n#{<<~'end;'}")
+ File.write(tmpdir+"/test-bug-14469.rb", "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
class AutoloadTest
ZZZ = :ZZZ
@@ -278,7 +329,67 @@ p Foo::Bar
bug = '[ruby-core:85516] [Bug #14469]'
begin;
class AutoloadTest
- autoload :ZZZ, "zzz.rb"
+ autoload :ZZZ, "test-bug-14469.rb"
+ end
+ assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ}
+ end;
+ end
+ end
+
+ def test_autoload_private_constant_before_autoload
+ Dir.mktmpdir('autoload') do |tmpdir|
+ File.write(tmpdir+"/test-bug-11055.rb", "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class AutoloadTest
+ ZZZ = :ZZZ
+ end
+ end;
+ assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}")
+ bug = '[Bug #11055]'
+ begin;
+ class AutoloadTest
+ autoload :ZZZ, "test-bug-11055.rb"
+ private_constant :ZZZ
+ ZZZ
+ end
+ assert_raise(NameError, bug) {AutoloadTest::ZZZ}
+ end;
+ assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}")
+ bug = '[Bug #11055]'
+ begin;
+ class AutoloadTest
+ autoload :ZZZ, "test-bug-11055.rb"
+ private_constant :ZZZ
+ end
+ assert_raise(NameError, bug) {AutoloadTest::ZZZ}
+ end;
+ end
+ end
+
+ def test_autoload_deprecate_constant_before_autoload
+ Dir.mktmpdir('autoload') do |tmpdir|
+ File.write(tmpdir+"/test-bug-11055.rb", "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class AutoloadTest
+ ZZZ = :ZZZ
+ end
+ end;
+ assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}")
+ bug = '[Bug #11055]'
+ begin;
+ class AutoloadTest
+ autoload :ZZZ, "test-bug-11055.rb"
+ deprecate_constant :ZZZ
+ end
+ assert_warning(/ZZZ is deprecated/, bug) {class AutoloadTest; ZZZ; end}
+ assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ}
+ end;
+ assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}")
+ bug = '[Bug #11055]'
+ begin;
+ class AutoloadTest
+ autoload :ZZZ, "test-bug-11055.rb"
+ deprecate_constant :ZZZ
end
assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ}
end;
@@ -294,7 +405,7 @@ p Foo::Bar
begin
thrs = []
3.times do
- thrs << Thread.new { AutoloadTest; nil }
+ thrs << Thread.new { AutoloadTest && nil }
thrs << Thread.new { fork { AutoloadTest } }
end
thrs.each(&:join)
@@ -311,6 +422,122 @@ p Foo::Bar
end
end if Process.respond_to?(:fork)
+ def test_autoload_same_file
+ Dir.mktmpdir('autoload') do |tmpdir|
+ File.write("#{tmpdir}/test-bug-14742.rb", "#{<<~'begin;'}\n#{<<~'end;'}")
+ begin;
+ module Foo; end
+ module Bar; end
+ end;
+ 3.times do # timing-dependent, needs a few times to hit [Bug #14742]
+ assert_separately(%W[-I #{tmpdir}], "#{<<-'begin;'}\n#{<<-'end;'}")
+ begin;
+ autoload :Foo, 'test-bug-14742'
+ autoload :Bar, 'test-bug-14742'
+ t1 = Thread.new do Foo end
+ t2 = Thread.new do Bar end
+ t1.join
+ t2.join
+ bug = '[ruby-core:86935] [Bug #14742]'
+ assert_instance_of Module, t1.value, bug
+ assert_instance_of Module, t2.value, bug
+ end;
+ end
+ end
+ end
+
+ def test_autoload_same_file_with_raise
+ Dir.mktmpdir('autoload') do |tmpdir|
+ File.write("#{tmpdir}/test-bug-16177.rb", "#{<<~'begin;'}\n#{<<~'end;'}")
+ begin;
+ raise '[ruby-core:95055] [Bug #16177]'
+ end;
+ assert_raise(RuntimeError, '[ruby-core:95055] [Bug #16177]') do
+ assert_separately(%W[-I #{tmpdir}], "#{<<-'begin;'}\n#{<<-'end;'}")
+ begin;
+ autoload :Foo, 'test-bug-16177'
+ autoload :Bar, 'test-bug-16177'
+ t1 = Thread.new do Foo end
+ t2 = Thread.new do Bar end
+ t1.join
+ t2.join
+ end;
+ end
+ end
+ end
+
+ def test_source_location
+ bug = "Bug16764"
+ Dir.mktmpdir('autoload') do |tmpdir|
+ path = "#{tmpdir}/test-#{bug}.rb"
+ File.write(path, "C::#{bug} = __FILE__\n")
+ assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ class C; end
+ C.autoload(:Bug16764, #{path.dump})
+ assert_equal [__FILE__, __LINE__-1], C.const_source_location(#{bug.dump})
+ assert_equal #{path.dump}, C.const_get(#{bug.dump})
+ assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump})
+ end;
+ end
+ end
+
+ def test_source_location_after_require
+ bug = "Bug18624"
+ Dir.mktmpdir('autoload') do |tmpdir|
+ path = "#{tmpdir}/test-#{bug}.rb"
+ File.write(path, "C::#{bug} = __FILE__\n")
+ assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ class C; end
+ C.autoload(:Bug18624, #{path.dump})
+ require #{path.dump}
+ assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump})
+ assert_equal #{path.dump}, C.const_get(#{bug.dump})
+ assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump})
+ end;
+ end
+ end
+
+ def test_no_memory_leak
+ assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", 'many autoloads', timeout: 60)
+ begin;
+ 200000.times do |i|
+ m = Module.new
+ m.instance_eval do
+ autoload :Foo, 'x'
+ autoload :Bar, i.to_s
+ end
+ end
+ end;
+ end
+
+ def test_autoload_after_failed_and_removed_from_loaded_features
+ Dir.mktmpdir('autoload') do |tmpdir|
+ autoload_path = File.join(tmpdir, "test-bug-15790.rb")
+ File.write(autoload_path, '')
+
+ assert_separately(%W[-I #{tmpdir}], <<-RUBY)
+ $VERBOSE = nil
+ path = #{File.realpath(autoload_path).inspect}
+ autoload :X, path
+ assert_equal(path, Object.autoload?(:X))
+
+ assert_raise(NameError){X}
+ assert_nil(Object.autoload?(:X))
+ assert_equal(false, Object.const_defined?(:X))
+
+ $LOADED_FEATURES.delete(path)
+ assert_equal(false, Object.const_defined?(:X))
+ assert_nil(Object.autoload?(:X))
+
+ assert_raise(NameError){X}
+ assert_equal(false, Object.const_defined?(:X))
+ assert_nil(Object.autoload?(:X))
+ RUBY
+ end
+ end
+
def add_autoload(path)
(@autoload_paths ||= []) << path
::Object.class_eval {autoload(:AutoloadTest, path)}
@@ -318,6 +545,82 @@ p Foo::Bar
def remove_autoload_constant
$".replace($" - @autoload_paths)
- ::Object.class_eval {remove_const(:AutoloadTest)}
+ ::Object.class_eval {remove_const(:AutoloadTest)} if defined? Object::AutoloadTest
+ TestAutoload.class_eval {remove_const(:AutoloadTest)} if defined? TestAutoload::AutoloadTest
+ end
+
+ def test_autoload_module_gc
+ Dir.mktmpdir('autoload') do |tmpdir|
+ autoload_path = File.join(tmpdir, "autoload_module_gc.rb")
+ File.write(autoload_path, "X = 1; Y = 2;")
+
+ x = Module.new
+ x.autoload :X, "./feature.rb"
+
+ 1000.times do
+ y = Module.new
+ y.autoload :Y, "./feature.rb"
+ end
+
+ x = y = nil
+
+ # Ensure the internal data structures are cleaned up correctly / don't crash:
+ GC.start
+ end
+ end
+
+ def test_autoload_parallel_race
+ Dir.mktmpdir('autoload') do |tmpdir|
+ autoload_path = File.join(tmpdir, "autoload_parallel_race.rb")
+ File.write(autoload_path, 'module Foo; end; module Bar; end')
+
+ assert_ruby_status([], <<-RUBY, timeout: 100)
+ autoload_path = #{File.realpath(autoload_path).inspect}
+
+ # This should work with no errors or failures.
+ 1000.times do
+ autoload :Foo, autoload_path
+ autoload :Bar, autoload_path
+
+ t1 = Thread.new {Foo}
+ t2 = Thread.new {Bar}
+
+ t1.join
+ GC.start # force GC.
+ t2.join
+
+ Object.send(:remove_const, :Foo)
+ Object.send(:remove_const, :Bar)
+
+ $LOADED_FEATURES.delete(autoload_path)
+ end
+ RUBY
+ end
+ end
+
+ def test_autoload_parent_namespace
+ Dir.mktmpdir('autoload') do |tmpdir|
+ autoload_path = File.join(tmpdir, "some_const.rb")
+ File.write(autoload_path, 'class SomeConst; end')
+
+ assert_separately(%W[-I #{tmpdir}], <<-RUBY)
+ module SomeNamespace
+ autoload :SomeConst, #{File.realpath(autoload_path).inspect}
+ assert_warning(%r{/some_const\.rb to define SomeNamespace::SomeConst but it didn't}) do
+ assert_not_nil SomeConst
+ end
+ end
+ 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 d38628cdb2..dad7dfcb55 100644
--- a/test/ruby/test_backtrace.rb
+++ b/test/ruby/test_backtrace.rb
@@ -138,10 +138,103 @@ class TestBacktrace < Test::Unit::TestCase
rec[m]
end
+ def test_caller_with_limit
+ x = nil
+ c = Class.new do
+ define_method(:bar) do
+ x = caller(1, 1)
+ end
+ end
+ [c.new].group_by(&:bar)
+ assert_equal 1, x.length
+ assert_equal caller(0), caller(0, nil)
+ end
+
def test_caller_with_nil_length
assert_equal caller(0), caller(0, nil)
end
+ def test_each_backtrace_location
+ assert_nil(Thread.each_caller_location {})
+
+ assert_raise(LocalJumpError) {Thread.each_caller_location}
+
+ i = 0
+ cl = caller_locations(1, 1)[0]; ecl = Thread.each_caller_location{|x| i+=1; break x if i == 1}
+ assert_equal(cl.to_s, ecl.to_s)
+ assert_kind_of(Thread::Backtrace::Location, ecl)
+
+ i = 0
+ ary = []
+ cllr = caller_locations(1, 2); last = Thread.each_caller_location{|x| ary << x; i+=1; break x if i == 2}
+ assert_equal(cllr.map(&:to_s), ary.map(&:to_s))
+ assert_kind_of(Thread::Backtrace::Location, last)
+
+ i = 0
+ ary = []
+ ->{->{
+ cllr = caller_locations(1, 2); last = Thread.each_caller_location{|x| ary << x; i+=1; break x if i == 2}
+ }.()}.()
+ assert_equal(cllr.map(&:to_s), ary.map(&:to_s))
+ assert_kind_of(Thread::Backtrace::Location, last)
+
+ cllr = caller_locations(1, 2); ary = Thread.to_enum(:each_caller_location).to_a[2..3]
+ assert_equal(cllr.map(&:to_s), ary.map(&:to_s))
+
+ ecl = Thread.to_enum(:each_caller_location)
+ assert_raise(StopIteration) {
+ ecl.next
+ }
+
+ ary = []
+ cl = caller_locations(1, 2); Thread.each_caller_location(1, 2) {|x| ary << x}
+ assert_equal(cl.map(&:to_s), ary.map(&:to_s))
+ end
+
+ def test_caller_locations_first_label
+ def self.label
+ caller_locations.first.label
+ end
+
+ def self.label_caller
+ label
+ end
+
+ assert_equal 'label_caller', label_caller
+
+ [1].group_by do
+ assert_equal 'label_caller', label_caller
+ end
+ end
+
+ def test_caller_limit_cfunc_iseq_no_pc
+ def self.a; [1].group_by { b } end
+ def self.b
+ [
+ caller_locations(2, 1).first.base_label,
+ caller_locations(3, 1).first.base_label
+ ]
+ end
+ assert_equal({["each", "group_by"]=>[1]}, a)
+ end
+
+ def test_caller_location_inspect_cfunc_iseq_no_pc
+ def self.foo
+ @res = caller_locations(2, 1).inspect
+ end
+ @line = __LINE__ + 1
+ [1].map.map { [1].map.map { foo } }
+ assert_equal("[\"#{__FILE__}:#{@line}:in 'Array#map'\"]", @res)
+ end
+
+ def test_caller_location_path_cfunc_iseq_no_pc
+ def self.foo
+ @res = caller_locations(2, 1)[0].path
+ end
+ [1].map.map { [1].map.map { foo } }
+ assert_equal(__FILE__, @res)
+ end
+
def test_caller_locations
cs = caller(0); locs = caller_locations(0).map{|loc|
loc.to_s
@@ -207,13 +300,13 @@ class TestBacktrace < Test::Unit::TestCase
end
def test_caller_locations_label
- assert_equal("#{__method__}", caller_locations(0, 1)[0].label)
+ assert_equal("TestBacktrace##{__method__}", caller_locations(0, 1)[0].label)
loc, = tap {break caller_locations(0, 1)}
- assert_equal("block in #{__method__}", loc.label)
+ assert_equal("block in TestBacktrace##{__method__}", loc.label)
begin
raise
rescue
- assert_equal("rescue in #{__method__}", caller_locations(0, 1)[0].label)
+ assert_equal("TestBacktrace##{__method__}", caller_locations(0, 1)[0].label)
end
end
@@ -293,8 +386,84 @@ class TestBacktrace < Test::Unit::TestCase
def test_core_backtrace_hash_merge
e = assert_raise(TypeError) do
- {**nil}
+ {**1}
end
assert_not_match(/\Acore#/, e.backtrace_locations[0].base_label)
end
+
+ def test_notty_backtrace
+ err = ["-:1:in '<main>': unhandled exception"]
+ assert_in_out_err([], "raise", [], err)
+
+ err = ["-:2:in 'Object#foo': foo! (RuntimeError)",
+ "\tfrom -:4:in '<main>'"]
+ assert_in_out_err([], <<-"end;", [], err)
+ def foo
+ raise "foo!"
+ end
+ foo
+ end;
+
+ err = ["-:7:in 'Object#bar': bar! (RuntimeError)",
+ "\tfrom -:9:in '<main>'",
+ "-:2:in 'Object#foo': foo! (RuntimeError)",
+ "\tfrom -:5:in 'Object#bar'",
+ "\tfrom -:9:in '<main>'"]
+ assert_in_out_err([], <<-"end;", [], err)
+ def foo
+ raise "foo!"
+ end
+ def bar
+ foo
+ rescue
+ raise "bar!"
+ end
+ bar
+ end;
+ end
+
+ def test_caller_to_enum
+ err = ["-:3:in 'Object#foo': unhandled exception", "\tfrom -:in 'Enumerator#each'"]
+ assert_in_out_err([], <<-"end;", [], err, "[ruby-core:91911]")
+ def foo
+ return to_enum(__method__) unless block_given?
+ raise
+ yield 1
+ end
+
+ enum = foo
+ enum.next
+ end;
+ end
+
+ def test_no_receiver_for_anonymous_class
+ err = ["-:2:in 'bar': unhandled exception", # Not '#<Class:0xXXX>.bar'
+ "\tfrom -:3:in '<main>'"]
+ assert_in_out_err([], <<-"end;", [], err)
+ foo = Class.new
+ def foo.bar = raise
+ foo.bar
+ end;
+
+ err = ["-:3:in 'baz': unhandled exception", # Not '#<Class:0xXXX>::Bar.baz'
+ "\tfrom -:4:in '<main>'"]
+ assert_in_out_err([], <<-"end;", [], err)
+ foo = Class.new
+ foo::Bar = Class.new
+ def (foo::Bar).baz = raise
+ 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_basicinstructions.rb b/test/ruby/test_basicinstructions.rb
index dd3ca4dd22..f6b69cc1e5 100644
--- a/test/ruby/test_basicinstructions.rb
+++ b/test/ruby/test_basicinstructions.rb
@@ -117,7 +117,6 @@ class TestBasicInstructions < Test::Unit::TestCase
assert_equal({1=>2}, {1=>2})
assert_equal({1=>2, 3=>4}, {1=>2, 3=>4})
assert_equal({1=>2, 3=>4}, {3=>4, 1=>2})
- # assert_equal({1=>2, 3=>4}, {1,2, 3,4}) # 1.9 doesn't support
assert_equal({"key"=>"val"}, {"key"=>"val"})
end
@@ -429,7 +428,9 @@ class TestBasicInstructions < Test::Unit::TestCase
end
class CVarA
- @@cv = 'CVarA@@cv'
+ def self.setup
+ @@cv = 'CVarA@@cv'
+ end
def self.cv() @@cv end
def self.cv=(v) @@cv = v end
class << self
@@ -450,6 +451,7 @@ class TestBasicInstructions < Test::Unit::TestCase
end
def test_class_variable
+ CVarA.setup
assert_equal 'CVarA@@cv', CVarA.cv
assert_equal 'CVarA@@cv', CVarA.cv2
assert_equal 'CVarA@@cv', CVarA.new.cv
diff --git a/test/ruby/test_beginendblock.rb b/test/ruby/test_beginendblock.rb
index eb8394864f..3706efab52 100644
--- a/test/ruby/test_beginendblock.rb
+++ b/test/ruby/test_beginendblock.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: false
require 'test/unit'
+EnvUtil.suppress_warning {require 'continuation'}
class TestBeginEndBlock < Test::Unit::TestCase
DIR = File.dirname(File.expand_path(__FILE__))
@@ -14,6 +15,11 @@ class TestBeginEndBlock < Test::Unit::TestCase
assert_in_out_err(["-p", "-eBEGIN{p :begin}", "-eEND{p :end}"], "foo\nbar\n", %w(:begin foo bar :end))
end
+ def test_endblock_variable
+ assert_in_out_err(["-n", "-ea = :ok", "-eEND{p a}"], "foo\n", %w(:ok))
+ assert_in_out_err(["-p", "-ea = :ok", "-eEND{p a}"], "foo\n", %w(foo :ok))
+ end
+
def test_begininmethod
assert_raise_with_message(SyntaxError, /BEGIN is permitted only at toplevel/) do
eval("def foo; BEGIN {}; end")
@@ -40,9 +46,9 @@ class TestBeginEndBlock < Test::Unit::TestCase
end
def test_endblockwarn_in_eval
- assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", [], ['(eval):2: warning: END in method; use at_exit'])
+ assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", [], ['test.rb:1: warning: END in method; use at_exit'])
begin;
- eval <<-EOE
+ eval <<-EOE, nil, "test.rb", 0
def end2
END {}
end
@@ -62,7 +68,7 @@ class TestBeginEndBlock < Test::Unit::TestCase
bug8501 = '[ruby-core:55365] [Bug #8501]'
args = ['-e', 'o = Object.new; def o.inspect; raise "[Bug #8501]"; end',
'-e', 'at_exit{o.nope}']
- status = assert_in_out_err(args, '', [], /undefined method `nope'/, bug8501)
+ status = assert_in_out_err(args, '', [], /undefined method 'nope'/, bug8501)
assert_not_predicate(status, :success?, bug8501)
end
@@ -126,6 +132,8 @@ class TestBeginEndBlock < Test::Unit::TestCase
end
def test_callcc_at_exit
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
bug9110 = '[ruby-core:58329][Bug #9110]'
assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}", bug9110)
begin;
diff --git a/test/ruby/test_bignum.rb b/test/ruby/test_bignum.rb
index 65d974005e..c366f794b2 100644
--- a/test/ruby/test_bignum.rb
+++ b/test/ruby/test_bignum.rb
@@ -10,7 +10,6 @@ class TestBignum < Test::Unit::TestCase
FIXNUM_MAX = RbConfig::LIMITS['FIXNUM_MAX']
BIGNUM_MIN = FIXNUM_MAX + 1
- b = BIGNUM_MIN
f = BIGNUM_MIN
n = 0
@@ -20,23 +19,22 @@ class TestBignum < Test::Unit::TestCase
end
BIGNUM_MIN_BITS = n
- T_ZERO = 0.to_bignum
- T_ONE = 1.to_bignum
- T_MONE = (-1).to_bignum
- T31 = (2**31).to_bignum # 2147483648
- T31P = (T31 - 1).to_bignum # 2147483647
- T32 = (2**32).to_bignum # 4294967296
- T32P = (T32 - 1).to_bignum # 4294967295
- T64 = (2**64).to_bignum # 18446744073709551616
- T64P = (T64 - 1).to_bignum # 18446744073709551615
- T128 = (2**128).to_bignum
- T128P = (T128 - 1).to_bignum
- T1024 = (2**1024).to_bignum
- T1024P = (T1024 - 1).to_bignum
+ T_ZERO = Bug::Integer.to_bignum(0)
+ T_ONE = Bug::Integer.to_bignum(1)
+ T_MONE = Bug::Integer.to_bignum(-1)
+ T31 = Bug::Integer.to_bignum(2**31) # 2147483648
+ T31P = Bug::Integer.to_bignum(T31 - 1) # 2147483647
+ T32 = Bug::Integer.to_bignum(2**32) # 4294967296
+ T32P = Bug::Integer.to_bignum(T32 - 1) # 4294967295
+ T64 = Bug::Integer.to_bignum(2**64) # 18446744073709551616
+ T64P = Bug::Integer.to_bignum(T64 - 1) # 18446744073709551615
+ T128 = Bug::Integer.to_bignum(2**128)
+ T128P = Bug::Integer.to_bignum(T128 - 1)
+ T1024 = Bug::Integer.to_bignum(2**1024)
+ T1024P = Bug::Integer.to_bignum(T1024 - 1)
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
@fmax = Float::MAX.to_i
@fmax2 = @fmax * 2
@big = (1 << BIGNUM_MIN_BITS) - 1
@@ -205,6 +203,15 @@ class TestBignum < Test::Unit::TestCase
assert_equal(00_02, '00_02'.to_i)
end
+ def test_very_big_str_to_inum
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ digits = [["3", 700], ["0", 2700], ["1", 1], ["0", 26599]]
+ num = digits.inject(+"") {|s,(c,n)|s << c*n}.to_i
+ assert_equal digits.sum {|c,n|n}, num.to_s.size
+ end;
+ end
+
def test_to_s2
assert_raise(ArgumentError) { T31P.to_s(37) }
assert_equal("9" * 32768, (10**32768-1).to_s)
@@ -215,9 +222,11 @@ class TestBignum < Test::Unit::TestCase
def test_to_f
assert_nothing_raised { T31P.to_f.to_i }
- assert_raise(FloatDomainError) { (1024**1024).to_f.to_i }
- assert_equal(1, (2**50000).to_f.infinite?)
- assert_equal(-1, (-(2**50000)).to_f.infinite?)
+ assert_raise(FloatDomainError) {
+ assert_warning(/out of Float range/) {(1024**1024).to_f}.to_i
+ }
+ assert_equal(1, assert_warning(/out of Float range/) {(2**50000).to_f}.infinite?)
+ assert_equal(-1, assert_warning(/out of Float range/) {(-(2**50000)).to_f}.infinite?)
end
def test_cmp
@@ -416,7 +425,7 @@ class TestBignum < Test::Unit::TestCase
def test_divide
bug5490 = '[ruby-core:40429]'
assert_raise(ZeroDivisionError, bug5490) {T1024./(0)}
- assert_equal(Float::INFINITY, T1024./(0.0), bug5490)
+ assert_equal(Float::INFINITY, assert_warning(/out of Float range/) {T1024./(0.0)}, bug5490)
end
def test_div
@@ -467,8 +476,8 @@ class TestBignum < Test::Unit::TestCase
def test_pow
assert_equal(1.0, T32 ** 0.0)
assert_equal(1.0 / T32, T32 ** -1)
- assert_equal(1, (T32 ** T32).infinite?)
- assert_equal(1, (T32 ** (2**30-1)).infinite?)
+ assert_raise(ArgumentError) { T32 ** T32 }
+ assert_raise(ArgumentError) { T32 ** (2**30-1) }
### rational changes the behavior of Bignum#**
#assert_raise(TypeError) { T32**"foo" }
@@ -506,39 +515,57 @@ class TestBignum < Test::Unit::TestCase
end
def test_and_with_float
- assert_raise(TypeError) { T1024 & 1.5 }
+ assert_raise(TypeError) {
+ assert_warning(/out of Float range/) {T1024 & 1.5}
+ }
end
def test_and_with_rational
- assert_raise(TypeError, "#1792") { T1024 & Rational(3, 2) }
+ assert_raise(TypeError, "#1792") {
+ assert_warn(/out of Float range/) {T1024 & Rational(3, 2)}
+ }
end
def test_and_with_nonintegral_numeric
- assert_raise(TypeError, "#1792") { T1024 & DummyNumeric.new }
+ assert_raise(TypeError, "#1792") {
+ assert_warn(/out of Float range/) {T1024 & DummyNumeric.new}
+ }
end
def test_or_with_float
- assert_raise(TypeError) { T1024 | 1.5 }
+ assert_raise(TypeError) {
+ assert_warn(/out of Float range/) {T1024 | 1.5}
+ }
end
def test_or_with_rational
- assert_raise(TypeError, "#1792") { T1024 | Rational(3, 2) }
+ assert_raise(TypeError, "#1792") {
+ assert_warn(/out of Float range/) {T1024 | Rational(3, 2)}
+ }
end
def test_or_with_nonintegral_numeric
- assert_raise(TypeError, "#1792") { T1024 | DummyNumeric.new }
+ assert_raise(TypeError, "#1792") {
+ assert_warn(/out of Float range/) {T1024 | DummyNumeric.new}
+ }
end
def test_xor_with_float
- assert_raise(TypeError) { T1024 ^ 1.5 }
+ assert_raise(TypeError) {
+ assert_warn(/out of Float range/) {T1024 ^ 1.5}
+ }
end
def test_xor_with_rational
- assert_raise(TypeError, "#1792") { T1024 ^ Rational(3, 2) }
+ assert_raise(TypeError, "#1792") {
+ assert_warn(/out of Float range/) {T1024 ^ Rational(3, 2)}
+ }
end
def test_xor_with_nonintegral_numeric
- assert_raise(TypeError, "#1792") { T1024 ^ DummyNumeric.new }
+ assert_raise(TypeError, "#1792") {
+ assert_warn(/out of Float range/) {T1024 ^ DummyNumeric.new}
+ }
end
def test_shift2
@@ -578,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
@@ -612,21 +682,21 @@ class TestBignum < Test::Unit::TestCase
return # GMP doesn't support interrupt during an operation.
end
time = Time.now
- start_flag = false
end_flag = false
num = (65536 ** 65536)
+ q = Thread::Queue.new
thread = Thread.new do
- start_flag = true
assert_raise(RuntimeError) {
+ q << true
num.to_s
end_flag = true
}
end
- sleep 0.001 until start_flag
+ q.pop # sync
thread.raise
thread.join
time = Time.now - time
- skip "too fast cpu" if end_flag
+ omit "too fast cpu" if end_flag
assert_operator(time, :<, 10)
end
@@ -657,14 +727,14 @@ class TestBignum < Test::Unit::TestCase
return
end
end
- skip "cannot create suitable test case"
+ omit "cannot create suitable test case"
ensure
Signal.trap(:INT, oldtrap) if oldtrap
end
end
def test_too_big_to_s
- if (big = 2**31-1).fixnum?
+ if Bug::Integer.fixnum?(big = 2**31-1)
return
end
assert_raise_with_message(RangeError, /too big to convert/) {(1 << big).to_s}
@@ -747,10 +817,13 @@ class TestBignum < Test::Unit::TestCase
end
def test_digits
- assert_equal([90, 78, 56, 34, 12], 1234567890.to_bignum.digits(100))
+ assert_equal([90, 78, 56, 34, 12], Bug::Integer.to_bignum(1234567890).digits(100))
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
@@ -760,13 +833,13 @@ class TestBignum < Test::Unit::TestCase
end
def test_digits_for_invalid_base_numbers
- assert_raise(ArgumentError) { T1024P.to_bignum.digits(0) }
- assert_raise(ArgumentError) { T1024P.to_bignum.digits(-1) }
- assert_raise(ArgumentError) { T1024P.to_bignum.digits(0.to_bignum) }
- assert_raise(ArgumentError) { T1024P.to_bignum.digits(1.to_bignum) }
- assert_raise(ArgumentError) { T1024P.to_bignum.digits(-T1024P) }
- assert_raise(ArgumentError) { 10.digits(0.to_bignum) }
- assert_raise(ArgumentError) { 10.digits(1.to_bignum) }
+ assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(0) }
+ assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(-1) }
+ assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(Bug::Integer.to_bignum(0)) }
+ assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(Bug::Integer.to_bignum(1)) }
+ assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(-T1024P) }
+ assert_raise(ArgumentError) { 10.digits(Bug::Integer.to_bignum(0)) }
+ assert_raise(ArgumentError) { 10.digits(Bug::Integer.to_bignum(1)) }
end
def test_digits_for_non_integral_base_numbers
@@ -794,5 +867,11 @@ class TestBignum < Test::Unit::TestCase
assert_nil(T1024P.infinite?)
assert_nil((-T1024P).infinite?)
end
+
+ def test_gmp_version
+ if RbConfig::CONFIG.fetch('configure_args').include?("'--with-gmp'")
+ assert_kind_of(String, Integer::GMP_VERSION)
+ end
+ end if ENV['GITHUB_WORKFLOW'] == 'Compilations'
end
end
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 2a1b671cac..dd1936c4e2 100644
--- a/test/ruby/test_call.rb
+++ b/test/ruby/test_call.rb
@@ -1,8 +1,26 @@
# frozen_string_literal: false
require 'test/unit'
+require '-test-/iter'
class TestCall < Test::Unit::TestCase
- def aaa(a, b=100, *rest)
+ # These dummy method definitions prevent warnings "the block passed to 'a'..."
+ def a(&) = nil
+ def b(&) = nil
+ def c(&) = nil
+ def d(&) = nil
+ def e(&) = nil
+ def f(&) = nil
+ def g(&) = nil
+ def h(&) = nil
+ def i(&) = nil
+ def j(&) = nil
+ def k(&) = nil
+ def l(&) = nil
+ def m(&) = nil
+ def n(&) = nil
+ def o(&) = nil
+
+ def aaa(a, b=100, *rest, &)
res = [a, b]
res += rest if rest
return res
@@ -47,12 +65,19 @@ class TestCall < Test::Unit::TestCase
assert_equal(5, o.y)
o&.z ||= 6
assert_equal(6, o.z)
+ o&.z &&= 7
+ assert_equal(7, o.z)
o = nil
assert_nil(o&.x)
assert_nothing_raised(NoMethodError) {o&.x = raise}
+ assert_nothing_raised(NoMethodError) {o&.x = raise; nil}
assert_nothing_raised(NoMethodError) {o&.x *= raise}
assert_nothing_raised(NoMethodError) {o&.x *= raise; nil}
+ assert_nothing_raised(NoMethodError) {o&.x ||= raise}
+ assert_nothing_raised(NoMethodError) {o&.x ||= raise; nil}
+ assert_nothing_raised(NoMethodError) {o&.x &&= raise}
+ assert_nothing_raised(NoMethodError) {o&.x &&= raise; nil}
end
def test_safe_call_evaluate_arguments_only_method_call_is_made
@@ -92,11 +117,1373 @@ class TestCall < Test::Unit::TestCase
}
end
- def test_call_splat_order
+ def test_frozen_splat_and_keywords
+ a = [1, 2].freeze
+ def self.f(*a); a end
+ 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)
+ ary = [10]
+ assert_equal(10, a(*ary))
+ end
+
+ def test_call_bmethod_proc_restarg
+ pr = proc{|*sym| sym}
+ define_singleton_method(:a, &pr)
+ ary = [10]
+ assert_equal([10], a(*ary))
+ assert_equal([10], a(10))
+ end
+
+ def test_call_op_asgn_keywords
+ h = Class.new do
+ attr_reader :get, :set
+ def v; yield; [*@get, *@set] end
+ def [](*a, **b, &c) @get = [a, b, c]; @set = []; 3 end
+ def []=(*a, **b, &c) @set = [a, b, c] end
+ end.new
+
+ a = []
+ kw = {}
+ b = lambda{}
+
+ # Prevent "assigned but unused variable" warnings
+ _ = [h, a, kw, b]
+
+ message = /keyword arg given in index assignment/
+
+ # +=, without block, non-popped
+ assert_syntax_error(%q{h[**kw] += 1}, message)
+ assert_syntax_error(%q{h[0, **kw] += 1}, message)
+ assert_syntax_error(%q{h[0, *a, **kw] += 1}, message)
+ assert_syntax_error(%q{h[kw: 5] += 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] += 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] += 1}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2] += 1}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] += 1}, message)
+
+ # +=, with block, non-popped
+ assert_syntax_error(%q{h[**kw, &b] += 1}, message)
+ assert_syntax_error(%q{h[0, **kw, &b] += 1}, message)
+ assert_syntax_error(%q{h[0, *a, **kw, &b] += 1}, message)
+ assert_syntax_error(%q{h[kw: 5, &b] += 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] += 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] += 1}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] += 1}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] += 1}, message)
+
+ # +=, without block, popped
+ assert_syntax_error(%q{h[**kw] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, **kw] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, **kw] += 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5] += 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] += 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] += 1; nil}, message)
+
+ # +=, with block, popped
+ assert_syntax_error(%q{h[**kw, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, **kw, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, **kw, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] += 1; nil}, message)
+
+ # &&=, without block, non-popped
+ assert_syntax_error(%q{h[**kw] &&= 1}, message)
+ assert_syntax_error(%q{h[0, **kw] &&= 1}, message)
+ assert_syntax_error(%q{h[0, *a, **kw] &&= 1}, message)
+ assert_syntax_error(%q{h[kw: 5] &&= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2] &&= 1}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] &&= 1}, message)
+
+ # &&=, with block, non-popped
+ assert_syntax_error(%q{h[**kw, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[0, **kw, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[0, *a, **kw, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] &&= 1}, message)
+
+ # &&=, without block, popped
+ assert_syntax_error(%q{h[**kw] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, **kw] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, **kw] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] &&= 1; nil}, message)
+
+ # &&=, with block, popped
+ assert_syntax_error(%q{h[**kw, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, **kw, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, **kw, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] &&= 1; nil}, message)
+
+ # ||=, without block, non-popped
+ assert_syntax_error(%q{h[**kw] ||= 1}, message)
+ assert_syntax_error(%q{h[0, **kw] ||= 1}, message)
+ assert_syntax_error(%q{h[0, *a, **kw] ||= 1}, message)
+ assert_syntax_error(%q{h[kw: 5] ||= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2] ||= 1}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] ||= 1}, message)
+
+ # ||=, with block, non-popped
+ assert_syntax_error(%q{h[**kw, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[0, **kw, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[0, *a, **kw, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] ||= 1}, message)
+
+ # ||=, without block, popped
+ assert_syntax_error(%q{h[**kw] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, **kw] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, **kw] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] ||= 1; nil}, message)
+
+ # ||=, with block, popped
+ assert_syntax_error(%q{h[**kw, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, **kw, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, **kw, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] ||= 1; nil}, message)
+
+ end
+
+ def test_kwsplat_block_order_op_asgn
+ o = Object.new
+ ary = []
+ o.define_singleton_method(:to_a) {ary << :to_a; []}
+ o.define_singleton_method(:to_hash) {ary << :to_hash; {}}
+ o.define_singleton_method(:to_proc) {ary << :to_proc; lambda{}}
+
+ def o.[](...) 2 end
+ def o.[]=(...) end
+
+ message = /keyword arg given in index assignment/
+
+ assert_syntax_error(%q{o[kw: 1] += 1}, message)
+ assert_syntax_error(%q{o[**o] += 1}, message)
+ assert_syntax_error(%q{o[**o, &o] += 1}, message)
+ assert_syntax_error(%q{o[*o, **o, &o] += 1}, message)
+ end
+
+ def test_call_op_asgn_keywords_mutable
+ h = Class.new do
+ attr_reader :get, :set
+ def v; yield; [*@get, *@set] end
+ def [](*a, **b)
+ @get = [a.dup, b.dup]
+ a << :splat_modified
+ b[:kw_splat_modified] = true
+ @set = []
+ 3
+ end
+ def []=(*a, **b) @set = [a, b] end
+ end.new
+
+ message = /keyword arg given in index assignment/
+
+ a = []
+ kw = {}
+
+ # Prevent "assigned but unused variable" warnings
+ _ = [h, a, kw]
+
+ assert_syntax_error(%q{h[*a, 2, b: 5, **kw] += 1}, message)
+ end
+
+ def test_call_splat_post_order
bug12860 = '[ruby-core:77701] [Bug# 12860]'
ary = [1, 2]
assert_equal([1, 2, 1], aaa(*ary, ary.shift), bug12860)
ary = [1, 2]
assert_equal([0, 1, 2, 1], aaa(0, *ary, ary.shift), bug12860)
end
+
+ def test_call_splat_block_order
+ bug16504 = '[ruby-core:96769] [Bug# 16504]'
+ b = proc{}
+ ary = [1, 2, b]
+ assert_equal([1, 2, b], aaa(*ary, &ary.pop), bug16504)
+ ary = [1, 2, b]
+ assert_equal([0, 1, 2, b], aaa(0, *ary, &ary.pop), bug16504)
+ end
+
+ def test_call_splat_kw_order
+ b = {}
+ ary = [1, 2, b]
+ assert_equal([1, 2, b, {a: b}], aaa(*ary, a: ary.pop))
+ ary = [1, 2, b]
+ assert_equal([0, 1, 2, b, {a: b}], aaa(0, *ary, a: ary.pop))
+ end
+
+ def test_call_splat_kw_splat_order
+ b = {}
+ ary = [1, 2, b]
+ assert_equal([1, 2, b], aaa(*ary, **ary.pop))
+ ary = [1, 2, b]
+ assert_equal([0, 1, 2, b], aaa(0, *ary, **ary.pop))
+ end
+
+ def test_call_args_splat_with_nonhash_keyword_splat
+ o = Object.new
+ def o.to_hash; {a: 1} end
+ def self.f(*a, **kw)
+ kw
+ end
+ assert_equal Hash, f(*[], **o).class
+ end
+
+ def test_call_args_splat_with_pos_arg_kw_splat_is_not_mutable
+ o = Object.new
+ def o.foo(a, **h)= h[:splat_modified] = true
+
+ a = []
+ b = {splat_modified: false}
+
+ o.foo(*a, :x, **b)
+
+ assert_equal({splat_modified: false}, b)
+ end
+
+ def test_anon_splat
+ r2kh = Hash.ruby2_keywords_hash(kw: 2)
+ r2kea = [r2kh]
+ r2ka = [1, r2kh]
+
+ def self.s(*) ->(*a){a}.call(*) end
+ assert_equal([], s)
+ assert_equal([1], s(1))
+ assert_equal([{kw: 2}], s(kw: 2))
+ assert_equal([{kw: 2}], s(**{kw: 2}))
+ assert_equal([1, {kw: 2}], s(1, kw: 2))
+ assert_equal([1, {kw: 2}], s(1, **{kw: 2}))
+ assert_equal([{kw: 2}], s(*r2kea))
+ assert_equal([1, {kw: 2}], s(*r2ka))
+
+ singleton_class.remove_method(:s)
+ def self.s(*, kw: 0) [*->(*a){a}.call(*), kw] end
+ assert_equal([0], s)
+ assert_equal([1, 0], s(1))
+ assert_equal([2], s(kw: 2))
+ assert_equal([2], s(**{kw: 2}))
+ assert_equal([1, 2], s(1, kw: 2))
+ assert_equal([1, 2], s(1, **{kw: 2}))
+ assert_equal([2], s(*r2kea))
+ assert_equal([1, 2], s(*r2ka))
+
+ singleton_class.remove_method(:s)
+ def self.s(*, **kw) [*->(*a){a}.call(*), kw] end
+ assert_equal([{}], s)
+ assert_equal([1, {}], s(1))
+ assert_equal([{kw: 2}], s(kw: 2))
+ assert_equal([{kw: 2}], s(**{kw: 2}))
+ assert_equal([1, {kw: 2}], s(1, kw: 2))
+ assert_equal([1, {kw: 2}], s(1, **{kw: 2}))
+ assert_equal([{kw: 2}], s(*r2kea))
+ assert_equal([1, {kw: 2}], s(*r2ka))
+
+ singleton_class.remove_method(:s)
+ def self.s(*, kw: 0, **kws) [*->(*a){a}.call(*), kw, kws] end
+ assert_equal([0, {}], s)
+ assert_equal([1, 0, {}], s(1))
+ assert_equal([2, {}], s(kw: 2))
+ assert_equal([2, {}], s(**{kw: 2}))
+ assert_equal([1, 2, {}], s(1, kw: 2))
+ assert_equal([1, 2, {}], s(1, **{kw: 2}))
+ assert_equal([2, {}], s(*r2kea))
+ 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
+
+ pr = ->{}
+ h = {a: pr}
+ a = []
+
+ ary = t(**h, &h.delete(:a))
+ assert_equal([{a: pr}, pr], ary)
+
+ h = {a: pr}
+ ary = t(*a, **h, &h.delete(:a))
+ assert_equal([{a: pr}, pr], ary)
+ end
+
+ def test_kwsplat_block_order
+ o = Object.new
+ ary = []
+ o.define_singleton_method(:to_a) {ary << :to_a; []}
+ o.define_singleton_method(:to_hash) {ary << :to_hash; {}}
+ o.define_singleton_method(:to_proc) {ary << :to_proc; lambda{}}
+
+ def self.t(...) end
+
+ t(**o, &o)
+ assert_equal([:to_hash, :to_proc], ary)
+
+ ary.clear
+ t(*o, **o, &o)
+ assert_equal([:to_a, :to_hash, :to_proc], ary)
+ end
+
+ def test_kwsplat_block_order_super
+ def self.t(splat)
+ o = Object.new
+ ary = []
+ o.define_singleton_method(:to_a) {ary << :to_a; []}
+ o.define_singleton_method(:to_hash) {ary << :to_hash; {}}
+ o.define_singleton_method(:to_proc) {ary << :to_proc; lambda{}}
+ if splat
+ super(*o, **o, &o)
+ else
+ super(**o, &o)
+ end
+ ary
+ end
+ extend Module.new{def t(...) end}
+
+ assert_equal([:to_hash, :to_proc], t(false))
+ assert_equal([:to_a, :to_hash, :to_proc], t(true))
+ end
+
+ OVER_STACK_LEN = (ENV['RUBY_OVER_STACK_LEN'] || 150).to_i # Greater than VM_ARGC_STACK_MAX
+ OVER_STACK_ARGV = OVER_STACK_LEN.times.to_a.freeze
+
+ def test_call_cfunc_splat_large_array_bug_4040
+ a = OVER_STACK_ARGV
+
+ assert_equal(a, [].push(*a))
+ assert_equal(a, [].push(a[0], *a[1..]))
+ assert_equal(a, [].push(a[0], a[1], *a[2..]))
+ assert_equal(a, [].push(*a[0..1], *a[2..]))
+ assert_equal(a, [].push(*a[...-1], a[-1]))
+ assert_equal(a, [].push(a[0], *a[1...-1], a[-1]))
+ assert_equal(a, [].push(a[0], a[1], *a[2...-1], a[-1]))
+ assert_equal(a, [].push(*a[0..1], *a[2...-1], a[-1]))
+ assert_equal(a, [].push(*a[...-2], a[-2], a[-1]))
+ assert_equal(a, [].push(a[0], *a[1...-2], a[-2], a[-1]))
+ assert_equal(a, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1]))
+ assert_equal(a, [].push(*a[0..1], *a[2...-2], a[-2], a[-1]))
+
+ kw = {x: 1}
+ a_kw = a + [kw]
+
+ assert_equal(a_kw, [].push(*a, **kw))
+ assert_equal(a_kw, [].push(a[0], *a[1..], **kw))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2..], **kw))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2..], **kw))
+ assert_equal(a_kw, [].push(*a[...-1], a[-1], **kw))
+ assert_equal(a_kw, [].push(a[0], *a[1...-1], a[-1], **kw))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2...-1], a[-1], **kw))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2...-1], a[-1], **kw))
+ assert_equal(a_kw, [].push(*a[...-2], a[-2], a[-1], **kw))
+ assert_equal(a_kw, [].push(a[0], *a[1...-2], a[-2], a[-1], **kw))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1], **kw))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2...-2], a[-2], a[-1], **kw))
+
+ assert_equal(a_kw, [].push(*a, x: 1))
+ assert_equal(a_kw, [].push(a[0], *a[1..], x: 1))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2..], x: 1))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2..], x: 1))
+ assert_equal(a_kw, [].push(*a[...-1], a[-1], x: 1))
+ assert_equal(a_kw, [].push(a[0], *a[1...-1], a[-1], x: 1))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2...-1], a[-1], x: 1))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2...-1], a[-1], x: 1))
+ assert_equal(a_kw, [].push(*a[...-2], a[-2], a[-1], x: 1))
+ assert_equal(a_kw, [].push(a[0], *a[1...-2], a[-2], a[-1], x: 1))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1], x: 1))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2...-2], a[-2], a[-1], x: 1))
+
+ a_kw[-1][:y] = 2
+ kw = {y: 2}
+
+ assert_equal(a_kw, [].push(*a, x: 1, **kw))
+ assert_equal(a_kw, [].push(a[0], *a[1..], x: 1, **kw))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2..], x: 1, **kw))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2..], x: 1, **kw))
+ assert_equal(a_kw, [].push(*a[...-1], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(a[0], *a[1...-1], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2...-1], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2...-1], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(*a[...-2], a[-2], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(a[0], *a[1...-2], a[-2], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2...-2], a[-2], a[-1], x: 1, **kw))
+
+ kw = {}
+
+ assert_equal(a, [].push(*a, **kw))
+ assert_equal(a, [].push(a[0], *a[1..], **kw))
+ assert_equal(a, [].push(a[0], a[1], *a[2..], **kw))
+ assert_equal(a, [].push(*a[0..1], *a[2..], **kw))
+ assert_equal(a, [].push(*a[...-1], a[-1], **kw))
+ assert_equal(a, [].push(a[0], *a[1...-1], a[-1], **kw))
+ assert_equal(a, [].push(a[0], a[1], *a[2...-1], a[-1], **kw))
+ assert_equal(a, [].push(*a[0..1], *a[2...-1], a[-1], **kw))
+ assert_equal(a, [].push(*a[...-2], a[-2], a[-1], **kw))
+ assert_equal(a, [].push(a[0], *a[1...-2], a[-2], a[-1], **kw))
+ assert_equal(a, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1], **kw))
+ assert_equal(a, [].push(*a[0..1], *a[2...-2], a[-2], a[-1], **kw))
+
+ a_kw = a + [Hash.ruby2_keywords_hash({})]
+ assert_equal(a, [].push(*a_kw))
+
+ # Single test with value that would cause SystemStackError.
+ # Not all tests use such a large array to reduce testing time.
+ assert_equal(1380888, [].push(*1380888.times.to_a).size)
+ end
+
+ def test_call_iseq_large_array_splat_fail
+ def self.a; end
+ def self.b(a=1); end
+ def self.c(k: 1); end
+ def self.d(**kw); end
+ def self.e(k: 1, **kw); end
+ def self.f(a=1, k: 1); end
+ def self.g(a=1, **kw); end
+ def self.h(a=1, k: 1, **kw); end
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ instance_eval("#{meth}(*OVER_STACK_ARGV)", __FILE__, __LINE__)
+ end
+ end
+ end
+
+ def test_call_iseq_large_array_splat_pass
+ def self.a(*a); a.length end
+ assert_equal OVER_STACK_LEN, a(*OVER_STACK_ARGV)
+
+ def self.b(_, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), b(*OVER_STACK_ARGV)
+
+ def self.c(_, *a, _); a.length end
+ assert_equal (OVER_STACK_LEN - 2), c(*OVER_STACK_ARGV)
+
+ def self.d(b=1, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), d(*OVER_STACK_ARGV)
+
+ def self.e(b=1, *a, _); a.length end
+ assert_equal (OVER_STACK_LEN - 2), e(*OVER_STACK_ARGV)
+
+ def self.f(b, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), f(*OVER_STACK_ARGV)
+
+ def self.g(*a, k: 1); a.length end
+ assert_equal OVER_STACK_LEN, g(*OVER_STACK_ARGV)
+
+ def self.h(*a, **kw); a.length end
+ assert_equal OVER_STACK_LEN, h(*OVER_STACK_ARGV)
+
+ def self.i(*a, k: 1, **kw); a.length end
+ assert_equal OVER_STACK_LEN, i(*OVER_STACK_ARGV)
+
+ def self.j(b=1, *a, k: 1); a.length end
+ assert_equal (OVER_STACK_LEN - 1), j(*OVER_STACK_ARGV)
+
+ def self.k(b=1, *a, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 1), k(*OVER_STACK_ARGV)
+
+ def self.l(b=1, *a, k: 1, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 1), l(*OVER_STACK_ARGV)
+
+ def self.m(b=1, *a, _, k: 1); a.length end
+ assert_equal (OVER_STACK_LEN - 2), m(*OVER_STACK_ARGV)
+
+ def self.n(b=1, *a, _, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 2), n(*OVER_STACK_ARGV)
+
+ def self.o(b=1, *a, _, k: 1, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 2), o(*OVER_STACK_ARGV)
+ end
+
+ def test_call_iseq_large_array_splat_with_large_number_of_parameters
+ args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',')
+ args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',')
+
+ singleton_class.class_eval("def a(#{args}); [#{args}] end")
+ assert_equal OVER_STACK_ARGV, a(*OVER_STACK_ARGV)
+
+ singleton_class.class_eval("def b(#{args}, b=0); [#{args}, b] end")
+ assert_equal(OVER_STACK_ARGV + [0], b(*OVER_STACK_ARGV))
+
+ singleton_class.class_eval("def c(#{args}, *b); [#{args}, b] end")
+ assert_equal(OVER_STACK_ARGV + [[]], c(*OVER_STACK_ARGV))
+
+ singleton_class.class_eval("def d(#{args1}, *b); [#{args1}, b] end")
+ assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], d(*OVER_STACK_ARGV))
+ end if OVER_STACK_LEN < 200
+
+ def test_call_proc_large_array_splat_pass
+ [
+ proc{0} ,
+ proc{|a=1|a},
+ proc{|k: 1|0},
+ proc{|**kw| 0},
+ proc{|k: 1, **kw| 0},
+ proc{|a=1, k: 1| a},
+ proc{|a=1, **kw| a},
+ proc{|a=1, k: 1, **kw| a},
+ ].each do |l|
+ assert_equal 0, l.call(*OVER_STACK_ARGV)
+ end
+
+ assert_equal OVER_STACK_LEN, proc{|*a| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|_, *a| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|_, *a, _| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a| a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, proc{|*a, k: 1| a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, proc{|*a, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, proc{|*a, k: 1, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, k: 1| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, k: 1, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, k: 1| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, k: 1, **kw| a.length}.(*OVER_STACK_ARGV)
+ end
+
+ def test_call_proc_large_array_splat_with_large_number_of_parameters
+ args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',')
+ args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',')
+
+ l = instance_eval("proc{|#{args}| [#{args}]}")
+ assert_equal OVER_STACK_ARGV, l.(*OVER_STACK_ARGV)
+
+ l = instance_eval("proc{|#{args}, b| [#{args}, b]}")
+ assert_equal(OVER_STACK_ARGV + [nil], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args1}| [#{args1}]}")
+ assert_equal(OVER_STACK_ARGV[0...-1], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args}, *b| [#{args}, b]}")
+ assert_equal(OVER_STACK_ARGV + [[]], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args1}, *b| [#{args1}, b]}")
+ assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args}, b, *c| [#{args}, b, c]}")
+ assert_equal(OVER_STACK_ARGV + [nil, []], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args}, b, *c, d| [#{args}, b, c, d]}")
+ assert_equal(OVER_STACK_ARGV + [nil, [], nil], l.(*OVER_STACK_ARGV))
+ end if OVER_STACK_LEN < 200
+
+ def test_call_lambda_large_array_splat_fail
+ [
+ ->{} ,
+ ->(a=1){},
+ ->(k: 1){},
+ ->(**kw){},
+ ->(k: 1, **kw){},
+ ->(a=1, k: 1){},
+ ->(a=1, **kw){},
+ ->(a=1, k: 1, **kw){},
+ ].each do |l|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ l.call(*OVER_STACK_ARGV)
+ end
+ end
+ end
+
+ def test_call_lambda_large_array_splat_pass
+ assert_equal OVER_STACK_LEN, ->(*a){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(_, *a){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(_, *a, _){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b, *a){a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, ->(*a, k: 1){a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, ->(*a, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, ->(*a, k: 1, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, k: 1){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, k: 1, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, k: 1){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, k: 1, **kw){a.length}.(*OVER_STACK_ARGV)
+ end
+
+ def test_call_yield_block_large_array_splat_pass
+ def self.a
+ yield(*OVER_STACK_ARGV)
+ end
+
+ [
+ proc{0} ,
+ proc{|a=1|a},
+ proc{|k: 1|0},
+ proc{|**kw| 0},
+ proc{|k: 1, **kw| 0},
+ proc{|a=1, k: 1| a},
+ proc{|a=1, **kw| a},
+ proc{|a=1, k: 1, **kw| a},
+ ].each do |l|
+ assert_equal 0, a(&l)
+ end
+
+ assert_equal OVER_STACK_LEN, a{|*a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|_, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|_, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b, *a| a.length}
+ assert_equal OVER_STACK_LEN, a{|*a, k: 1| a.length}
+ assert_equal OVER_STACK_LEN, a{|*a, **kw| a.length}
+ assert_equal OVER_STACK_LEN, a{|*a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, k: 1, **kw| a.length}
+ end
+
+ def test_call_yield_large_array_splat_with_large_number_of_parameters
+ def self.a
+ yield(*OVER_STACK_ARGV)
+ end
+
+ args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',')
+ args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',')
+
+ assert_equal OVER_STACK_ARGV, instance_eval("a{|#{args}| [#{args}]}", __FILE__, __LINE__)
+ assert_equal(OVER_STACK_ARGV + [nil], instance_eval("a{|#{args}, b| [#{args}, b]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV[0...-1], instance_eval("a{|#{args1}| [#{args1}]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV + [[]], instance_eval("a{|#{args}, *b| [#{args}, b]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], instance_eval("a{|#{args1}, *b| [#{args1}, b]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV + [nil, []], instance_eval("a{|#{args}, b, *c| [#{args}, b, c]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV + [nil, [], nil], instance_eval("a{|#{args}, b, *c, d| [#{args}, b, c, d]}", __FILE__, __LINE__))
+ end if OVER_STACK_LEN < 200
+
+ def test_call_yield_lambda_large_array_splat_fail
+ def self.a
+ yield(*OVER_STACK_ARGV)
+ end
+ [
+ ->{} ,
+ ->(a=1){},
+ ->(k: 1){},
+ ->(**kw){},
+ ->(k: 1, **kw){},
+ ->(a=1, k: 1){},
+ ->(a=1, **kw){},
+ ->(a=1, k: 1, **kw){},
+ ].each do |l|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ a(&l)
+ end
+ end
+ end
+
+ def test_call_yield_lambda_large_array_splat_pass
+ def self.a
+ yield(*OVER_STACK_ARGV)
+ end
+
+ assert_equal OVER_STACK_LEN, a(&->(*a){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(_, *a){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(_, *a, _){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b, *a){a.length})
+ assert_equal OVER_STACK_LEN, a(&->(*a, k: 1){a.length})
+ assert_equal OVER_STACK_LEN, a(&->(*a, **kw){a.length})
+ assert_equal OVER_STACK_LEN, a(&->(*a, k: 1, **kw){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, k: 1){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, **kw){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, k: 1, **kw){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, k: 1){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, **kw){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, k: 1, **kw){a.length})
+ end
+
+ def test_call_send_iseq_large_array_splat_fail
+ def self.a; end
+ def self.b(a=1); end
+ def self.c(k: 1); end
+ def self.d(**kw); end
+ def self.e(k: 1, **kw); end
+ def self.f(a=1, k: 1); end
+ def self.g(a=1, **kw); end
+ def self.h(a=1, k: 1, **kw); end
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ send(meth, *OVER_STACK_ARGV)
+ end
+ end
+ end
+
+ def test_call_send_iseq_large_array_splat_pass
+ def self.a(*a); a.length end
+ assert_equal OVER_STACK_LEN, send(:a, *OVER_STACK_ARGV)
+
+ def self.b(_, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:b, *OVER_STACK_ARGV)
+
+ def self.c(_, *a, _); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:c, *OVER_STACK_ARGV)
+
+ def self.d(b=1, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:d, *OVER_STACK_ARGV)
+
+ def self.e(b=1, *a, _); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:e, *OVER_STACK_ARGV)
+
+ def self.f(b, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:f, *OVER_STACK_ARGV)
+
+ def self.g(*a, k: 1); a.length end
+ assert_equal OVER_STACK_LEN, send(:g, *OVER_STACK_ARGV)
+
+ def self.h(*a, **kw); a.length end
+ assert_equal OVER_STACK_LEN, send(:h, *OVER_STACK_ARGV)
+
+ def self.i(*a, k: 1, **kw); a.length end
+ assert_equal OVER_STACK_LEN, send(:i, *OVER_STACK_ARGV)
+
+ def self.j(b=1, *a, k: 1); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:j, *OVER_STACK_ARGV)
+
+ def self.k(b=1, *a, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:k, *OVER_STACK_ARGV)
+
+ def self.l(b=1, *a, k: 1, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:l, *OVER_STACK_ARGV)
+
+ def self.m(b=1, *a, _, k: 1); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:m, *OVER_STACK_ARGV)
+
+ def self.n(b=1, *a, _, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:n, *OVER_STACK_ARGV)
+
+ def self.o(b=1, *a, _, k: 1, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:o, *OVER_STACK_ARGV)
+ end
+
+ def test_call_send_iseq_large_array_splat_with_large_number_of_parameters
+ args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',')
+ args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',')
+
+ singleton_class.class_eval("def a(#{args}); [#{args}] end")
+ assert_equal OVER_STACK_ARGV, send(:a, *OVER_STACK_ARGV)
+
+ singleton_class.class_eval("def b(#{args}, b=0); [#{args}, b] end")
+ assert_equal(OVER_STACK_ARGV + [0], send(:b, *OVER_STACK_ARGV))
+
+ singleton_class.class_eval("def c(#{args}, *b); [#{args}, b] end")
+ assert_equal(OVER_STACK_ARGV + [[]], send(:c, *OVER_STACK_ARGV))
+
+ singleton_class.class_eval("def d(#{args1}, *b); [#{args1}, b] end")
+ assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], send(:d, *OVER_STACK_ARGV))
+ end if OVER_STACK_LEN < 200
+
+ def test_call_send_cfunc_large_array_splat_fail
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ send(:object_id, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_send_cfunc_large_array_splat_pass
+ assert_equal OVER_STACK_LEN, [].send(:push, *OVER_STACK_ARGV).length
+ end
+
+ def test_call_attr_reader_large_array_splat_fail
+ singleton_class.send(:attr_reader, :a)
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ a(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ send(:a, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_attr_writer_large_array_splat_fail
+ singleton_class.send(:attr_writer, :a)
+ singleton_class.send(:alias_method, :a, :a=)
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ a(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ send(:a, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_struct_aref_large_array_splat_fail
+ s = Struct.new(:a).new
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ s.a(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ s.send(:a, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_struct_aset_large_array_splat_fail
+ s = Struct.new(:a) do
+ alias b a=
+ end.new
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ s.b(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ s.send(:b, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_alias_large_array_splat
+ c = Class.new do
+ def a; end
+ def c(*a); a.length end
+ attr_accessor :e
+ end
+ sc = Class.new(c) do
+ alias b a
+ alias d c
+ alias f e
+ alias g e=
+ end
+
+ obj = sc.new
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ obj.b(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ obj.f(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ obj.g(*OVER_STACK_ARGV)
+ end
+
+ assert_equal OVER_STACK_LEN, obj.d(*OVER_STACK_ARGV)
+ end
+
+ def test_call_zsuper_large_array_splat
+ c = Class.new do
+ private
+ def a; end
+ def c(*a); a.length end
+ attr_reader :e
+ end
+ sc = Class.new(c) do
+ public :a
+ public :c
+ public :e
+ end
+
+ obj = sc.new
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ obj.a(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ obj.e(*OVER_STACK_ARGV)
+ end
+
+ assert_equal OVER_STACK_LEN, obj.c(*OVER_STACK_ARGV)
+ end
+
+ class RefinedModuleLargeArrayTest
+ c = self
+ using(Module.new do
+ refine c do
+ def a; end
+ def c(*a) a.length end
+ attr_reader :e
+ end
+ end)
+
+ def b
+ a(*OVER_STACK_ARGV)
+ end
+
+ def d
+ c(*OVER_STACK_ARGV)
+ end
+
+ def f
+ e(*OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_refined_large_array_splat_fail
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ RefinedModuleLargeArrayTest.new.b
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ RefinedModuleLargeArrayTest.new.f
+ end
+ end
+
+ def test_call_refined_large_array_splat_pass
+ assert_equal OVER_STACK_LEN, RefinedModuleLargeArrayTest.new.d
+ end
+
+ def test_call_method_missing_iseq_large_array_splat_fail
+ def self.method_missing(_) end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ nonexistent_method(*OVER_STACK_ARGV)
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ send(:nonexistent_method, *OVER_STACK_ARGV)
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ send("nonexistent_method123", *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_method_missing_iseq_large_array_splat_pass
+ def self.method_missing(m, *a)
+ a.length
+ end
+ assert_equal OVER_STACK_LEN, nonexistent_method(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, send(:nonexistent_method, *OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, send("nonexistent_method123", *OVER_STACK_ARGV)
+ end
+
+ def test_call_bmethod_large_array_splat_fail
+ define_singleton_method(:a){}
+ define_singleton_method(:b){|a=1|}
+ define_singleton_method(:c){|k: 1|}
+ define_singleton_method(:d){|**kw|}
+ define_singleton_method(:e){|k: 1, **kw|}
+ define_singleton_method(:f){|a=1, k: 1|}
+ define_singleton_method(:g){|a=1, **kw|}
+ define_singleton_method(:h){|a=1, k: 1, **kw|}
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ instance_eval("#{meth}(*OVER_STACK_ARGV)", __FILE__, __LINE__)
+ end
+ end
+ end
+
+ def test_call_bmethod_large_array_splat_pass
+ define_singleton_method(:a){|*a| a.length}
+ assert_equal OVER_STACK_LEN, a(*OVER_STACK_ARGV)
+
+ define_singleton_method(:b){|_, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), b(*OVER_STACK_ARGV)
+
+ define_singleton_method(:c){|_, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), c(*OVER_STACK_ARGV)
+
+ define_singleton_method(:d){|b=1, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), d(*OVER_STACK_ARGV)
+
+ define_singleton_method(:e){|b=1, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), e(*OVER_STACK_ARGV)
+
+ define_singleton_method(:f){|b, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), f(*OVER_STACK_ARGV)
+
+ define_singleton_method(:g){|*a, k: 1| a.length}
+ assert_equal OVER_STACK_LEN, g(*OVER_STACK_ARGV)
+
+ define_singleton_method(:h){|*a, **kw| a.length}
+ assert_equal OVER_STACK_LEN, h(*OVER_STACK_ARGV)
+
+ define_singleton_method(:i){|*a, k: 1, **kw| a.length}
+ assert_equal OVER_STACK_LEN, i(*OVER_STACK_ARGV)
+
+ define_singleton_method(:j){|b=1, *a, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 1), j(*OVER_STACK_ARGV)
+
+ define_singleton_method(:k){|b=1, *a, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), k(*OVER_STACK_ARGV)
+
+ define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), l(*OVER_STACK_ARGV)
+
+ define_singleton_method(:m){|b=1, *a, _, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 2), m(*OVER_STACK_ARGV)
+
+ define_singleton_method(:n){|b=1, *a, _, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), n(*OVER_STACK_ARGV)
+
+ define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), o(*OVER_STACK_ARGV)
+ end
+
+ def test_call_method_missing_bmethod_large_array_splat_fail
+ define_singleton_method(:method_missing){|_|}
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ nonexistent_method(*OVER_STACK_ARGV)
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ send(:nonexistent_method, *OVER_STACK_ARGV)
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ send("nonexistent_method123", *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_method_missing_bmethod_large_array_splat_pass
+ define_singleton_method(:method_missing){|_, *a| a.length}
+ assert_equal OVER_STACK_LEN, nonexistent_method(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, send(:nonexistent_method, *OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, send("nonexistent_method123", *OVER_STACK_ARGV)
+ end
+
+ def test_call_symproc_large_array_splat_fail
+ define_singleton_method(:a){}
+ define_singleton_method(:b){|a=1|}
+ define_singleton_method(:c){|k: 1|}
+ define_singleton_method(:d){|**kw|}
+ define_singleton_method(:e){|k: 1, **kw|}
+ define_singleton_method(:f){|a=1, k: 1|}
+ define_singleton_method(:g){|a=1, **kw|}
+ define_singleton_method(:h){|a=1, k: 1, **kw|}
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ instance_eval(":#{meth}.to_proc.(self, *OVER_STACK_ARGV)", __FILE__, __LINE__)
+ end
+ end
+ end
+
+ def test_call_symproc_large_array_splat_pass
+ define_singleton_method(:a){|*a| a.length}
+ assert_equal OVER_STACK_LEN, :a.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:b){|_, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :b.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:c){|_, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :c.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:d){|b=1, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :d.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:e){|b=1, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :e.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:f){|b, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :f.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:g){|*a, k: 1| a.length}
+ assert_equal OVER_STACK_LEN, :g.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:h){|*a, **kw| a.length}
+ assert_equal OVER_STACK_LEN, :h.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:i){|*a, k: 1, **kw| a.length}
+ assert_equal OVER_STACK_LEN, :i.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:j){|b=1, *a, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :j.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:k){|b=1, *a, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :k.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :l.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:m){|b=1, *a, _, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :m.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:n){|b=1, *a, _, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :n.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :o.to_proc.(self, *OVER_STACK_ARGV)
+ end
+
+ def test_call_rb_call_iseq_large_array_splat_fail
+ extend Bug::Iter::Yield
+ l = ->(*a){}
+
+ def self.a; end
+ def self.b(a=1) end
+ def self.c(k: 1) end
+ def self.d(**kw) end
+ def self.e(k: 1, **kw) end
+ def self.f(a=1, k: 1) end
+ def self.g(a=1, **kw) end
+ def self.h(a=1, k: 1, **kw) end
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ yield_block(meth, *OVER_STACK_ARGV, &l)
+ end
+ end
+ end
+
+ def test_call_rb_call_iseq_large_array_splat_pass
+ extend Bug::Iter::Yield
+ l = ->(*a){a.length}
+
+ def self.a(*a) a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ def self.b(_, *a) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:b, *OVER_STACK_ARGV, &l)
+
+ def self.c(_, *a, _) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:c, *OVER_STACK_ARGV, &l)
+
+ def self.d(b=1, *a) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:d, *OVER_STACK_ARGV, &l)
+
+ def self.e(b=1, *a, _) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:e, *OVER_STACK_ARGV, &l)
+
+ def self.f(b, *a) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:f, *OVER_STACK_ARGV, &l)
+
+ def self.g(*a, k: 1) a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:g, *OVER_STACK_ARGV, &l)
+
+ def self.h(*a, **kw) a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l)
+
+ def self.i(*a, k: 1, **kw) a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l)
+
+ def self.j(b=1, *a, k: 1) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:j, *OVER_STACK_ARGV, &l)
+
+ def self.k(b=1, *a, **kw) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:k, *OVER_STACK_ARGV, &l)
+
+ def self.l(b=1, *a, k: 1, **kw) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:l, *OVER_STACK_ARGV, &l)
+
+ def self.m(b=1, *a, _, k: 1) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:m, *OVER_STACK_ARGV, &l)
+
+ def self.n(b=1, *a, _, **kw) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:n, *OVER_STACK_ARGV, &l)
+
+ def self.o(b=1, *a, _, k: 1, **kw) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:o, *OVER_STACK_ARGV, &l)
+ end
+
+ def test_call_rb_call_bmethod_large_array_splat_fail
+ extend Bug::Iter::Yield
+ l = ->(*a){}
+
+ define_singleton_method(:a){||}
+ define_singleton_method(:b){|a=1|}
+ define_singleton_method(:c){|k: 1|}
+ define_singleton_method(:d){|**kw|}
+ define_singleton_method(:e){|k: 1, **kw|}
+ define_singleton_method(:f){|a=1, k: 1|}
+ define_singleton_method(:g){|a=1, **kw|}
+ define_singleton_method(:h){|a=1, k: 1, **kw|}
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ yield_block(meth, *OVER_STACK_ARGV, &l)
+ end
+ end
+ end
+
+ def test_call_rb_call_bmethod_large_array_splat_pass
+ extend Bug::Iter::Yield
+ l = ->(*a){a.length}
+
+ define_singleton_method(:a){|*a| a.length}
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:b){|_, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:b, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:c){|_, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:c, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:d){|b=1, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:d, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:e){|b=1, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:e, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:f){|b, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:f, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:g){|*a, k: 1| a.length}
+ assert_equal OVER_STACK_LEN, yield_block(:g, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:h){|*a, **kw| a.length}
+ assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:i){|*a, k: 1, **kw| a.length}
+ assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:j){|b=1, *a, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:j, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:k){|b=1, *a, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:k, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:l, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:m){|b=1, *a, _, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:m, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:n){|b=1, *a, _, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:n, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:o, *OVER_STACK_ARGV, &l)
+ end
+
+ def test_call_ifunc_iseq_large_array_splat_fail
+ extend Bug::Iter::Yield
+ def self.a(*a)
+ yield(*a)
+ end
+ [
+ ->(){},
+ ->(a=1){},
+ ->(k: 1){},
+ ->(**kw){},
+ ->(k: 1, **kw){},
+ ->(a=1, k: 1){},
+ ->(a=1, **kw){},
+ ->(a=1, k: 1, **kw){},
+ ].each do |l|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ yield_block(:a, *OVER_STACK_ARGV, &l)
+ end
+ end
+ end
+
+ def test_call_ifunc_iseq_large_array_splat_pass
+ extend Bug::Iter::Yield
+ def self.a(*a)
+ yield(*a)
+ end
+
+ l = ->(*a) do a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(_, *a) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(_, *a, _) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, _) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b, *a) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(*a, k: 1) do a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(*a, **kw) do a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(*a, k: 1, **kw) do a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, k: 1) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, **kw) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, k: 1, **kw) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, _, k: 1) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, _, **kw) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, _, k: 1, **kw) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+ end
end
diff --git a/test/ruby/test_case.rb b/test/ruby/test_case.rb
index 77612a8945..9e8502fb27 100644
--- a/test/ruby/test_case.rb
+++ b/test/ruby/test_case.rb
@@ -68,10 +68,13 @@ class TestCase < Test::Unit::TestCase
assert(false)
end
- assert_raise(NameError) do
- case
- when false, *x, false
+ begin
+ verbose_bak, $VERBOSE = $VERBOSE, nil
+ assert_raise(NameError) do
+ eval("case; when false, *x, false; end")
end
+ ensure
+ $VERBOSE = verbose_bak
end
end
@@ -102,6 +105,18 @@ class TestCase < Test::Unit::TestCase
else
assert(false)
end
+ case 0
+ when 0r
+ assert(true)
+ else
+ assert(false)
+ end
+ case 0
+ when 0i
+ assert(true)
+ else
+ assert(false)
+ end
end
def test_method_missing
diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb
index ad2caeb8be..8f12e06685 100644
--- a/test/ruby/test_class.rb
+++ b/test/ruby/test_class.rb
@@ -96,6 +96,13 @@ class TestClass < Test::Unit::TestCase
def test_superclass_of_basicobject
assert_equal(nil, BasicObject.superclass)
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ module Mod end
+ BasicObject.include(Mod)
+ assert_equal(nil, BasicObject.superclass)
+ end;
end
def test_module_function
@@ -131,6 +138,48 @@ class TestClass < Test::Unit::TestCase
[:module_function, :extend_object, :append_features, :prepend_features])
end
+ def test_visibility_inside_method
+ assert_warn(/calling private without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do
+ Class.new do
+ def self.foo
+ private
+ end
+ foo
+ end
+ end
+
+ assert_warn(/calling protected without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do
+ Class.new do
+ def self.foo
+ protected
+ end
+ foo
+ end
+ end
+
+ assert_warn(/calling public without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do
+ Class.new do
+ def self.foo
+ public
+ end
+ foo
+ end
+ end
+
+ assert_warn(/calling private without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do
+ Class.new do
+ class << self
+ alias priv private
+ end
+
+ def self.foo
+ priv
+ end
+ foo
+ end
+ end
+ end
+
def test_method_redefinition
feature2155 = '[ruby-dev:39400]'
@@ -210,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) }
@@ -232,6 +321,10 @@ class TestClass < Test::Unit::TestCase
assert_raise(TypeError) { Class.allocate.superclass }
bug6863 = '[ruby-core:47148]'
assert_raise(TypeError, bug6863) { Class.new(Class.allocate) }
+
+ allocator = Class.instance_method(:allocate)
+ assert_nothing_raised { allocator.bind(Rational).call }
+ assert_nothing_raised { allocator.bind_call(Rational) }
end
def test_nonascii_name
@@ -242,27 +335,30 @@ class TestClass < Test::Unit::TestCase
end
def test_invalid_next_from_class_definition
- assert_raise(SyntaxError) { eval("class C; next; end") }
+ assert_syntax_error("class C; next; end", /Invalid next/)
end
def test_invalid_break_from_class_definition
- assert_raise(SyntaxError) { eval("class C; break; end") }
+ assert_syntax_error("class C; break; end", /Invalid break/)
end
def test_invalid_redo_from_class_definition
- assert_raise(SyntaxError) { eval("class C; redo; end") }
+ assert_syntax_error("class C; redo; end", /Invalid redo/)
end
def test_invalid_retry_from_class_definition
- assert_raise(SyntaxError) { eval("class C; retry; end") }
+ assert_syntax_error("class C; retry; end", /Invalid retry/)
end
def test_invalid_return_from_class_definition
- assert_raise(SyntaxError) { eval("class C; return; end") }
+ assert_syntax_error("class C; return; end", /Invalid return/)
+ assert_syntax_error("class << Object; return; end", /Invalid return/)
end
def test_invalid_yield_from_class_definition
- assert_raise(LocalJumpError) { eval("class C; yield; end") }
+ assert_raise(SyntaxError) {
+ EnvUtil.suppress_warning {eval("class C; yield; end")}
+ }
end
def test_clone
@@ -303,6 +399,17 @@ class TestClass < Test::Unit::TestCase
assert_equal(42, PrivateClass.new.foo)
end
+ def test_private_const_access
+ assert_raise_with_message NameError, /uninitialized/ do
+ begin
+ eval('class ::TestClass::PrivateClass; end')
+ rescue NameError
+ end
+
+ Object.const_get "NOT_AVAILABLE_CONST_NAME_#{__LINE__}"
+ end
+ end
+
StrClone = String.clone
Class.new(StrClone)
@@ -327,18 +434,22 @@ class TestClass < Test::Unit::TestCase
end;
end
- module M
- C = 1
+ class CloneTest
+ def foo; TEST; end
+ end
- def self.m
- C
- end
+ CloneTest1 = CloneTest.clone
+ CloneTest2 = CloneTest.clone
+ class CloneTest1
+ TEST = :C1
+ end
+ class CloneTest2
+ TEST = :C2
end
- def test_constant_access_from_method_in_cloned_module # [ruby-core:47834]
- m = M.dup
- assert_equal 1, m::C
- assert_equal 1, m.m
+ def test_constant_access_from_method_in_cloned_class
+ assert_equal :C1, CloneTest1.new.foo, '[Bug #15877]'
+ assert_equal :C2, CloneTest2.new.foo, '[Bug #15877]'
end
def test_invalid_superclass
@@ -427,6 +538,53 @@ class TestClass < Test::Unit::TestCase
assert_equal(:foo, d.foo)
end
+ def test_clone_singleton_class_exists
+ klass = Class.new do
+ def self.bar; :bar; end
+ end
+
+ o = klass.new
+ o.singleton_class
+ clone = o.clone
+
+ assert_empty(o.singleton_class.instance_methods(false))
+ assert_empty(clone.singleton_class.instance_methods(false))
+ assert_empty(o.singleton_class.singleton_class.instance_methods(false))
+ assert_empty(clone.singleton_class.singleton_class.instance_methods(false))
+ end
+
+ def test_clone_when_singleton_class_of_singleton_class_exists
+ klass = Class.new do
+ def self.bar; :bar; end
+ end
+
+ o = klass.new
+ o.singleton_class.singleton_class
+ clone = o.clone
+
+ assert_empty(o.singleton_class.instance_methods(false))
+ assert_empty(clone.singleton_class.instance_methods(false))
+ assert_empty(o.singleton_class.singleton_class.instance_methods(false))
+ assert_empty(clone.singleton_class.singleton_class.instance_methods(false))
+ end
+
+ def test_clone_when_method_exists_on_singleton_class_of_singleton_class
+ klass = Class.new do
+ def self.bar; :bar; end
+ end
+
+ o = klass.new
+ o.singleton_class.singleton_class.define_method(:s2_method) { :s2 }
+ clone = o.clone
+
+ assert_empty(o.singleton_class.instance_methods(false))
+ assert_empty(clone.singleton_class.instance_methods(false))
+ assert_equal(:s2, o.singleton_class.s2_method)
+ assert_equal(:s2, clone.singleton_class.s2_method)
+ assert_equal([:s2_method], o.singleton_class.singleton_class.instance_methods(false))
+ assert_equal([:s2_method], clone.singleton_class.singleton_class.instance_methods(false))
+ end
+
def test_singleton_class_p
feature7609 = '[ruby-core:51087] [Feature #7609]'
assert_predicate(self.singleton_class, :singleton_class?, feature7609)
@@ -443,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
@@ -578,28 +736,36 @@ 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
m = Module.new
- m.module_eval "A = 1"
- assert_raise_with_message(TypeError, /is not a class/) {
+ m.module_eval "A = 1", __FILE__, line = __LINE__
+ e = assert_raise_with_message(TypeError, /is not a class/) {
m.module_eval "class A; end"
}
+ assert_include(e.message, "#{__FILE__}:#{line}: previous definition")
n = "M\u{1f5ff}"
- m.module_eval "#{n} = 42"
- assert_raise_with_message(TypeError, "#{n} is not a class") {
+ m.module_eval "#{n} = 42", __FILE__, line = __LINE__
+ e = assert_raise_with_message(TypeError, /#{n} is not a class/) {
m.module_eval "class #{n}; end"
}
+ assert_include(e.message, "#{__FILE__}:#{line}: previous definition")
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}")
begin;
- Date = (class C\u{1f5ff}; self; end).new
+ module Bug
+ module Class
+ TestClassDefinedInC = (class C\u{1f5ff}; self; end).new
+ end
+ end
assert_raise_with_message(TypeError, /C\u{1f5ff}/) {
- require 'date'
+ require '-test-/class'
}
end;
end
@@ -625,4 +791,158 @@ class TestClass < Test::Unit::TestCase
end;
end
+
+ def test_assign_frozen_class_to_const
+ c = Class.new.freeze
+ assert_same(c, Module.new.module_eval("self::Foo = c"))
+ c = Class.new.freeze
+ assert_same(c, Module.new.const_set(:Foo, c))
+ end
+
+ def test_subclasses
+ c = Class.new
+ sc = Class.new(c)
+ ssc = Class.new(sc)
+ [c, sc, ssc].each do |k|
+ k.include Module.new
+ k.new.define_singleton_method(:force_singleton_class){}
+ end
+ assert_equal([sc], c.subclasses)
+ assert_equal([ssc], sc.subclasses)
+ assert_equal([], ssc.subclasses)
+
+ object_subclasses = Object.subclasses
+ assert_include(object_subclasses, c)
+ assert_not_include(object_subclasses, sc)
+ assert_not_include(object_subclasses, ssc)
+ object_subclasses.each do |subclass|
+ assert_equal Object, subclass.superclass, "Expected #{subclass}.superclass to be Object"
+ end
+ end
+
+ def test_attached_object
+ c = Class.new
+ sc = c.singleton_class
+ obj = c.new
+
+ assert_equal(obj, obj.singleton_class.attached_object)
+ assert_equal(c, sc.attached_object)
+
+ assert_raise_with_message(TypeError, /is not a singleton class/) do
+ c.attached_object
+ end
+
+ assert_raise_with_message(TypeError, /'NilClass' is not a singleton class/) do
+ nil.singleton_class.attached_object
+ end
+
+ assert_raise_with_message(TypeError, /'FalseClass' is not a singleton class/) do
+ false.singleton_class.attached_object
+ end
+
+ assert_raise_with_message(TypeError, /'TrueClass' is not a singleton class/) do
+ true.singleton_class.attached_object
+ end
+ end
+
+ def test_subclass_gc
+ c = Class.new
+ 10_000.times do
+ cc = Class.new(c)
+ 100.times { Class.new(cc) }
+ end
+ assert(c.subclasses.size <= 10_000)
+ end
+
+ def test_subclass_gc_stress
+ 10000.times do
+ c = Class.new
+ 100.times { Class.new(c) }
+ assert(c.subclasses.size <= 100)
+ end
+ end
+
+ def test_classext_memory_leak
+ assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true)
+code = proc { Class.new }
+1_000.times(&code)
+PREP
+3_000_000.times(&code)
+CODE
+ end
+
+ def test_instance_freeze_dont_freeze_the_class_bug_19164
+ klass = Class.new
+ klass.prepend(Module.new)
+
+ klass.new.freeze
+ 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_clone.rb b/test/ruby/test_clone.rb
index 93ef438461..775c9ed848 100644
--- a/test/ruby/test_clone.rb
+++ b/test/ruby/test_clone.rb
@@ -26,4 +26,92 @@ class TestClone < Test::Unit::TestCase
assert_equal([M003, M002, M001], M003.ancestors)
end
+
+ def test_frozen_properties_retained_on_clone
+ obj = Object.new.freeze
+ cloned_obj = obj.clone
+
+ assert_predicate(obj, :frozen?)
+ assert_predicate(cloned_obj, :frozen?)
+ end
+
+ def test_ivar_retained_on_clone
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ cloned_obj = obj.clone
+
+ assert_equal(obj.instance_variable_get(:@a), 1)
+ assert_equal(cloned_obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_ivars_retained_on_extended_obj_clone
+ ivars = { :@a => 1, :@b => 2, :@c => 3, :@d => 4 }
+ obj = Object.new
+ ivars.each do |ivar_name, val|
+ obj.instance_variable_set(ivar_name, val)
+ end
+
+ cloned_obj = obj.clone
+
+ ivars.each do |ivar_name, val|
+ assert_equal(obj.instance_variable_get(ivar_name), val)
+ assert_equal(cloned_obj.instance_variable_get(ivar_name), val)
+ end
+ end
+
+ def test_frozen_properties_and_ivars_retained_on_clone_with_ivar
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ obj.freeze
+
+ cloned_obj = obj.clone
+
+ assert_predicate(obj, :frozen?)
+ assert_equal(obj.instance_variable_get(:@a), 1)
+
+ assert_predicate(cloned_obj, :frozen?)
+ assert_equal(cloned_obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_proc_obj_id_flag_reset
+ # [Bug #20250]
+ proc = Proc.new { }
+ proc.object_id
+ proc.clone.object_id # Would crash with RUBY_DEBUG=1
+ end
+
+ def test_user_flags
+ assert_separately([], <<-EOS)
+ #
+ class Array
+ undef initialize_copy
+ def initialize_copy(*); end
+ end
+ x = [1, 2, 3].clone
+ assert_equal [], x, '[Bug #14847]'
+ EOS
+
+ assert_separately([], <<-EOS)
+ #
+ class Array
+ undef initialize_copy
+ def initialize_copy(*); end
+ end
+ x = [1,2,3,4,5,6,7][1..-2].clone
+ x.push(1,1,1,1,1)
+ assert_equal [1, 1, 1, 1, 1], x, '[Bug #14847]'
+ EOS
+
+ assert_separately([], <<-EOS)
+ #
+ class Hash
+ undef initialize_copy
+ def initialize_copy(*); end
+ end
+ h = {}
+ h.default_proc = proc { raise }
+ h = h.clone
+ assert_equal nil, h[:not_exist], '[Bug #14847]'
+ EOS
+ end
end
diff --git a/test/ruby/test_comparable.rb b/test/ruby/test_comparable.rb
index 94c05d5f91..b689469d9e 100644
--- a/test/ruby/test_comparable.rb
+++ b/test/ruby/test_comparable.rb
@@ -85,10 +85,46 @@ class TestComparable < Test::Unit::TestCase
assert_equal(1, @o.clamp(1, 1))
assert_equal(@o, @o.clamp(0, 0))
- assert_raise_with_message(ArgumentError, 'min argument must be smaller than max argument') {
+ assert_equal(@o, @o.clamp(nil, 2))
+ assert_equal(-2, @o.clamp(nil, -2))
+ assert_equal(@o, @o.clamp(-2, nil))
+ assert_equal(2, @o.clamp(2, nil))
+ assert_equal(@o, @o.clamp(nil, nil))
+
+ assert_raise_with_message(ArgumentError, 'min argument must be less than or equal to max argument') {
@o.clamp(2, 1)
}
- end
+ end
+
+ def test_clamp_with_range
+ cmp->(x) do 0 <=> x end
+ assert_equal(1, @o.clamp(1..2))
+ assert_equal(-1, @o.clamp(-2..-1))
+ assert_equal(@o, @o.clamp(-1..3))
+
+ assert_equal(1, @o.clamp(1..1))
+ assert_equal(@o, @o.clamp(0..0))
+
+ assert_equal(1, @o.clamp(1..))
+ assert_equal(1, @o.clamp(1...))
+ assert_equal(@o, @o.clamp(0..))
+ assert_equal(@o, @o.clamp(0...))
+ assert_equal(@o, @o.clamp(..2))
+ assert_equal(-1, @o.clamp(-2..-1))
+ assert_equal(@o, @o.clamp(-2..0))
+ assert_equal(@o, @o.clamp(-2..))
+ assert_equal(@o, @o.clamp(-2...))
+
+ exc = [ArgumentError, 'cannot clamp with an exclusive range']
+ assert_raise_with_message(*exc) {@o.clamp(1...2)}
+ assert_raise_with_message(*exc) {@o.clamp(0...2)}
+ assert_raise_with_message(*exc) {@o.clamp(-1...0)}
+ assert_raise_with_message(*exc) {@o.clamp(...2)}
+
+ assert_raise_with_message(ArgumentError, 'min argument must be less than or equal to max argument') {
+ @o.clamp(2..1)
+ }
+ end
def test_err
assert_raise(ArgumentError) { 1.0 < nil }
diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb
new file mode 100644
index 0000000000..76b961b37e
--- /dev/null
+++ b/test/ruby/test_compile_prism.rb
@@ -0,0 +1,2755 @@
+# frozen_string_literal: true
+
+# This file is organized to match itemization in https://github.com/ruby/prism/issues/1335
+module Prism
+ class TestCompilePrism < Test::Unit::TestCase
+ def test_iseq_has_node_id
+ code = "proc { <<END }\n hello\nEND"
+ iseq = RubyVM::InstructionSequence.compile_prism(code)
+ assert_operator iseq.to_a[4][:node_id], :>, -1
+ end
+
+ # Subclass is used for tests which need it
+ class Subclass; end
+ ############################################################################
+ # Literals #
+ ############################################################################
+
+ def test_FalseNode
+ assert_prism_eval("false")
+ end
+
+ def test_FloatNode
+ assert_prism_eval("1.2")
+ assert_prism_eval("1.2e3")
+ assert_prism_eval("+1.2e+3")
+ assert_prism_eval("-1.2e-3")
+ end
+
+ def test_ImaginaryNode
+ assert_prism_eval("1i")
+ assert_prism_eval("+1.0i")
+ assert_prism_eval("1ri")
+ end
+
+ def test_IntegerNode
+ assert_prism_eval("1")
+ assert_prism_eval("+1")
+ assert_prism_eval("-1")
+ assert_prism_eval("0x10")
+ assert_prism_eval("0b10")
+ assert_prism_eval("0o10")
+ assert_prism_eval("010")
+ assert_prism_eval("(0o00)")
+ end
+
+ def test_NilNode
+ assert_prism_eval("nil")
+ end
+
+ def test_RationalNode
+ assert_prism_eval("1.2r")
+ assert_prism_eval("+1.2r")
+ end
+
+ def test_SelfNode
+ assert_prism_eval("self")
+ end
+
+ def test_SourceEncodingNode
+ assert_prism_eval("__ENCODING__")
+ end
+
+ def test_SourceFileNode
+ assert_prism_eval("__FILE__")
+ end
+
+ def test_SourceLineNode
+ assert_prism_eval("__LINE__", raw: true)
+ end
+
+ def test_TrueNode
+ assert_prism_eval("true")
+ end
+
+ ############################################################################
+ # Reads #
+ ############################################################################
+
+ def test_BackReferenceReadNode
+ assert_prism_eval("$+")
+ end
+
+ def test_ClassVariableReadNode
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit = 1; @@pit; end")
+ end
+
+ def test_ConstantPathNode
+ assert_prism_eval("Prism::TestCompilePrism")
+ end
+
+ def test_ConstantReadNode
+ assert_prism_eval("Prism")
+ end
+
+ Z = 1
+
+ def test_DefinedNode
+ assert_prism_eval("defined? nil")
+ assert_prism_eval("defined? self")
+ assert_prism_eval("defined? true")
+ assert_prism_eval("defined? false")
+ assert_prism_eval("defined? 1")
+ assert_prism_eval("defined? 1i")
+ assert_prism_eval("defined? 1.0")
+ assert_prism_eval("defined? 1..2")
+ assert_prism_eval("defined? [A, B, C]")
+ assert_prism_eval("defined? [1, 2, 3]")
+ assert_prism_eval("defined?({ a: 1 })")
+ assert_prism_eval("defined? 'str'")
+ assert_prism_eval('defined?("#{expr}")')
+ assert_prism_eval("defined? :sym")
+ assert_prism_eval("defined? /foo/")
+ assert_prism_eval('defined?(/#{1}/)')
+ assert_prism_eval("defined? -> { 1 + 1 }")
+ assert_prism_eval("defined? a && b")
+ assert_prism_eval("defined? a || b")
+ assert_prism_eval("defined? __ENCODING__")
+ assert_prism_eval("defined? __FILE__")
+ assert_prism_eval("defined? __LINE__")
+
+ assert_prism_eval("defined? %[1,2,3]")
+ assert_prism_eval("defined? %q[1,2,3]")
+ assert_prism_eval("defined? %Q[1,2,3]")
+ assert_prism_eval("defined? %r[1,2,3]")
+ assert_prism_eval("defined? %i[1,2,3]")
+ assert_prism_eval("defined? %I[1,2,3]")
+ assert_prism_eval("defined? %w[1,2,3]")
+ assert_prism_eval("defined? %W[1,2,3]")
+ assert_prism_eval("defined? %s[1,2,3]")
+ assert_prism_eval("defined? %x[1,2,3]")
+
+ assert_prism_eval("defined? [*b]")
+ assert_prism_eval("defined? [[*1..2], 3, *4..5]")
+ assert_prism_eval("defined? [a: [:b, :c]]")
+ assert_prism_eval("defined? 1 in 1")
+
+ assert_prism_eval("defined? @a")
+ assert_prism_eval("defined? $a")
+ assert_prism_eval("defined? @@a")
+ assert_prism_eval("defined? A")
+ assert_prism_eval("defined? ::A")
+ assert_prism_eval("defined? A::B")
+ assert_prism_eval("defined? A::B::C")
+ assert_prism_eval("defined? #{self.class.name}::Z::A")
+ assert_prism_eval("defined? yield")
+ assert_prism_eval("defined? super")
+
+ assert_prism_eval("defined? X = 1")
+ assert_prism_eval("defined? X *= 1")
+ assert_prism_eval("defined? X /= 1")
+ assert_prism_eval("defined? X &= 1")
+ assert_prism_eval("defined? X ||= 1")
+
+ assert_prism_eval("defined? $1")
+ assert_prism_eval("defined? $2")
+ assert_prism_eval("defined? $`")
+ assert_prism_eval("defined? $'")
+ assert_prism_eval("defined? $+")
+
+ assert_prism_eval("defined? $X = 1")
+ assert_prism_eval("defined? $X *= 1")
+ assert_prism_eval("defined? $X /= 1")
+ assert_prism_eval("defined? $X &= 1")
+ assert_prism_eval("defined? $X ||= 1")
+
+ assert_prism_eval("defined? @@X = 1")
+ assert_prism_eval("defined? @@X *= 1")
+ assert_prism_eval("defined? @@X /= 1")
+ assert_prism_eval("defined? @@X &= 1")
+ assert_prism_eval("defined? @@X ||= 1")
+
+ assert_prism_eval("defined? @X = 1")
+ assert_prism_eval("defined? @X *= 1")
+ assert_prism_eval("defined? @X /= 1")
+ assert_prism_eval("defined? @X &= 1")
+ assert_prism_eval("defined? @X ||= 1")
+
+ assert_prism_eval("x = 1; defined? x = 1")
+ assert_prism_eval("x = 1; defined? x *= 1")
+ assert_prism_eval("x = 1; defined? x /= 1")
+ assert_prism_eval("x = 1; defined? x &= 1")
+ assert_prism_eval("x = 1; defined? x ||= 1")
+
+ assert_prism_eval("if defined? A; end")
+
+ assert_prism_eval("defined?(())")
+ assert_prism_eval("defined?(('1'))")
+
+ # method chain starting with self that's truthy
+ assert_prism_eval("defined?(self.itself.itself.itself)")
+
+ # method chain starting with self that's false (exception swallowed)
+ assert_prism_eval("defined?(self.itself.itself.neat)")
+
+ # single self with method, truthy
+ assert_prism_eval("defined?(self.itself)")
+
+ # single self with method, false
+ assert_prism_eval("defined?(self.neat!)")
+
+ # method chain implicit self that's truthy
+ assert_prism_eval("defined?(itself.itself.itself)")
+
+ # method chain implicit self that's false
+ assert_prism_eval("defined?(itself.neat.itself)")
+
+ ## single method implicit self that's truthy
+ assert_prism_eval("defined?(itself)")
+
+ ## single method implicit self that's false
+ assert_prism_eval("defined?(neatneat)")
+
+ assert_prism_eval("defined?(a(itself))")
+ assert_prism_eval("defined?(itself(itself))")
+
+ # method chain with a block on the inside
+ assert_prism_eval("defined?(itself { 1 }.itself)")
+
+ # method chain with parenthesized receiver
+ assert_prism_eval("defined?((itself).itself)")
+
+ # Method chain on a constant
+ assert_prism_eval(<<~RUBY)
+ class PrismDefinedNode
+ def m1; end
+ end
+
+ defined?(PrismDefinedNode.new.m1)
+ RUBY
+
+ assert_prism_eval("defined?(next)")
+ assert_prism_eval("defined?(break)")
+ assert_prism_eval("defined?(redo)")
+ assert_prism_eval("defined?(retry)")
+
+ assert_prism_eval(<<~RUBY)
+ class PrismDefinedReturnNode
+ def self.m1; defined?(return) end
+ end
+
+ PrismDefinedReturnNode.m1
+ RUBY
+
+ assert_prism_eval("defined?(begin; 1; end)")
+
+ assert_prism_eval("defined?(defined?(a))")
+ assert_prism_eval('defined?(:"#{1}")')
+ assert_prism_eval("defined?(`echo #{1}`)")
+
+ assert_prism_eval("defined?(PrismTestSubclass.test_call_and_write_node &&= 1)")
+ assert_prism_eval("defined?(PrismTestSubclass.test_call_operator_write_node += 1)")
+ assert_prism_eval("defined?(PrismTestSubclass.test_call_or_write_node ||= 1)")
+ assert_prism_eval("defined?(Prism::CPAWN &&= 1)")
+ assert_prism_eval("defined?(Prism::CPOWN += 1)")
+ assert_prism_eval("defined?(Prism::CPOrWN ||= 1)")
+ assert_prism_eval("defined?(Prism::CPWN = 1)")
+ assert_prism_eval("defined?([0][0] &&= 1)")
+ assert_prism_eval("defined?([0][0] += 1)")
+ assert_prism_eval("defined?([0][0] ||= 1)")
+
+ assert_prism_eval("defined?(case :a; when :a; 1; else; 2; end)")
+ assert_prism_eval("defined?(case [1, 2, 3]; in [1, 2, 3]; 4; end)")
+ assert_prism_eval("defined?(class PrismClassA; end)")
+ assert_prism_eval("defined?(def prism_test_def_node; end)")
+ assert_prism_eval("defined?(for i in [1,2] do; i; end)")
+ assert_prism_eval("defined?(if true; 1; end)")
+ assert_prism_eval("defined?(/(?<foo>bar)/ =~ 'barbar')")
+ assert_prism_eval("defined?(1 => 1)")
+ assert_prism_eval("defined?(module M; end)")
+ assert_prism_eval("defined?(1.2r)")
+ assert_prism_eval("defined?(class << self; end)")
+ assert_prism_eval("defined?(while a != 1; end)")
+ assert_prism_eval("defined?(until a == 1; end)")
+ assert_prism_eval("defined?(unless true; 1; end)")
+ end
+
+ def test_GlobalVariableReadNode
+ assert_prism_eval("$pit = 1; $pit")
+ end
+
+ def test_InstanceVariableReadNode
+ assert_prism_eval("class Prism::TestCompilePrism; @pit = 1; @pit; end")
+ end
+
+ def test_LocalVariableReadNode
+ assert_prism_eval("pit = 1; pit")
+ end
+
+ def test_NumberedReferenceReadNode
+ assert_prism_eval("$1")
+ assert_prism_eval("$99999")
+ end
+
+ ############################################################################
+ # Writes #
+ ############################################################################
+
+ def test_ClassVariableAndWriteNode
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit = 0; @@pit &&= 1; end")
+ end
+
+ def test_ClassVariableOperatorWriteNode
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit = 0; @@pit += 1; end")
+ end
+
+ def test_ClassVariableOrWriteNode
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit = 1; @@pit ||= 0; end")
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit = nil; @@pit ||= 1; end")
+ end
+
+ def test_ClassVariableWriteNode
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit = 1; end")
+ end
+
+ def test_ConstantAndWriteNode
+ assert_prism_eval("Constant = 1; Constant &&= 1")
+ end
+
+ def test_ConstantOperatorWriteNode
+ assert_prism_eval("Constant = 1; Constant += 1")
+ end
+
+ def test_ConstantOrWriteNode
+ assert_prism_eval("Constant = 1; Constant ||= 1")
+ end
+
+ def test_ConstantWriteNode
+ # We don't call assert_prism_eval directly in this case because we
+ # don't want to assign the constant multiple times if we run
+ # with `--repeat-count`
+ # Instead, we eval manually here, and remove the constant to
+ constant_name = "YCT"
+ source = "#{constant_name} = 1"
+ prism_eval = RubyVM::InstructionSequence.compile_prism(source).eval
+ assert_equal prism_eval, 1
+ Object.send(:remove_const, constant_name)
+ end
+
+ def test_ConstantPathWriteNode
+ assert_prism_eval("Prism::CPWN = 1")
+ assert_prism_eval("::CPWN = 1")
+ end
+
+ def test_ConstantPathAndWriteNode
+ assert_prism_eval("Prism::CPAWN = 1; Prism::CPAWN &&= 2")
+ assert_prism_eval("Prism::CPAWN &&= 1")
+ assert_prism_eval("::CPAWN = 1; ::CPAWN &&= 2")
+ end
+
+ def test_ConstantPathOrWriteNode
+ assert_prism_eval("Prism::CPOrWN = nil; Prism::CPOrWN ||= 1")
+ assert_prism_eval("Prism::CPOrWN ||= 1")
+ assert_prism_eval("::CPOrWN = nil; ::CPOrWN ||= 1")
+ end
+
+ def test_ConstantPathOperatorWriteNode
+ assert_prism_eval("Prism::CPOWN = 0; Prism::CPOWN += 1")
+ assert_prism_eval("::CPOWN = 0; ::CPOWN += 1")
+ end
+
+ def test_GlobalVariableAndWriteNode
+ assert_prism_eval("$pit = 0; $pit &&= 1")
+ end
+
+ def test_GlobalVariableOperatorWriteNode
+ assert_prism_eval("$pit = 0; $pit += 1")
+ end
+
+ def test_GlobalVariableOrWriteNode
+ assert_prism_eval("$pit ||= 1")
+ end
+
+ def test_GlobalVariableWriteNode
+ assert_prism_eval("$pit = 1")
+ end
+
+ def test_InstanceVariableAndWriteNode
+ assert_prism_eval("@pit = 0; @pit &&= 1")
+ end
+
+ def test_InstanceVariableOperatorWriteNode
+ assert_prism_eval("@pit = 0; @pit += 1")
+ end
+
+ def test_InstanceVariableOrWriteNode
+ assert_prism_eval("@pit ||= 1")
+ end
+
+ def test_InstanceVariableWriteNode
+ assert_prism_eval("class Prism::TestCompilePrism; @pit = 1; end")
+ end
+
+ def test_LocalVariableAndWriteNode
+ assert_prism_eval("pit = 0; pit &&= 1")
+ end
+
+ def test_LocalVariableOperatorWriteNode
+ assert_prism_eval("pit = 0; pit += 1")
+ end
+
+ def test_LocalVariableOrWriteNode
+ assert_prism_eval("pit ||= 1")
+ end
+
+ def test_LocalVariableWriteNode
+ assert_prism_eval("pit = 1")
+ assert_prism_eval(<<-CODE)
+ a = 0
+ [].each do
+ a = 1
+ end
+ a
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ a = 1
+ d = 1
+ [1].each do
+ b = 2
+ a = 2
+ [2].each do
+ c = 3
+ d = 4
+ a = 2
+ end
+ end
+ [a, d]
+ CODE
+ end
+
+ def test_MatchWriteNode
+ assert_prism_eval("/(?<foo>bar)(?<baz>bar>)/ =~ 'barbar'")
+ assert_prism_eval("/(?<foo>bar)/ =~ 'barbar'")
+ end
+
+ ############################################################################
+ # Multi-writes #
+ ############################################################################
+
+ def test_ClassVariableTargetNode
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit, @@pit1 = 1; end")
+ end
+
+ def test_ConstantTargetNode
+ # We don't call assert_prism_eval directly in this case because we
+ # don't want to assign the constant multiple times if we run
+ # with `--repeat-count`
+ # Instead, we eval manually here, and remove the constant to
+ constant_names = ["YCT", "YCT2"]
+ source = "#{constant_names.join(",")} = 1"
+ prism_eval = RubyVM::InstructionSequence.compile_prism(source).eval
+ assert_equal prism_eval, 1
+ constant_names.map { |name|
+ Object.send(:remove_const, name)
+ }
+ end
+
+ def test_ConstantPathTargetNode
+ assert_separately([], <<~'RUBY')
+ verbose = $VERBOSE
+ # Create some temporary nested constants
+ Object.send(:const_set, "MyFoo", Object)
+ Object.const_get("MyFoo").send(:const_set, "Bar", Object)
+
+ constant_names = ["MyBar", "MyFoo::Bar", "MyFoo::Bar::Baz"]
+ source = "#{constant_names.join(",")} = Object"
+ iseq = RubyVM::InstructionSequence.compile_prism(source)
+ $VERBOSE = nil
+ prism_eval = iseq.eval
+ $VERBOSE = verbose
+ assert_equal prism_eval, Object
+ RUBY
+ end
+
+ def test_GlobalVariableTargetNode
+ assert_prism_eval("$pit, $pit1 = 1")
+ end
+
+ def test_InstanceVariableTargetNode
+ assert_prism_eval("class Prism::TestCompilePrism; @pit, @pit1 = 1; end")
+ end
+
+ def test_LocalVariableTargetNode
+ assert_prism_eval("pit, pit1 = 1")
+ assert_prism_eval(<<-CODE)
+ a = 1
+ [1].each do
+ c = 2
+ a, b = 2
+ end
+ a
+ CODE
+ end
+
+ def test_MultiTargetNode
+ assert_prism_eval("a, (b, c) = [1, 2, 3]")
+ assert_prism_eval("a, (b, c) = [1, 2, 3]; a")
+ assert_prism_eval("a, (b, c) = [1, 2, 3]; b")
+ assert_prism_eval("a, (b, c) = [1, 2, 3]; c")
+ assert_prism_eval("a, (b, c) = [1, [2, 3]]; c")
+ assert_prism_eval("a, (b, *c) = [1, [2, 3]]; c")
+ assert_prism_eval("a, (b, *c) = 1, [2, 3]; c")
+ assert_prism_eval("a, (b, *) = 1, [2, 3]; b")
+ assert_prism_eval("a, (b, *c, d) = 1, [2, 3, 4]; [a, b, c, d]")
+ assert_prism_eval("(a, (b, c, d, e), f, g), h = [1, [2, 3]], 4, 5, [6, 7]; c")
+ end
+
+ def test_MultiWriteNode
+ assert_prism_eval("foo, bar = [1, 2]")
+ assert_prism_eval("foo, = [1, 2]")
+ assert_prism_eval("foo, *, bar = [1, 2]")
+ assert_prism_eval("foo, bar = 1, 2")
+ assert_prism_eval("foo, *, bar = 1, 2")
+ assert_prism_eval("foo, *, bar = 1, 2, 3, 4")
+ assert_prism_eval("a, b, *, d = 1, 2, 3, 4")
+ assert_prism_eval("a, b, *, d = 1, 2")
+ assert_prism_eval("(a, b), *, c = [1, 3], 4, 5")
+ assert_prism_eval("(a, b), *, c = [1, 3], 4, 5; a")
+ assert_prism_eval("(a, b), *, c = [1, 3], 4, 5; b")
+ assert_prism_eval("(a, b), *, c = [1, 3], 4, 5; c")
+ assert_prism_eval("a, *, (c, d) = [1, 3], 4, 5; a")
+ assert_prism_eval("a, *, (c, d) = [1, 3], 4, 5; c")
+ assert_prism_eval("(a, b, c), *, (d, e) = [1, 3], 4, 5, [6, 7]")
+ assert_prism_eval("(a, b, c), *, (d, e) = [1, 3], 4, 5, [6, 7]; b")
+ assert_prism_eval("(a, b, c), *, (d, e) = [1, 3], 4, 5, [6, 7]; d")
+ assert_prism_eval("((a, *, b), *, (c, *, (d, *, e, f, g))), *, ((h, i, *, j), *, (k, l, m, *, n, o, p), q, r) = 1; a")
+ assert_prism_eval("*a = 1; a")
+ assert_prism_eval("_, {}[:foo] = 1")
+ assert_prism_eval("_, {}[:foo], _ = 1")
+ assert_prism_eval("_, {}[:foo], _ = 1")
+ assert_prism_eval("_,{}[:foo], _, {}[:bar] = 1")
+ assert_prism_eval("* = :foo")
+ assert_prism_eval("* = *[]")
+ assert_prism_eval("a, * = :foo")
+
+
+ assert_prism_eval(<<~CODE)
+ class Foo
+ def bar=(x); end
+ def baz=(c); end
+ end
+ foo = Foo.new
+ foo.bar, foo.baz = 1
+ CODE
+ assert_prism_eval(<<~CODE)
+ class Foo
+ def bar=(x); end
+ def baz=(c); end
+ end
+ foo = Foo.new
+ _, foo.bar, foo.baz = 1
+ CODE
+ assert_prism_eval(<<~CODE)
+ class Foo
+ def bar=(x); end
+ def baz=(c); end
+ end
+ foo = Foo.new
+ _, foo.bar, _, foo.baz = 1
+ CODE
+
+ # Test nested writes with method calls
+ assert_prism_eval(<<~RUBY)
+ class Foo
+ attr_accessor :bar
+ end
+
+ a = Foo.new
+
+ (a.bar, a.bar), b = [1], 2
+ RUBY
+ assert_prism_eval(<<~RUBY)
+ h = {}
+ (h[:foo], h[:bar]), a = [1], 2
+ RUBY
+ end
+
+ ############################################################################
+ # String-likes #
+ ############################################################################
+
+ def test_EmbeddedStatementsNode
+ assert_prism_eval('"foo #{to_s} baz"')
+ end
+
+ def test_EmbeddedVariableNode
+ assert_prism_eval('class Prism::TestCompilePrism; @pit = 1; "#@pit"; end')
+ assert_prism_eval('class Prism::TestCompilePrism; @@pit = 1; "#@@pit"; end')
+ assert_prism_eval('$pit = 1; "#$pit"')
+ end
+
+ def test_InterpolatedMatchLastLineNode
+ assert_prism_eval('$pit = ".oo"; if /"#{$pit}"/mix; end')
+ end
+
+ def test_InterpolatedRegularExpressionNode
+ assert_prism_eval('$pit = 1; /1 #$pit 1/')
+ assert_prism_eval('$pit = 1; /#$pit/i')
+ assert_prism_eval('/1 #{1 + 2} 1/')
+ assert_prism_eval('/1 #{"2"} #{1 + 2} 1/')
+ end
+
+ def test_InterpolatedStringNode
+ assert_prism_eval('$pit = 1; "1 #$pit 1"')
+ assert_prism_eval('"1 #{1 + 2} 1"')
+ assert_prism_eval('"Prism" "::" "TestCompilePrism"')
+ assert_prism_eval(<<-'RUBY')
+ # frozen_string_literal: true
+
+ !("a""b""#{1}").frozen?
+ RUBY
+ assert_prism_eval(<<-'RUBY')
+ # frozen_string_literal: true
+
+ !("a""#{1}""b").frozen?
+ RUBY
+
+ # Test encoding of interpolated strings
+ assert_prism_eval(<<~'RUBY')
+ "#{"foo"}s".encoding
+ RUBY
+ assert_prism_eval(<<~'RUBY')
+ a = "foo"
+ b = "#{a}" << "Bar"
+ [a, b, b.encoding]
+ RUBY
+ end
+
+ def test_concatenated_StringNode
+ assert_prism_eval('("a""b").frozen?')
+ assert_prism_eval(<<-CODE)
+ # frozen_string_literal: true
+
+ ("a""b").frozen?
+ CODE
+ end
+
+ def test_InterpolatedSymbolNode
+ assert_prism_eval('$pit = 1; :"1 #$pit 1"')
+ assert_prism_eval(':"1 #{1 + 2} 1"')
+ end
+
+ def test_InterpolatedXStringNode
+ assert_prism_eval(<<~RUBY)
+ def self.`(command) = command * 2
+ `echo \#{1}`
+ RUBY
+
+ assert_prism_eval(<<~RUBY)
+ def self.`(command) = command * 2
+ `echo \#{"100"}`
+ RUBY
+ end
+
+ def test_MatchLastLineNode
+ assert_prism_eval("if /foo/; end")
+ assert_prism_eval("if /foo/i; end")
+ assert_prism_eval("if /foo/x; end")
+ assert_prism_eval("if /foo/m; end")
+ assert_prism_eval("if /foo/im; end")
+ assert_prism_eval("if /foo/mx; end")
+ assert_prism_eval("if /foo/xi; end")
+ assert_prism_eval("if /foo/ixm; end")
+ end
+
+ def test_RegularExpressionNode
+ assert_prism_eval('/pit/')
+ assert_prism_eval('/pit/i')
+ assert_prism_eval('/pit/x')
+ assert_prism_eval('/pit/m')
+ assert_prism_eval('/pit/im')
+ assert_prism_eval('/pit/mx')
+ assert_prism_eval('/pit/xi')
+ assert_prism_eval('/pit/ixm')
+
+ assert_prism_eval('/pit/u')
+ assert_prism_eval('/pit/e')
+ assert_prism_eval('/pit/s')
+ assert_prism_eval('/pit/n')
+
+ assert_prism_eval('/pit/me')
+ assert_prism_eval('/pit/ne')
+
+ assert_prism_eval('2.times.map { /#{1}/o }')
+ assert_prism_eval('2.times.map { foo = 1; /#{foo}/o }')
+ end
+
+ def test_StringNode
+ assert_prism_eval('"pit"')
+ assert_prism_eval('"a".frozen?')
+ end
+
+ def test_StringNode_frozen_string_literal_true
+ [
+ # Test that string literal is frozen
+ <<~RUBY,
+ # frozen_string_literal: true
+ "a".frozen?
+ RUBY
+ # Test that two string literals with the same contents are the same string
+ <<~RUBY,
+ # frozen_string_literal: true
+ "hello".equal?("hello")
+ RUBY
+ ].each do |src|
+ assert_prism_eval(src, raw: true)
+ end
+ end
+
+ def test_StringNode_frozen_string_literal_false
+ [
+ # Test that string literal is frozen
+ <<~RUBY,
+ # frozen_string_literal: false
+ !"a".frozen?
+ RUBY
+ # Test that two string literals with the same contents are the same string
+ <<~RUBY,
+ # frozen_string_literal: false
+ !"hello".equal?("hello")
+ RUBY
+ ].each do |src|
+ assert_prism_eval(src, raw: true)
+ end
+ end
+
+ def test_StringNode_frozen_string_literal_default
+ # Test that string literal is chilled
+ assert_prism_eval('"a".frozen?')
+
+ # Test that two identical chilled string literals aren't the same object
+ assert_prism_eval('!"hello".equal?("hello")')
+ end
+
+ def test_SymbolNode
+ assert_prism_eval(":pit")
+
+ # Test UTF-8 symbol in a US-ASCII file
+ assert_prism_eval(<<~'RUBY', raw: true)
+ # -*- coding: us-ascii -*-
+ :"\u{e9}"
+ RUBY
+
+ # Test ASCII-8BIT symbol in a US-ASCII file
+ assert_prism_eval(<<~'RUBY', raw: true)
+ # -*- coding: us-ascii -*-
+ :"\xff"
+ RUBY
+
+ # Test US-ASCII symbol in a ASCII-8BIT file
+ assert_prism_eval(<<~'RUBY', raw: true)
+ # -*- coding: ascii-8bit -*-
+ :a
+ RUBY
+ end
+
+ def test_XStringNode
+ assert_prism_eval(<<~RUBY)
+ class Prism::TestCompilePrism
+ def self.`(command) = command * 2
+ `pit`
+ end
+ RUBY
+ end
+
+ ############################################################################
+ # Structures #
+ ############################################################################
+
+ def test_ArrayNode
+ assert_prism_eval("[]")
+ assert_prism_eval("[1, 2, 3]")
+ assert_prism_eval("%i[foo bar baz]")
+ assert_prism_eval("%w[foo bar baz]")
+ assert_prism_eval("[*1..2]")
+ assert_prism_eval("[*1..2, 3, 4, *5..6, 7, 8]")
+ assert_prism_eval("[*1..2, 3, 4, *5..6, 7, 8, *9..11]")
+ assert_prism_eval("[0, *1..2, 3, 4, *5..6, 7, 8, *9..11]")
+ assert_prism_eval("[-1, true, 0, *1..2, 3, 4, *5..6, 7, 8, *9..11]")
+ assert_prism_eval("a = [1,2]; [0, *a, 3, 4, *5..6, 7, 8, *9..11]")
+ assert_prism_eval("[[*1..2], 3, *4..5]")
+
+ elements = Array.new(64) { ":foo" }
+ assert_prism_eval("[#{elements.join(", ")}, bar: 1, baz: 2]")
+
+ # Test keyword splat inside of array
+ assert_prism_eval("[**{x: 'hello'}]")
+
+ # Test UTF-8 string array literal in a US-ASCII file
+ assert_prism_eval(<<~'RUBY', raw: true)
+ # -*- coding: us-ascii -*-
+ # frozen_string_literal: true
+ %W"\u{1f44b} \u{1f409}"
+ RUBY
+ end
+
+ def test_AssocNode
+ assert_prism_eval("{ foo: :bar }")
+ end
+
+ def test_AssocSplatNode
+ assert_prism_eval("foo = { a: 1 }; { **foo }")
+ assert_prism_eval("foo = { a: 1 }; bar = foo; { **foo, b: 2, **bar, c: 3 }")
+ assert_prism_eval("foo = { a: 1 }; { b: 2, **foo, c: 3}")
+
+ # Test anonymous AssocSplatNode
+ assert_prism_eval(<<~RUBY)
+ o = Object.new
+ def o.bar(**) = Hash(**)
+
+ o.bar(hello: "world")
+ RUBY
+
+ # Test that AssocSplatNode is evaluated before BlockArgumentNode using
+ # the splatkw instruction
+ assert_prism_eval(<<~RUBY)
+ o = Struct.new(:ary) do
+ def to_hash
+ ary << :to_hash
+ {}
+ end
+
+ def to_proc
+ ary << :to_proc
+ -> {}
+ end
+
+ def t(...); end
+ end.new
+ o.ary = []
+
+ o.t(**o, &o)
+ o.ary
+ RUBY
+ end
+
+ def test_HashNode
+ assert_prism_eval("{}")
+ assert_prism_eval("{ a: :a }")
+ assert_prism_eval("{ a: :a, b: :b }")
+ assert_prism_eval("a = 1; { a: a }")
+ assert_prism_eval("a = 1; { a: }")
+ assert_prism_eval("{ to_s: }")
+ assert_prism_eval("{ Prism: }")
+ assert_prism_eval("[ Prism: [:b, :c]]")
+ assert_prism_eval("{ [] => 1}")
+ end
+
+ def test_ImplicitNode
+ assert_prism_eval("{ to_s: }")
+ end
+
+ def test_RangeNode
+ assert_prism_eval("1..2")
+ assert_prism_eval("1...2")
+ assert_prism_eval("..2")
+ assert_prism_eval("...2")
+ assert_prism_eval("1..")
+ assert_prism_eval("1...")
+ assert_prism_eval("a1 = 1; a2 = 2; a1..a2")
+ assert_prism_eval("a1 = 1; a2 = 2; a1...a2")
+ assert_prism_eval("a2 = 2; ..a2")
+ assert_prism_eval("a2 = 2; ...a2")
+ assert_prism_eval("a1 = 1; a1..")
+ assert_prism_eval("a1 = 1; a1...")
+ assert_prism_eval("1..2; nil")
+ assert_prism_eval("1...2; nil")
+ assert_prism_eval("..2; nil")
+ assert_prism_eval("...2; nil")
+ assert_prism_eval("1..; nil")
+ assert_prism_eval("1...; nil")
+ assert_prism_eval("a1 = 1; a2 = 2; a1..a2; nil")
+ assert_prism_eval("a1 = 1; a2 = 2; a1...a2; nil")
+ assert_prism_eval("a2 = 2; ..a2; nil")
+ assert_prism_eval("a2 = 2; ...a2; nil")
+ assert_prism_eval("a1 = 1; a1..; nil")
+ assert_prism_eval("a1 = 1; a1...; nil")
+ end
+
+ def test_SplatNode
+ assert_prism_eval("*b = []; b")
+ assert_prism_eval("*b = [1, 2, 3]; b")
+ assert_prism_eval("a, *b = [1, 2, 3]; a")
+ assert_prism_eval("a, *b = [1, 2, 3]; b")
+ assert_prism_eval("a, *b, c = [1, 2, 3]; a")
+ assert_prism_eval("a, *b, c = [1, 2, 3]; b")
+ assert_prism_eval("a, *b, c = [1, 2, 3]; c")
+ assert_prism_eval("*b, c = [1, 2, 3]; b")
+ assert_prism_eval("*b, c = [1, 2, 3]; c")
+ assert_prism_eval("a, *, c = [1, 2, 3]; a")
+ assert_prism_eval("a, *, c = [1, 2, 3]; c")
+
+ # Test anonymous splat node
+ assert_prism_eval(<<~RUBY)
+ def self.bar(*) = Array(*)
+
+ bar([1, 2, 3])
+ RUBY
+ end
+
+ ############################################################################
+ # Jumps #
+ ############################################################################
+
+ def test_AndNode
+ assert_prism_eval("true && 1")
+ assert_prism_eval("false && 1")
+ end
+
+ def test_CaseNode
+ assert_prism_eval("case :a; when :a; 1; else; 2; end")
+ assert_prism_eval("case :a; when :b; 1; else; 2; end")
+ assert_prism_eval("case :a; when :a; 1; else; 2; end")
+ assert_prism_eval("case :a; when :a; end")
+ assert_prism_eval("case :a; when :b, :c; end")
+ assert_prism_eval("case; when :a; end")
+ assert_prism_eval("case; when :a, :b; 1; else; 2 end")
+ assert_prism_eval("case :a; when :b; else; end")
+ assert_prism_eval("b = 1; case :a; when b; else; end")
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_case_node
+ case :a
+ when :b
+ else
+ return 2
+ end
+ 1
+ end
+ prism_test_case_node
+ CODE
+
+ # Test splat in when
+ assert_prism_eval(<<~RUBY)
+ ary = [1, 2]
+ case 1
+ when *ary
+ :ok
+ else
+ :ng
+ end
+ RUBY
+
+ # Test splat in when
+ assert_prism_eval(<<~RUBY)
+ ary = [1, 2]
+ case 1
+ when :foo, *ary
+ :ok
+ else
+ :ng
+ end
+ RUBY
+
+ # Test case without predicate
+ assert_prism_eval(<<~RUBY)
+ case
+ when 1 == 2
+ :ng
+ else
+ :ok
+ end
+ RUBY
+
+ # test splat with no predicate
+ assert_prism_eval(<<~RUBY)
+ case
+ when *[true]
+ :ok
+ else
+ :ng
+ end
+ RUBY
+ end
+
+ def test_ElseNode
+ assert_prism_eval("if false; 0; else; 1; end")
+ assert_prism_eval("if true; 0; else; 1; end")
+ assert_prism_eval("true ? 1 : 0")
+ assert_prism_eval("false ? 0 : 1")
+ end
+
+ def test_FlipFlopNode
+ assert_prism_eval("not (1 == 1) .. (2 == 2)")
+ assert_prism_eval("not (1 == 1) ... (2 == 2)")
+ end
+
+ def test_IfNode
+ assert_prism_eval("if true; 1; end")
+ assert_prism_eval("1 if true")
+ assert_prism_eval('a = b = 1; if a..b; end')
+ assert_prism_eval('if "a".."b"; end')
+ assert_prism_eval('if "a"..; end')
+ assert_prism_eval('if .."b"; end')
+ assert_prism_eval('if ..1; end')
+ assert_prism_eval('if 1..; end')
+ assert_prism_eval('if 1..2; end')
+ assert_prism_eval('if true or true; end');
+ end
+
+ def test_OrNode
+ assert_prism_eval("true || 1")
+ assert_prism_eval("false || 1")
+ end
+
+ def test_UnlessNode
+ assert_prism_eval("1 unless true")
+ assert_prism_eval("1 unless false")
+ assert_prism_eval("unless true; 1; end")
+ assert_prism_eval("unless false; 1; end")
+ end
+
+ def test_UntilNode
+ assert_prism_eval("a = 0; until a == 1; a = a + 1; end")
+
+ # Test UntilNode in rescue
+ assert_prism_eval(<<~RUBY)
+ o = Object.new
+ o.instance_variable_set(:@ret, [])
+ def o.foo = @ret << @ret.length
+ def o.bar = @ret.length > 3
+ begin
+ raise
+ rescue
+ o.foo until o.bar
+ end
+ o.instance_variable_get(:@ret)
+ RUBY
+ end
+
+ def test_WhileNode
+ assert_prism_eval("a = 0; while a != 1; a = a + 1; end")
+
+ # Test WhileNode in rescue
+ assert_prism_eval(<<~RUBY)
+ o = Object.new
+ o.instance_variable_set(:@ret, [])
+ def o.foo = @ret << @ret.length
+ def o.bar = @ret.length < 3
+ begin
+ raise
+ rescue
+ o.foo while o.bar
+ end
+ o.instance_variable_get(:@ret)
+ RUBY
+ end
+
+ def test_ForNode
+ 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("r = []; for foo, in [1,2,3] do r << foo end; r")
+
+ 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
+
+ ############################################################################
+ # Throws #
+ ############################################################################
+
+ def test_BeginNode
+ assert_prism_eval("begin; 1; end")
+ assert_prism_eval("begin; end; 1")
+ end
+
+ def test_BreakNode
+ assert_prism_eval("while true; break; end")
+ assert_prism_eval("while true; break 1; end")
+ assert_prism_eval("while true; break 1, 2; end")
+
+ assert_prism_eval("[].each { break }")
+ assert_prism_eval("[true].map { break }")
+ end
+
+ def test_ensure_in_methods
+ assert_prism_eval(<<-CODE)
+def self.m
+ a = []
+ensure
+ a << 5
+ return a
+end
+m
+ CODE
+ end
+
+ def test_break_runs_ensure
+ assert_prism_eval(<<-CODE)
+a = []
+while true
+ begin
+ break
+ ensure
+ a << 1
+ end
+end
+a
+ CODE
+ end
+
+ def test_EnsureNode
+ assert_prism_eval("begin; 1; ensure; 2; end")
+ assert_prism_eval("begin; 1; begin; 3; ensure; 4; end; ensure; 2; end")
+ assert_prism_eval(<<-CODE)
+ begin
+ a = 2
+ ensure
+ end
+ CODE
+ assert_prism_eval(<<-CODE)
+ begin
+ a = 2
+ ensure
+ a = 3
+ end
+ a
+ CODE
+
+ # Test that ensure block only evaluated once
+ assert_prism_eval(<<~RUBY)
+ res = []
+ begin
+ begin
+ raise
+ ensure
+ res << $!.to_s
+ end
+ rescue
+ res
+ end
+ RUBY
+
+ assert_prism_eval(<<-CODE)
+ a = 1
+ begin
+ a = 2
+ ensure
+ a = 3
+ end
+ a
+ CODE
+ assert_prism_eval(<<-CODE)
+ a = 1
+ begin
+ b = 2
+ ensure
+ c = 3
+ end
+ a + b + c
+ CODE
+ assert_prism_eval(<<~CODE)
+ foo = 1
+ begin
+ ensure
+ begin
+ ensure
+ foo.nil?
+ end
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ def test
+ ensure
+ {}.each do |key, value|
+ {}[key] = value
+ end
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ def test
+ a = 1
+ ensure
+ {}.each do |key, value|
+ {}[key] = a
+ end
+ end
+ CODE
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_ensure_node
+ begin
+ ensure
+ end
+ return
+ end
+ prism_test_ensure_node
+ CODE
+
+ # Test empty ensure block
+ assert_prism_eval(<<~RUBY)
+ res = []
+
+ begin
+ begin
+ raise
+ ensure
+ end
+ rescue
+ res << "rescue"
+ end
+
+ res
+ RUBY
+
+ # Bug #21001
+ assert_prism_eval(<<~RUBY)
+ RUN_ARRAY = [1,2]
+
+ MAP_PROC = Proc.new do |&blk|
+ block_results = []
+ RUN_ARRAY.each do |value|
+ block_value = blk.call(value)
+ block_results.push block_value
+ end
+ block_results
+ ensure
+ next block_results
+ end
+
+ MAP_PROC.call do |value|
+ break if value > 1
+ next value
+ end
+ RUBY
+ end
+
+ def test_NextNode
+ assert_prism_eval("2.times do |i|; next if i == 1; end")
+
+ assert_prism_eval(<<-CODE)
+ res = []
+ i = 0
+ while i < 5
+ i += 1
+ next if i == 3
+ res << i
+ end
+ res
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ res = []
+ (1..5).each do |i|
+ next if i.even?
+ res << i
+ end
+ res
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ (1..5).map do |i|
+ next i, :even if i.even?
+ i
+ end
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ res = []
+ i = 0
+ begin
+ i += 1
+ next if i == 3
+ res << i
+ end while i < 5
+ res
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ while false
+ begin
+ ensure
+ end
+ next
+ end
+ CODE
+
+ assert_prism_eval(<<~CODE)
+ [].each do
+ begin
+ rescue
+ next
+ end
+ end
+ CODE
+ end
+
+ def test_RedoNode
+ assert_prism_eval(<<-CODE)
+ counter = 0
+
+ 5.times do |i|
+ counter += 1
+ if i == 2 && counter < 3
+ redo
+ end
+ end
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ for i in 1..5
+ if i == 3
+ i = 0
+ redo
+ end
+ end
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ i = 0
+ begin
+ i += 1
+ redo if i == 3
+ end while i < 5
+ CODE
+ end
+
+ def test_RescueNode
+ assert_prism_eval("begin; 1; rescue; 2; end")
+ assert_prism_eval(<<~CODE)
+ begin
+ 1
+ rescue SyntaxError
+ 2
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ begin
+ 1
+ raise 'boom'
+ rescue StandardError
+ 2
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ begin
+ a = 1
+ rescue StandardError => e
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ begin
+ raise StandardError
+ rescue StandardError => e
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ begin
+ 1
+ rescue StandardError => e
+ e
+ rescue SyntaxError => f
+ f
+ else
+ 4
+ end
+ CODE
+ assert_prism_eval(<<-CODE)
+ begin
+ a = 2
+ rescue
+ a = 3
+ end
+ a
+ CODE
+ assert_prism_eval(<<-CODE)
+ a = 1
+ begin
+ a = 2
+ rescue
+ a = 3
+ end
+ a
+ CODE
+ assert_prism_eval(<<-CODE)
+ a = 1
+ begin
+ b = 2
+ raise "bang"
+ rescue
+ c = 3
+ end
+ a + b + c
+ CODE
+ assert_prism_eval("begin; rescue; end")
+
+ assert_prism_eval(<<~CODE)
+ begin
+ rescue
+ args.each do |key, value|
+ tmp[key] = 1
+ end
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ 10.times do
+ begin
+ rescue
+ break
+ end
+ end
+ CODE
+
+ # Test RescueNode with ElseNode
+ assert_prism_eval(<<~RUBY)
+ calls = []
+ begin
+ begin
+ rescue RuntimeError
+ calls << 1
+ else
+ calls << 2
+ raise RuntimeError
+ end
+ rescue RuntimeError
+ end
+
+ calls
+ RUBY
+ end
+
+ def test_RescueModifierNode
+ assert_prism_eval("1.nil? rescue false")
+ assert_prism_eval("1.nil? rescue 1")
+ assert_prism_eval("raise 'bang' rescue nil")
+ assert_prism_eval("raise 'bang' rescue a = 1; a.nil?")
+ assert_prism_eval("a = 0 rescue (a += 1 && retry if a <= 1)")
+ end
+
+ def test_RetryNode
+ assert_prism_eval(<<~CODE)
+ a = 1
+ begin
+ a
+ raise "boom"
+ rescue
+ a += 1
+ retry unless a > 1
+ ensure
+ a = 3
+ end
+ CODE
+
+ assert_prism_eval(<<~CODE)
+ begin
+ rescue
+ foo = 2
+ retry
+ end
+ CODE
+
+ assert_prism_eval(<<~CODE)
+ begin
+ a = 2
+ rescue
+ retry
+ end
+ CODE
+ end
+
+ def test_ReturnNode
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_return_node
+ return 1
+ end
+ prism_test_return_node
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_return_node
+ return 1, 2
+ end
+ prism_test_return_node
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_return_node
+ [1].each do |e|
+ return true
+ end
+ end
+ prism_test_return_node
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_return_node
+ [1].map do |i|
+ return i if i == 1
+ 2
+ end
+ end
+ prism_test_return_node
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_return_node(*args, **kwargs)
+ return *args, *args, **kwargs
+ end
+ prism_test_return_node(1, foo: 0)
+ CODE
+ end
+
+ ############################################################################
+ # Scopes/statements #
+ ############################################################################
+
+ def test_BlockNode
+ assert_prism_eval("[1, 2, 3].each { |num| num }")
+
+ assert_prism_eval("[].tap { _1 }")
+
+ assert_prism_eval("[].each { |a,| }")
+ assert_prism_eval("[[1, 2, 3]].map { |_, _, a| a }")
+ assert_prism_eval("[[1, 2, 3]].map { |_, a| a }")
+
+ assert_prism_eval("[[]].map { |a| a }")
+ assert_prism_eval("[[]].map { |a| a }")
+ assert_prism_eval("[[]].map { |a, &block| a }")
+ assert_prism_eval("[[]].map { |a, &block| a }")
+ assert_prism_eval("[{}].map { |a,| }")
+ assert_prism_eval("[[]].map { |a,b=1| a }")
+ assert_prism_eval("[{}].map { |a,| }")
+ assert_prism_eval("[{}].map { |a| a }")
+
+ # Test blocks with MultiTargetNode
+ assert_prism_eval("[[1, 2]].each.map { |(a), (b)| [a, b] }")
+ end
+
+ def test_ClassNode
+ assert_prism_eval("class PrismClassA; end")
+ assert_prism_eval("class PrismClassA; end; class PrismClassB < PrismClassA; end")
+ assert_prism_eval("class PrismClassA; end; class PrismClassA::PrismClassC; end")
+ assert_prism_eval(<<-HERE
+ class PrismClassA; end
+ class PrismClassA::PrismClassC; end
+ class PrismClassB; end
+ class PrismClassB::PrismClassD < PrismClassA::PrismClassC; end
+ HERE
+ )
+ end
+
+ # Many of these tests are versions of tests at bootstraptest/test_method.rb
+ def test_DefNode
+ assert_prism_eval("def prism_test_def_node; end")
+ assert_prism_eval("a = Object.new; def a.prism_singleton; :ok; end; a.prism_singleton")
+ assert_prism_eval("def self.prism_test_def_node() 1 end; prism_test_def_node()")
+ assert_prism_eval("def self.prism_test_def_node(a,b) [a, b] end; prism_test_def_node(1,2)")
+ assert_prism_eval("def self.prism_test_def_node(a,x=7,y=1) x end; prism_test_def_node(7,1)")
+ assert_prism_eval("def self.prism_test_def_node(a = 1); x = 2; end; prism_test_def_node")
+
+ # rest argument
+ assert_prism_eval("def self.prism_test_def_node(*a) a end; prism_test_def_node().inspect")
+ assert_prism_eval("def self.prism_test_def_node(*a) a end; prism_test_def_node(1).inspect")
+ assert_prism_eval("def self.prism_test_def_node(x,y,*a) a end; prism_test_def_node(7,7,1,2).inspect")
+ assert_prism_eval("def self.prism_test_def_node(x,y=7,*a) a end; prism_test_def_node(7).inspect")
+ assert_prism_eval("def self.prism_test_def_node(x,y,z=7,*a) a end; prism_test_def_node(7,7).inspect")
+ assert_prism_eval("def self.prism_test_def_node(x,y,z=7,zz=7,*a) a end; prism_test_def_node(7,7,7).inspect")
+
+ # keyword arguments
+ assert_prism_eval("def self.prism_test_def_node(a: 1, b: 2, c: 4) a + b + c; end; prism_test_def_node(a: 2)")
+ assert_prism_eval("def self.prism_test_def_node(a: 1, b: 2, c: 4) a + b + c; end; prism_test_def_node(b: 3)")
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(x = 1, y, a: 8, b: 2, c: 4)
+ a + b + c + x + y
+ end
+ prism_test_def_node(10, b: 3)
+ CODE
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(a: [])
+ a
+ end
+ prism_test_def_node
+ CODE
+
+ # block arguments
+ assert_prism_eval("def self.prism_test_def_node(&block) block end; prism_test_def_node{}.class")
+ assert_prism_eval("def self.prism_test_def_node(&block) block end; prism_test_def_node().inspect")
+ assert_prism_eval("def self.prism_test_def_node(a,b=7,*c,&block) b end; prism_test_def_node(7,1).inspect")
+ assert_prism_eval("def self.prism_test_def_node(a,b=7,*c,&block) c end; prism_test_def_node(7,7,1).inspect")
+
+ # splat
+ assert_prism_eval("def self.prism_test_def_node(a) a end; prism_test_def_node(*[1])")
+ assert_prism_eval("def self.prism_test_def_node(x,a) a end; prism_test_def_node(7,*[1])")
+ assert_prism_eval("def self.prism_test_def_node(x,y,a) a end; prism_test_def_node(7,7,*[1])")
+ assert_prism_eval("def self.prism_test_def_node(x,y,a,b,c) a end; prism_test_def_node(7,7,*[1,7,7])")
+
+ # recursive call
+ assert_prism_eval("def self.prism_test_def_node(n) n == 0 ? 1 : prism_test_def_node(n-1) end; prism_test_def_node(5)")
+
+ # instance method
+ assert_prism_eval("class PrismTestDefNode; def prism_test_def_node() 1 end end; PrismTestDefNode.new.prism_test_def_node")
+ assert_prism_eval("class PrismTestDefNode; def prism_test_def_node(*a) a end end; PrismTestDefNode.new.prism_test_def_node(1).inspect")
+
+ # block argument
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(&block) prism_test_def_node2(&block) end
+ def self.prism_test_def_node2() yield 1 end
+ prism_test_def_node2 {|a| a }
+ CODE
+
+ # multi argument
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(a, (b, *c, d))
+ [a, b, c, d]
+ end
+ prism_test_def_node("a", ["b", "c", "d"])
+ CODE
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(a, (b, c, *))
+ [a, b, c]
+ end
+ prism_test_def_node("a", ["b", "c"])
+ CODE
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(a, (*, b, c))
+ [a, b, c]
+ end
+ prism_test_def_node("a", ["b", "c"])
+ CODE
+
+ # recursive multis
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(a, (b, *c, (d, *e, f)))
+ [a, b, c, d, d, e, f]
+ end
+ prism_test_def_node("a", ["b", "c", ["d", "e", "f"]])
+ CODE
+
+ # Many arguments
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m)
+ [a, b, c, d, e, f, g, h, i, j, k, l, m]
+ end
+ prism_test_def_node(
+ "a",
+ ["b", "c1", "c2", "d"],
+ "e",
+ "f1", "f2",
+ "g",
+ ["h", "i1", "i2", "j"],
+ k: "k",
+ l: "l",
+ m1: "m1",
+ m2: "m2"
+ )
+ CODE
+ end
+
+ def test_pow_parameters
+ assert_prism_eval("def self.m(a, **); end; method(:m).parameters")
+ end
+
+ def test_star_parameters
+ assert_prism_eval("def self.m(a, *, b); end; method(:m).parameters")
+ end
+
+ def test_repeated_block_params
+ assert_prism_eval("def self.x(&blk); blk; end; x { |_, _, _ = 1, *_, _:, _: 2, **_, &_| }.parameters")
+ end
+
+ def test_repeated_proc_params
+ assert_prism_eval("proc {|_, _, _ = 1, *_, _:, _: 2, **_, &_| }.parameters")
+ end
+
+ def test_forward_parameters_block
+ assert_prism_eval("def self.m(&); end; method(:m).parameters")
+ end
+
+ def test_forward_parameters
+ assert_prism_eval("def self.m(...); end; method(:m).parameters")
+ end
+
+ def test_repeated_block_underscore
+ assert_prism_eval("def self.m(_, **_, &_); _; end; method(:m).parameters")
+ end
+
+ def test_repeated_kw_rest_underscore
+ assert_prism_eval("def self.m(_, **_); _; end; method(:m).parameters")
+ end
+
+ def test_repeated_required_keyword_underscore
+ assert_prism_eval("def self.m(_, _, *_, _, _:); _; end; method(:m).parameters")
+ assert_prism_eval("def self.m(_, _, *_, _, _:, _: 2); _; end; method(:m).parameters")
+ end
+
+ def test_repeated_required_post_underscore
+ assert_prism_eval("def self.m(_, _, *_, _); _; end; method(:m).parameters")
+ end
+
+ def test_repeated_splat_underscore
+ assert_prism_eval("def self.m(_, _, _ = 1, _ = 2, *_); end; method(:m).parameters")
+ end
+
+ def test_repeated_optional_underscore
+ assert_prism_eval("def self.m(a, _, _, _ = 1, _ = 2, b); end; method(:m).parameters")
+ end
+
+ def test_repeated_required_underscore
+ assert_prism_eval("def self.m(a, _, _, b); end; method(:m).parameters")
+ end
+
+ def test_locals_in_parameters
+ assert_prism_eval("def self.m(a = b = c = 1); [a, b, c]; end; self.m")
+ end
+
+ def test_trailing_comma_on_block
+ assert_prism_eval("def self.m; yield [:ok]; end; m {|v0,| v0 }")
+ end
+
+ def test_complex_default_params
+ assert_prism_eval("def self.foo(a:, b: '2'.to_i); [a, b]; end; foo(a: 1)")
+ assert_prism_eval("def self.foo(a:, b: 2, c: '3'.to_i); [a, b, c]; end; foo(a: 1)")
+ end
+
+ def test_numbered_params
+ assert_prism_eval("[1, 2, 3].then { _3 }")
+ assert_prism_eval("1.then { one = 1; one + _1 }")
+ end
+
+ def test_rescue_with_ensure
+ assert_prism_eval(<<-CODE)
+begin
+ begin
+ raise "a"
+ rescue
+ raise "b"
+ ensure
+ raise "c"
+ end
+rescue => e
+ e.message
+end
+ CODE
+ end
+
+ def test_required_kwarg_ordering
+ assert_prism_eval("def self.foo(a: 1, b:); [a, b]; end; foo(b: 2)")
+ end
+
+ def test_trailing_keyword_method_params
+ # foo(1, b: 2, c: 3) # argc -> 3
+ assert_prism_eval("def self.foo(a, b:, c:); [a, b, c]; end; foo(1, b: 2, c: 3)")
+ end
+
+ def test_keyword_method_params_only
+ # foo(a: 1, b: 2) # argc -> 2
+ assert_prism_eval("def self.foo(a:, b:); [a, b]; end; foo(a: 1, b: 2)")
+ end
+
+ def test_keyword_method_params_with_splat
+ # foo(a: 1, **b) # argc -> 1
+ assert_prism_eval("def self.foo(a:, b:); [a, b]; end; b = { b: 2 }; foo(a: 1, **b)")
+ end
+
+ def test_positional_and_splat_keyword_method_params
+ # foo(a, **b) # argc -> 2
+ assert_prism_eval("def self.foo(a, b); [a, b]; end; b = { b: 2 }; foo(1, **b)")
+ end
+
+ def test_positional_and_splat_method_params
+ # foo(a, *b, c, *d, e) # argc -> 2
+ assert_prism_eval("def self.foo(a, b, c, d, e); [a, b, c, d, e]; end; b = [2]; d = [4]; foo(1, *b, 3, *d, 5)")
+ end
+
+ def test_positional_with_splat_and_splat_keyword_method_params
+ # foo(a, *b, c, *d, **e) # argc -> 3
+ assert_prism_eval("def self.foo(a, b, c, d, e); [a, b, c, d, e]; end; b = [2]; d = [4]; e = { e: 5 }; foo(1, *b, 3, *d, **e)")
+ end
+
+ def test_positional_with_splat_and_keyword_method_params
+ # foo(a, *b, c, *d, e:) # argc -> 3
+ assert_prism_eval("def self.foo(a, b, c, d, e:); [a, b, c, d, e]; end; b = [2]; d = [4]; foo(1, *b, 3, *d, e: 5)")
+ end
+
+ def test_leading_splat_and_keyword_method_params
+ # foo(*a, b:) # argc -> 2
+ assert_prism_eval("def self.foo(a, b:); [a, b]; end; a = [1]; foo(*a, b: 2)")
+ end
+
+ def test_repeated_method_params
+ assert_prism_eval("def self.foo(_a, _a); _a; end; foo(1, 2)")
+ end
+
+ def test_splat_params_with_no_lefties
+ assert_prism_eval("def self.foo(v, (*)); v; end; foo(1, [2, 3, 4])")
+ end
+
+ def test_method_parameters
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_method_parameters(a, b=1, *c, d:, e: 2, **f, &g)
+ end
+
+ method(:prism_test_method_parameters).parameters
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_method_parameters(d:, e: 2, **f, &g)
+ end
+
+ method(:prism_test_method_parameters).parameters
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_method_parameters(**f, &g)
+ end
+
+ method(:prism_test_method_parameters).parameters
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_method_parameters(&g)
+ end
+
+ method(:prism_test_method_parameters).parameters
+ CODE
+ end
+
+ def test_LambdaNode
+ assert_prism_eval("-> { to_s }.call")
+ end
+
+ def test_LambdaNode_with_multiline_args
+ assert_prism_eval(<<-CODE)
+ -> (a,
+ b) {
+ a + b
+ }.call(1, 2)
+ CODE
+ end
+
+ def test_ModuleNode
+ assert_prism_eval("module M; end")
+ assert_prism_eval("module M::N; end")
+ assert_prism_eval("module ::O; end")
+ end
+
+ def test_ParenthesesNode
+ assert_prism_eval("()")
+ assert_prism_eval("(1)")
+ end
+
+ def test_PreExecutionNode
+ assert_prism_eval("BEGIN { a = 1 }; 2", raw: true)
+ assert_prism_eval("b = 2; BEGIN { a = 1 }; a + b", raw: true)
+ end
+
+ def test_PostExecutionNode
+ assert_prism_eval("END { 1 }")
+ assert_prism_eval("END { @b }; @b = 1")
+ assert_prism_eval("END { @b; 0 }; @b = 1")
+ assert_prism_eval("foo = 1; END { foo.nil? }")
+ assert_prism_eval("foo = 1; END { END { foo.nil? }}")
+ end
+
+ def test_ProgramNode
+ assert_prism_eval("")
+ assert_prism_eval("1")
+ end
+
+ def test_SingletonClassNode
+ assert_prism_eval("class << self; end")
+ end
+
+ def test_StatementsNode
+ assert_prism_eval("1")
+ end
+
+ def test_YieldNode
+ assert_prism_eval("def prism_test_yield_node; yield; end")
+ assert_prism_eval("def prism_test_yield_node; yield 1, 2; end")
+ assert_prism_eval("def prism_test_yield_node; yield **kw if condition; end")
+
+ # Test case where there's a call directly after the yield call
+ assert_prism_eval("def prism_test_yield_node; yield; 1; end")
+ assert_prism_eval("def prism_test_yield_node; yield 1, 2; 1; end")
+ end
+
+ ############################################################################
+ # Calls / arguments #
+ ############################################################################
+
+ def test_ArgumentsNode
+ # assert_prism_eval("[].push 1")
+ end
+
+ def test_BlockArgumentNode
+ assert_prism_eval("1.then(&:to_s)")
+
+ # Test anonymous block forwarding
+ assert_prism_eval(<<~RUBY)
+ o = Object.new
+ def o.foo(&) = yield
+ def o.bar(&) = foo(&)
+
+ o.bar { :ok }
+ RUBY
+ end
+
+ def test_BlockLocalVariableNode
+ assert_prism_eval(<<-CODE
+ pm_var = "outer scope variable"
+
+ 1.times { |;pm_var| pm_var = "inner scope variable"; pm_var }
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ pm_var = "outer scope variable"
+
+ 1.times { |;pm_var| pm_var = "inner scope variable"; pm_var }
+ pm_var
+ CODE
+ )
+ end
+
+ def test_CallNode
+ assert_prism_eval("to_s")
+
+ # with arguments
+ assert_prism_eval("eval '1'")
+
+ # with arguments and popped
+ assert_prism_eval("eval '1'; 1")
+
+ # With different types of calling arguments
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_call_node_double_splat(**); end
+ prism_test_call_node_double_splat(b: 1, **{})
+ CODE
+ assert_prism_eval(<<-CODE)
+ prism_test_call_node_double_splat(:b => 1)
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_call_node_splat(*); end
+ prism_test_call_node_splat(*[], 1)
+ CODE
+
+ assert_prism_eval("prism_test_call_node_splat(*[], 1, 2)")
+
+ assert_prism_eval(<<~RUBY)
+ def self.prism_test_call_node_splat_and_double_splat(a, b, **opts); end
+ prism_test_call_node_splat_and_double_splat(*[1], 2, **{})
+ RUBY
+
+ assert_prism_eval(<<-CODE)
+ class Foo
+ def []=(a, b)
+ 1234
+ end
+ end
+
+ def self.foo(i, j)
+ tbl = Foo.new
+ tbl[i] = j
+ end
+ foo(1, 2)
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ class Foo
+ def i=(a)
+ 1234
+ end
+ end
+
+ def self.foo(j)
+ tbl = Foo.new
+ tbl.i = j
+ end
+ foo(1)
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ foo = Object.new
+ def foo.[]=(k,v); 42; end
+ foo.[]=(1,2)
+ CODE
+
+ # With splat inside of []=
+ assert_prism_eval(<<~RUBY)
+ obj = Object.new
+ def obj.[]=(a, b); 10; end
+ obj[*[1]] = 3
+ RUBY
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_opt_var_trail_hash(a = nil, *b, c, **d); end
+ prism_opt_var_trail_hash("a")
+ prism_opt_var_trail_hash("a", c: 1)
+ prism_opt_var_trail_hash("a", "b")
+ prism_opt_var_trail_hash("a", "b", "c")
+ prism_opt_var_trail_hash("a", "b", "c", c: 1)
+ prism_opt_var_trail_hash("a", "b", "c", "c" => 0, c: 1)
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.foo(*args, **kwargs) = [args, kwargs]
+
+ [
+ foo(2 => 3),
+ foo([] => 42),
+ foo(a: 42, b: 61),
+ foo(1, 2, 3, a: 42, "b" => 61),
+ foo(:a => 42, :b => 61),
+ ]
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ class PrivateMethod
+ def initialize
+ self.instance_var
+ end
+ private
+ attr_accessor :instance_var
+ end
+ pm = PrivateMethod.new
+ pm.send(:instance_var)
+ CODE
+
+ # Testing safe navigation operator
+ assert_prism_eval(<<-CODE)
+ def self.test_prism_call_node
+ if [][0]&.first
+ 1
+ end
+ end
+ test_prism_call_node
+ CODE
+
+ # Specialized instructions
+ assert_prism_eval(%{-"literal"})
+ assert_prism_eval(%{"literal".freeze})
+ end
+
+ def test_CallAndWriteNode
+ assert_prism_eval(<<-CODE
+ class PrismTestSubclass; end
+ def PrismTestSubclass.test_call_and_write_node; end;
+ PrismTestSubclass.test_call_and_write_node &&= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ def PrismTestSubclass.test_call_and_write_node
+ "str"
+ end
+ def PrismTestSubclass.test_call_and_write_node=(val)
+ val
+ end
+ PrismTestSubclass.test_call_and_write_node &&= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ def self.test_call_and_write_node; end;
+ self.test_call_and_write_node &&= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ def self.test_call_and_write_node
+ "str"
+ end
+ def self.test_call_and_write_node=(val)
+ val
+ end
+ self.test_call_and_write_node &&= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE)
+ def self.test_prism_call_node; end
+ def self.test_prism_call_node=(val)
+ val
+ end
+ self&.test_prism_call_node &&= 1
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.test_prism_call_node
+ 2
+ end
+ def self.test_prism_call_node=(val)
+ val
+ end
+ self&.test_prism_call_node &&= 1
+ CODE
+ end
+
+ def test_CallOrWriteNode
+ assert_prism_eval(<<-CODE
+ class PrismTestSubclass; end
+ def PrismTestSubclass.test_call_or_write_node; end;
+ def PrismTestSubclass.test_call_or_write_node=(val)
+ val
+ end
+ PrismTestSubclass.test_call_or_write_node ||= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ def PrismTestSubclass.test_call_or_write_node
+ "str"
+ end
+ PrismTestSubclass.test_call_or_write_node ||= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ def self.test_call_or_write_node; end;
+ def self.test_call_or_write_node=(val)
+ val
+ end
+ self.test_call_or_write_node ||= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ def self.test_call_or_write_node
+ "str"
+ end
+ self.test_call_or_write_node ||= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE)
+ def self.test_prism_call_node
+ 2
+ end
+ def self.test_prism_call_node=(val)
+ val
+ end
+ self&.test_prism_call_node ||= 1
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.test_prism_call_node; end
+ def self.test_prism_call_node=(val)
+ val
+ end
+ self&.test_prism_call_node ||= 1
+ CODE
+ end
+
+ def test_CallOperatorWriteNode
+ assert_prism_eval(<<-CODE
+ class PrismTestSubclass; end
+ def PrismTestSubclass.test_call_operator_write_node
+ 2
+ end
+ def PrismTestSubclass.test_call_operator_write_node=(val)
+ val
+ end
+ PrismTestSubclass.test_call_operator_write_node += 1
+ CODE
+ )
+ end
+
+ def test_ForwardingArgumentsNode
+ assert_prism_eval(<<-CODE)
+ def prism_test_forwarding_arguments_node(...); end;
+ def prism_test_forwarding_arguments_node1(...)
+ prism_test_forwarding_arguments_node(...)
+ end
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def prism_test_forwarding_arguments_node(...); end;
+ def prism_test_forwarding_arguments_node1(a, ...)
+ prism_test_forwarding_arguments_node(1,2, 3, ...)
+ end
+ CODE
+
+ assert_prism_eval(<<~RUBY)
+ o = Object.new
+ def o.bar(a, b, c) = [a, b, c]
+ def o.foo(...) = 1.times { bar(...) }
+
+ o.foo(1, 2, 3)
+ 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")
+ assert_prism_eval(<<-CODE)
+ class A
+ def initialize(a, b)
+ end
+ end
+
+ class B < A
+ attr_reader :res
+ def initialize(a, b, *)
+ super
+ @res = [a, b]
+ end
+ end
+
+ B.new(1, 2).res
+ CODE
+ end
+
+ def test_KeywordHashNode
+ assert_prism_eval("[a: [:b, :c]]")
+ end
+
+ def test_SuperNode
+ assert_prism_eval("def to_s; super 1; end")
+ assert_prism_eval("def to_s; super(); end")
+ assert_prism_eval("def to_s; super('a', :b, [1,2,3]); end")
+ assert_prism_eval("def to_s; super(1, 2, 3, &:foo); end")
+ end
+
+ ############################################################################
+ # Methods / parameters #
+ ############################################################################
+
+ def test_AliasGlobalVariableNode
+ assert_prism_eval("alias $prism_foo $prism_bar")
+ end
+
+ def test_AliasMethodNode
+ assert_prism_eval("alias :prism_a :to_s")
+ end
+
+ def test_BlockParameterNode
+ assert_prism_eval("def prism_test_block_parameter_node(&bar) end")
+ assert_prism_eval("->(b, c=1, *d, e, &f){}")
+
+ # Test BlockParameterNode with no name
+ assert_prism_eval("->(&){}")
+ assert_prism_eval("def prism_test_block_parameter_node(&); end")
+ end
+
+ def test_BlockParametersNode
+ assert_prism_eval("Object.tap { || }")
+ assert_prism_eval("[1].map { |num| num }")
+ assert_prism_eval("[1].map { |a; b| b = 2; a + b}")
+
+ # Test block parameters with multiple _
+ assert_prism_eval(<<~RUBY)
+ [[1, 2, 3, 4, 5, 6]].map { |(_, _, _, _, _, _)| _ }
+ RUBY
+ end
+
+ def test_FowardingParameterNode
+ assert_prism_eval("def prism_test_forwarding_parameter_node(...); end")
+ end
+
+ def test_KeywordRestParameterNode
+ assert_prism_eval("def prism_test_keyword_rest_parameter_node(a, **b); end")
+ assert_prism_eval("Object.tap { |**| }")
+
+ # Test that KeywordRestParameterNode creates a copy
+ assert_prism_eval(<<~RUBY)
+ hash = {}
+ o = Object.new
+ def o.foo(**a) = a[:foo] = 1
+
+ o.foo(**hash)
+ hash
+ RUBY
+ end
+
+ def test_NoKeywordsParameterNode
+ assert_prism_eval("def prism_test_no_keywords(**nil); end")
+ assert_prism_eval("def prism_test_no_keywords(a, b = 2, **nil); end")
+ end
+
+ def test_OptionalParameterNode
+ assert_prism_eval("def prism_test_optional_param_node(bar = nil); end")
+ end
+
+ def test_OptionalKeywordParameterNode
+ assert_prism_eval("def prism_test_optional_keyword_param_node(bar: nil); end")
+
+ # Test with optional argument and method call in OptionalKeywordParameterNode
+ assert_prism_eval(<<~RUBY)
+ o = Object.new
+ def o.foo = 1
+ def o.bar(a = nil, b: foo) = b
+ o.bar
+ RUBY
+ end
+
+ def test_ParametersNode
+ assert_prism_eval("def prism_test_parameters_node(bar, baz); end")
+ assert_prism_eval("def prism_test_parameters_node(a, b = 2); end")
+ end
+
+ def test_RequiredParameterNode
+ assert_prism_eval("def prism_test_required_param_node(bar); end")
+ assert_prism_eval("def prism_test_required_param_node(foo, bar); end")
+ end
+
+ def test_RequiredKeywordParameterNode
+ assert_prism_eval("def prism_test_required_param_node(bar:); end")
+ assert_prism_eval("def prism_test_required_param_node(foo:, bar:); end")
+ assert_prism_eval("-> a, b = 1, c:, d:, &e { a }")
+ end
+
+ def test_RestParameterNode
+ assert_prism_eval("def prism_test_rest_parameter_node(*a); end")
+ end
+
+ def test_UndefNode
+ assert_prism_eval("def prism_undef_node_1; end; undef prism_undef_node_1")
+ assert_prism_eval(<<-HERE
+ def prism_undef_node_2
+ end
+ def prism_undef_node_3
+ end
+ undef prism_undef_node_2, prism_undef_node_3
+ HERE
+ )
+ assert_prism_eval(<<-HERE
+ def prism_undef_node_4
+ end
+ undef :'prism_undef_node_#{4}'
+ HERE
+ )
+ end
+
+ ############################################################################
+ # Pattern matching #
+ ############################################################################
+
+ def test_AlternationPatternNode
+ assert_prism_eval("1 in 1 | 2")
+ assert_prism_eval("1 in 2 | 1")
+ assert_prism_eval("1 in 2 | 3 | 4 | 1")
+ assert_prism_eval("1 in 2 | 3")
+ end
+
+ def test_ArrayPatternNode
+ assert_prism_eval("[] => []")
+
+ ["in", "=>"].each do |operator|
+ ["", "Array"].each do |constant|
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, 3]")
+
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, *]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, *]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, 3, *]")
+
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*foo]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, *foo]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, *foo]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, 3, *foo]")
+
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*, 3]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*, 2, 3]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*, 1, 2, 3]")
+
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*foo, 3]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*foo, 2, 3]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*foo, 1, 2, 3]")
+ end
+ end
+
+ assert_prism_eval("begin; Object.new => [1, 2, 3]; rescue NoMatchingPatternError; true; end")
+ assert_prism_eval("begin; [1, 2, 3] => Object[1, 2, 3]; rescue NoMatchingPatternError; true; end")
+ end
+
+ def test_CapturePatternNode
+ assert_prism_eval("[1] => [Integer => foo]")
+ end
+
+ def test_CaseMatchNode
+ assert_prism_eval(<<~RUBY)
+ case [1, 2, 3]
+ in [1, 2, 3]
+ 4
+ end
+ RUBY
+
+ assert_prism_eval(<<~RUBY)
+ case { a: 5, b: 6 }
+ in [1, 2, 3]
+ 4
+ in { a: 5, b: 6 }
+ 7
+ end
+ RUBY
+
+ assert_prism_eval(<<~RUBY)
+ case [1, 2, 3, 4]
+ in [1, 2, 3]
+ 4
+ in { a: 5, b: 6 }
+ 7
+ else
+ end
+ RUBY
+
+ assert_prism_eval(<<~RUBY)
+ case [1, 2, 3, 4]
+ in [1, 2, 3]
+ 4
+ in { a: 5, b: 6 }
+ 7
+ else
+ 8
+ end
+ RUBY
+
+ assert_prism_eval(<<~RUBY)
+ case [1, 2, 3]
+ in [1, 2, 3] unless to_s
+ in [1, 2, 3] if to_s.nil?
+ in [1, 2, 3]
+ true
+ end
+ RUBY
+ end
+
+ def test_FindPatternNode
+ ["in", "=>"].each do |operator|
+ ["", "Array"].each do |constant|
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, 3, 4, 5, *]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 5, *]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 2, 3, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, 4, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 4, 5, *]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, 3, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 2, 3, 4, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, 4, 5, *]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, 3, 4, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 2, 3, 4, 5, *]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, 4, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, 4, 5, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 1, 2, 3, 4, *]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, *foo]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, 4, *foo]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, 4, 5, *foo]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, 3, 4, *foo]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, *bar]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, 4, *bar]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, 4, 5, *bar]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 1, 2, 3, 4, *bar]")
+ end
+ end
+
+ assert_prism_eval("[1, [2, [3, [4, [5]]]]] => [*, [*, [*, [*, [*]]]]]")
+ assert_prism_eval("[1, [2, [3, [4, [5]]]]] => [1, [2, [3, [4, [5]]]]]")
+
+ assert_prism_eval("begin; Object.new => [*, 2, *]; rescue NoMatchingPatternError; true; end")
+ assert_prism_eval("begin; [1, 2, 3] => Object[*, 2, *]; rescue NoMatchingPatternError; true; end")
+ end
+
+ def test_HashPatternNode
+ assert_prism_eval("{} => {}")
+
+ [["{ ", " }"], ["Hash[", "]"]].each do |(prefix, suffix)|
+ assert_prism_eval("{} => #{prefix} **nil #{suffix}")
+
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1 #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2 #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} b: 2, c: 3 #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, c: 3 #{suffix}")
+
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} ** #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, ** #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, ** #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} b: 2, c: 3, ** #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, c: 3, ** #{suffix}")
+
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} **foo #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, **foo #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, **foo #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} b: 2, c: 3, **foo #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, c: 3, **foo #{suffix}")
+
+ assert_prism_eval("{ a: 1 } => #{prefix} a: 1, **nil #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, c: 3, **nil #{suffix}")
+ end
+
+ assert_prism_eval("{ a: { b: { c: 1 } } } => { a: { b: { c: 1 } } }")
+ end
+
+ def test_MatchPredicateNode
+ assert_prism_eval("1 in 1")
+ assert_prism_eval("1.0 in 1.0")
+ assert_prism_eval("1i in 1i")
+ assert_prism_eval("1r in 1r")
+
+ assert_prism_eval("\"foo\" in \"foo\"")
+ assert_prism_eval("\"foo \#{1}\" in \"foo \#{1}\"")
+
+ assert_prism_eval("false in false")
+ assert_prism_eval("nil in nil")
+ assert_prism_eval("self in self")
+ assert_prism_eval("true in true")
+
+ assert_prism_eval("5 in 0..10")
+ assert_prism_eval("5 in 0...10")
+
+ assert_prism_eval("[\"5\"] in %w[5]")
+
+ assert_prism_eval("Prism in Prism")
+ assert_prism_eval("Prism in ::Prism")
+
+ assert_prism_eval(":prism in :prism")
+ assert_prism_eval("%s[prism\#{1}] in %s[prism\#{1}]")
+ assert_prism_eval("\"foo\" in /.../")
+ assert_prism_eval("\"foo1\" in /...\#{1}/")
+ assert_prism_eval("4 in ->(v) { v.even? }")
+
+ assert_prism_eval("5 in foo")
+
+ assert_prism_eval("1 in 2")
+
+ # Bug: https://bugs.ruby-lang.org/issues/20956
+ assert_prism_eval("1 in [1 | [1]]")
+ end
+
+ def test_MatchRequiredNode
+ assert_prism_eval("1 => 1")
+ assert_prism_eval("1.0 => 1.0")
+ assert_prism_eval("1i => 1i")
+ assert_prism_eval("1r => 1r")
+
+ assert_prism_eval("\"foo\" => \"foo\"")
+ assert_prism_eval("\"foo \#{1}\" => \"foo \#{1}\"")
+
+ assert_prism_eval("false => false")
+ assert_prism_eval("nil => nil")
+ assert_prism_eval("true => true")
+
+ assert_prism_eval("5 => 0..10")
+ assert_prism_eval("5 => 0...10")
+
+ assert_prism_eval("[\"5\"] => %w[5]")
+
+ assert_prism_eval(":prism => :prism")
+ assert_prism_eval("%s[prism\#{1}] => %s[prism\#{1}]")
+ assert_prism_eval("\"foo\" => /.../")
+ assert_prism_eval("\"foo1\" => /...\#{1}/")
+ assert_prism_eval("4 => ->(v) { v.even? }")
+
+ assert_prism_eval("5 => foo")
+ end
+
+ def test_PinnedExpressionNode
+ assert_prism_eval("4 in ^(4)")
+ end
+
+ def test_PinnedVariableNode
+ assert_prism_eval("module Prism; @@prism = 1; 1 in ^@@prism; end")
+ assert_prism_eval("module Prism; @prism = 1; 1 in ^@prism; end")
+ assert_prism_eval("$prism = 1; 1 in ^$prism")
+ assert_prism_eval("prism = 1; 1 in ^prism")
+ assert_prism_eval("[1].each { 1 => ^it }")
+ end
+
+ ############################################################################
+ # Miscellaneous #
+ ############################################################################
+
+ def test_eval
+ assert_prism_eval("eval('1 + 1')", raw: true)
+ assert_prism_eval("a = 1; eval('a + 1')", raw: true)
+
+ assert_prism_eval(<<~CODE, raw: true)
+ def prism_eval_splat(**bar)
+ eval("bar")
+ end
+ prism_eval_splat(bar: 10)
+ CODE
+
+ assert_prism_eval(<<~CODE, raw: true)
+ def prism_eval_keywords(baz:)
+ eval("baz")
+ end
+ prism_eval_keywords(baz: 10)
+ CODE
+
+ assert_prism_eval(<<~CODE, raw: true)
+ [1].each do |a|
+ [2].each do |b|
+ c = 3
+ eval("a + b + c")
+ end
+ end
+ CODE
+
+ assert_prism_eval(<<~CODE, raw: true)
+ def prism_eval_binding(b)
+ eval("bar", b)
+ end
+
+ bar = :ok
+ prism_eval_binding(binding)
+ CODE
+ end
+
+ def test_ScopeNode
+ assert_separately(%w[], <<~'RUBY')
+ def compare_eval(source)
+ ruby_eval = RubyVM::InstructionSequence.compile("module A; " + source + "; end").eval
+ prism_eval = RubyVM::InstructionSequence.compile_prism("module B; " + source + "; end").eval
+
+ assert_equal ruby_eval, prism_eval
+ end
+
+ def assert_prism_eval(source)
+ $VERBOSE, verbose_bak = nil, $VERBOSE
+
+ begin
+ compare_eval(source)
+
+ # Test "popped" functionality
+ compare_eval("#{source}; 1")
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ end
+
+ assert_prism_eval("a = 1; 1.times do; { a: }; end")
+ assert_prism_eval("a = 1; def foo(a); a; end")
+ RUBY
+ end
+
+ ############################################################################
+ # Errors #
+ ############################################################################
+
+ def test_MissingNode
+ # TODO
+ end
+
+ ############################################################################
+ # Encoding #
+ ############################################################################
+
+ def test_encoding
+ assert_prism_eval('"però"')
+ assert_prism_eval(":però")
+ end
+
+ def test_parse_file
+ assert_nothing_raised do
+ RubyVM::InstructionSequence.compile_file_prism(__FILE__)
+ end
+
+ error = assert_raise Errno::ENOENT do
+ RubyVM::InstructionSequence.compile_file_prism("idontexist.rb")
+ end
+
+ assert_equal "No such file or directory - idontexist.rb", error.message
+
+ assert_raise TypeError do
+ RubyVM::InstructionSequence.compile_file_prism(nil)
+ end
+ end
+
+ private
+
+ def compare_eval(source, raw:, location:)
+ source = raw ? source : "class Prism::TestCompilePrism\n#{source}\nend"
+
+ ruby_eval = RubyVM::InstructionSequence.compile_parsey(source).eval
+ prism_eval = RubyVM::InstructionSequence.compile_prism(source).eval
+
+ if ruby_eval.is_a? Proc
+ assert_equal ruby_eval.class, prism_eval.class, "@#{location.path}:#{location.lineno}"
+ else
+ assert_equal ruby_eval, prism_eval, "@#{location.path}:#{location.lineno}"
+ end
+ end
+
+ def assert_prism_eval(source, raw: false)
+ location = caller_locations(1, 1).first
+ $VERBOSE, verbose_bak = nil, $VERBOSE
+
+ begin
+ compare_eval(source, raw:, location:)
+
+ # Test "popped" functionality
+ compare_eval("#{source}; 1", raw:, location:)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ end
+ end
+end
diff --git a/test/ruby/test_complex.rb b/test/ruby/test_complex.rb
index 316e3e21ff..bb131cee91 100644
--- a/test/ruby/test_complex.rb
+++ b/test/ruby/test_complex.rb
@@ -123,6 +123,10 @@ class Complex_Test < Test::Unit::TestCase
assert_raise(TypeError){Complex(Object.new)}
assert_raise(ArgumentError){Complex()}
assert_raise(ArgumentError){Complex(1,2,3)}
+ c = Complex(1,0)
+ assert_same(c, Complex(c))
+ assert_same(c, Complex(c, exception: false))
+ assert_raise(ArgumentError){Complex(c, bad_keyword: true)}
if (0.0/0).nan?
assert_nothing_raised{Complex(0.0/0)}
@@ -216,6 +220,17 @@ class Complex_Test < Test::Unit::TestCase
def test_polar
assert_equal([1,2], Complex.polar(1,2).polar)
assert_equal(Complex.polar(1.0, Math::PI * 2 / 3), Complex.polar(1, Math::PI * 2 / 3))
+
+ one = 1+0i
+ c = Complex.polar(0, one)
+ assert_equal(0, c)
+ assert_predicate(c.real, :real?)
+ c = Complex.polar(one, 0)
+ assert_equal(1, c)
+ assert_predicate(c.real, :real?)
+ c = Complex.polar(one)
+ assert_equal(1, c)
+ assert_predicate(c.real, :real?)
end
def test_uplus
@@ -269,6 +284,39 @@ class Complex_Test < Test::Unit::TestCase
assert_equal(Complex(Rational(5,3),Rational(2)), c + Rational(2,3))
end
+ def test_add_with_redefining_int_plus
+ assert_in_out_err([], <<-'end;', ['true'], [])
+ class Integer
+ remove_method :+
+ def +(other); 42; end
+ end
+ a = Complex(1, 2) + Complex(0, 1)
+ puts a == Complex(42, 42)
+ end;
+ end
+
+ def test_add_with_redefining_float_plus
+ assert_in_out_err([], <<-'end;', ['true'], [])
+ class Float
+ remove_method :+
+ def +(other); 42.0; end
+ end
+ a = Complex(1.0, 2.0) + Complex(0, 1)
+ puts a == Complex(42.0, 42.0)
+ end;
+ end
+
+ def test_add_with_redefining_rational_plus
+ assert_in_out_err([], <<-'end;', ['true'], [])
+ class Rational
+ remove_method :+
+ def +(other); 355/113r; end
+ end
+ a = Complex(1r, 2r) + Complex(0, 1)
+ puts a == Complex(355/113r, 355/113r)
+ end;
+ end
+
def test_sub
c = Complex(1,2)
c2 = Complex(2,3)
@@ -282,6 +330,39 @@ class Complex_Test < Test::Unit::TestCase
assert_equal(Complex(Rational(1,3),Rational(2)), c - Rational(2,3))
end
+ def test_sub_with_redefining_int_minus
+ assert_in_out_err([], <<-'end;', ['true'], [])
+ class Integer
+ remove_method :-
+ def -(other); 42; end
+ end
+ a = Complex(1, 2) - Complex(0, 1)
+ puts a == Complex(42, 42)
+ end;
+ end
+
+ def test_sub_with_redefining_float_minus
+ assert_in_out_err([], <<-'end;', ['true'], [])
+ class Float
+ remove_method :-
+ def -(other); 42.0; end
+ end
+ a = Complex(1.0, 2.0) - Complex(0, 1)
+ puts a == Complex(42.0, 42.0)
+ end;
+ end
+
+ def test_sub_with_redefining_rational_minus
+ assert_in_out_err([], <<-'end;', ['true'], [])
+ class Rational
+ remove_method :-
+ def -(other); 355/113r; end
+ end
+ a = Complex(1r, 2r) - Complex(0, 1)
+ puts a == Complex(355/113r, 355/113r)
+ end;
+ end
+
def test_mul
c = Complex(1,2)
c2 = Complex(2,3)
@@ -300,6 +381,42 @@ class Complex_Test < Test::Unit::TestCase
c = Complex(0, Float::INFINITY)
assert_equal(Complex(0, Float::INFINITY), c * Complex(1, 0))
assert_equal(Complex(-Float::INFINITY, 0), c * Complex(0, 1))
+
+ assert_equal(Complex(-0.0, -0.0), Complex(-0.0, 0) * Complex(0, 0))
+ end
+
+ def test_mul_with_redefining_int_mult
+ assert_in_out_err([], <<-'end;', ['true'], [])
+ class Integer
+ remove_method :*
+ def *(other); 42; end
+ end
+ a = Complex(2, 0) * Complex(1, 2)
+ puts a == Complex(0, 84)
+ end;
+ end
+
+ def test_mul_with_redefining_float_mult
+ assert_in_out_err([], <<-'end;', ['true'], [])
+ class Float
+ remove_method :*
+ def *(other); 42.0; end
+ end
+ a = Complex(2.0, 0.0) * Complex(1, 2)
+ puts a == Complex(0.0, 84.0)
+ end;
+ end
+
+
+ def test_mul_with_redefining_rational_mult
+ assert_in_out_err([], <<-'end;', ['true'], [])
+ class Rational
+ remove_method :*
+ def *(other); 355/113r; end
+ end
+ a = Complex(2r, 0r) * Complex(1, 2)
+ puts a == Complex(0r, 2*355/113r)
+ end;
end
def test_div
@@ -323,6 +440,15 @@ class Complex_Test < Test::Unit::TestCase
assert_equal(Complex(Rational(1,2),Rational(1)), c / Rational(2))
assert_equal(Complex(Rational(3,2),Rational(3)), c / Rational(2,3))
+
+ c = Complex(1)
+ [ 1, Rational(1), c ].each do |d|
+ r = c / d
+ assert_instance_of(Complex, r)
+ assert_equal(1, r)
+ assert_predicate(r.real, :integer?)
+ assert_predicate(r.imag, :integer?)
+ end
end
def test_quo
@@ -402,10 +528,86 @@ class Complex_Test < Test::Unit::TestCase
assert_in_delta(-0.393, r.imag, 0.001)
end
+ def test_expt_for_special_angle
+ c = Complex(1, 0) ** 100000000000000000000000000000000
+ assert_equal(Complex(1, 0), c)
+
+ c = Complex(-1, 0) ** 10000000000000000000000000000000
+ assert_equal(Complex(1, 0), c)
+
+ c = Complex(-1, 0) ** 10000000000000000000000000000001
+ assert_equal(Complex(-1, 0), c)
+
+ c = Complex(0, 1) ** 100000000000000000000000000000000
+ assert_equal(Complex(1, 0), c)
+
+ c = Complex(0, 1) ** 100000000000000000000000000000001
+ assert_equal(Complex(0, 1), c)
+
+ c = Complex(0, 1) ** 100000000000000000000000000000002
+ assert_equal(Complex(-1, 0), c)
+
+ c = Complex(0, 1) ** 100000000000000000000000000000003
+ assert_equal(Complex(0, -1), c)
+
+ c = Complex(0, -1) ** 100000000000000000000000000000000
+ assert_equal(Complex(1, 0), c)
+
+ c = Complex(0, -1) ** 100000000000000000000000000000001
+ assert_equal(Complex(0, -1), c)
+
+ c = Complex(0, -1) ** 100000000000000000000000000000002
+ assert_equal(Complex(-1, 0), c)
+
+ c = Complex(0, -1) ** 100000000000000000000000000000003
+ assert_equal(Complex(0, 1), c)
+
+ c = Complex(1, 1) ** 1
+ assert_equal(Complex(1, 1), c)
+
+ c = Complex(1, 1) ** 2
+ assert_equal(Complex(0, 2), c)
+
+ c = Complex(1, 1) ** 3
+ assert_equal(Complex(-2, 2), c)
+
+ c = Complex(1, 1) ** 4
+ assert_equal(Complex(-4, 0), c)
+
+ c = Complex(1, 1) ** 5
+ assert_equal(Complex(-4, -4), c)
+
+ c = Complex(1, 1) ** 6
+ assert_equal(Complex(0, -8), c)
+
+ c = Complex(1, 1) ** 7
+ assert_equal(Complex(8, -8), c)
+
+ c = Complex(-2, -2) ** 3
+ assert_equal(Complex(16, -16), c)
+
+ c = Complex(2, -2) ** 3
+ assert_equal(Complex(-16, -16), c)
+
+ c = Complex(-2, 2) ** 3
+ assert_equal(Complex(16, 16), c)
+
+ c = Complex(0.0, -888888888888888.0)**8888
+ assert_not_predicate(c.real, :nan?)
+ assert_not_predicate(c.imag, :nan?)
+ end
+
def test_cmp
- assert_raise(NoMethodError){1 <=> Complex(1,1)}
- assert_raise(NoMethodError){Complex(1,1) <=> 1}
- assert_raise(NoMethodError){Complex(1,1) <=> Complex(1,1)}
+ assert_nil(Complex(5, 1) <=> Complex(2))
+ assert_nil(5 <=> Complex(2, 1))
+
+ assert_equal(1, Complex(5) <=> Complex(2))
+ assert_equal(-1, Complex(2) <=> Complex(3))
+ assert_equal(0, Complex(2) <=> Complex(2))
+
+ assert_equal(1, Complex(5) <=> 2)
+ assert_equal(-1, Complex(2) <=> 3)
+ assert_equal(0, Complex(2) <=> 2)
end
def test_eqeq
@@ -436,20 +638,24 @@ class Complex_Test < Test::Unit::TestCase
assert_raise_with_message(TypeError, /C\u{1f5ff}/) { Complex(1).coerce(obj) }
end
- class ObjectX
- def +(x) Rational(1) end
+ class ObjectX < Numeric
+ def initialize(real = true, n = 1) @n = n; @real = real; end
+ def +(x) Rational(@n) end
alias - +
alias * +
alias / +
alias quo +
alias ** +
- def coerce(x) [x, Complex(1)] end
+ def coerce(x) [x, Complex(@n)] end
+ def real?; @real; end
end
def test_coerce2
x = ObjectX.new
- %w(+ - * / quo **).each do |op|
- assert_kind_of(Numeric, Complex(1).__send__(op, x))
+ y = ObjectX.new(false)
+ %w(+ - * / quo ** <=>).each do |op|
+ assert_kind_of(Numeric, Complex(1).__send__(op, x), op)
+ assert_kind_of(Numeric, Complex(1).__send__(op, y), op)
end
end
@@ -535,6 +741,17 @@ class Complex_Test < Test::Unit::TestCase
assert_equal('(1+2i)', c.inspect)
end
+ def test_inspect_to_s_frozen_bug_20337
+ assert_separately([], <<~'RUBY')
+ class Numeric
+ def inspect = super.freeze
+ end
+ c = Complex(Numeric.new, 1)
+ assert_match(/\A\(#<Numeric:/, c.inspect)
+ assert_match(/\A#<Numeric:/, c.to_s)
+ RUBY
+ end
+
def test_marshal
c = Complex(1,2)
@@ -712,20 +929,42 @@ class Complex_Test < Test::Unit::TestCase
assert_equal(Complex(0), '_5'.to_c)
assert_equal(Complex(5), '5_'.to_c)
assert_equal(Complex(5), '5x'.to_c)
+ assert_equal(Complex(51), '5_1'.to_c)
+ assert_equal(Complex(5), '5__1'.to_c)
assert_equal(Complex(5), '5+_3i'.to_c)
assert_equal(Complex(5), '5+3_i'.to_c)
assert_equal(Complex(5,3), '5+3i_'.to_c)
assert_equal(Complex(5,3), '5+3ix'.to_c)
+ assert_equal(Complex(5,31), '5+3_1i'.to_c)
+ assert_equal(Complex(5), '5+3__1i'.to_c)
+ assert_equal(Complex(51), Complex('5_1'))
+ assert_equal(Complex(5,31), Complex('5+3_1i'))
+ assert_equal(Complex(5,31), Complex('5+3_1I'))
+ assert_equal(Complex(5,31), Complex('5+3_1j'))
+ assert_equal(Complex(5,31), Complex('5+3_1J'))
+ assert_equal(Complex(0,31), Complex('3_1i'))
+ assert_equal(Complex(0,31), Complex('3_1I'))
+ assert_equal(Complex(0,31), Complex('3_1j'))
+ assert_equal(Complex(0,31), Complex('3_1J'))
assert_raise(ArgumentError){ Complex('')}
assert_raise(ArgumentError){ Complex('_')}
assert_raise(ArgumentError){ Complex("\f\n\r\t\v5\0")}
assert_raise(ArgumentError){ Complex('_5')}
assert_raise(ArgumentError){ Complex('5_')}
+ assert_raise(ArgumentError){ Complex('5__1')}
assert_raise(ArgumentError){ Complex('5x')}
assert_raise(ArgumentError){ Complex('5+_3i')}
assert_raise(ArgumentError){ Complex('5+3_i')}
assert_raise(ArgumentError){ Complex('5+3i_')}
assert_raise(ArgumentError){ Complex('5+3ix')}
+ assert_raise(ArgumentError){ Complex('5+3__1i')}
+ assert_raise(ArgumentError){ Complex('5+3__1I')}
+ assert_raise(ArgumentError){ Complex('5+3__1j')}
+ assert_raise(ArgumentError){ Complex('5+3__1J')}
+ assert_raise(ArgumentError){ Complex('3__1i')}
+ assert_raise(ArgumentError){ Complex('3__1I')}
+ assert_raise(ArgumentError){ Complex('3__1j')}
+ assert_raise(ArgumentError){ Complex('3__1J')}
assert_equal(Complex(Rational(1,5)), '1/5'.to_c)
assert_equal(Complex(Rational(-1,5)), '-1/5'.to_c)
@@ -746,10 +985,38 @@ class Complex_Test < Test::Unit::TestCase
end
+ def test_Complex_with_invalid_exception
+ assert_raise(ArgumentError) {
+ Complex("0", exception: 1)
+ }
+ end
+
+ def assert_complex_with_exception(error, *args, message: "")
+ assert_raise(error, message) do
+ Complex(*args, exception: true)
+ end
+ assert_nothing_raised(error, message) do
+ assert_nil(Complex(*args, exception: false))
+ assert_nil($!)
+ end
+ end
+
+ def test_Complex_with_exception
+ assert_complex_with_exception(ArgumentError, '5x')
+ assert_complex_with_exception(TypeError, nil)
+ assert_complex_with_exception(TypeError, Object.new)
+ assert_complex_with_exception(TypeError, 1, nil)
+ assert_complex_with_exception(TypeError, 1, Object.new)
+
+ o = Object.new
+ def o.to_c; raise; end
+ assert_complex_with_exception(RuntimeError, o)
+ assert_complex_with_exception(TypeError, 1, o)
+ end
+
def test_respond
c = Complex(1,1)
assert_not_respond_to(c, :%)
- assert_not_respond_to(c, :<=>)
assert_not_respond_to(c, :div)
assert_not_respond_to(c, :divmod)
assert_not_respond_to(c, :floor)
@@ -798,6 +1065,29 @@ class Complex_Test < Test::Unit::TestCase
assert_raise(RangeError){Rational(Complex(3,2))}
end
+ def test_to_r_with_float
+ assert_equal(Rational(3), Complex(3, 0.0).to_r)
+ assert_raise(RangeError){Complex(3, 1.0).to_r}
+ end
+
+ def test_to_r_with_numeric_obj
+ c = Class.new(Numeric)
+
+ num = 0
+ c.define_method(:to_s) { num.to_s }
+ c.define_method(:==) { num == it }
+ c.define_method(:<) { num < it }
+
+ o = c.new
+ assert_equal(Rational(3), Complex(3, o).to_r)
+
+ num = 1
+ assert_raise(RangeError){Complex(3, o).to_r}
+
+ c.define_method(:to_r) { 0r }
+ assert_equal(Rational(3), Complex(3, o).to_r)
+ end
+
def test_to_c
c = nil.to_c
assert_equal([0,0], [c.real, c.imag])
@@ -958,4 +1248,49 @@ class Complex_Test < Test::Unit::TestCase
def test_known_bug
end
+ def test_canonicalize_internal
+ obj = Class.new(Numeric) do
+ attr_accessor :real
+ alias real? real
+ end.new
+ obj.real = true
+ c = Complex.rect(obj, 1);
+ obj.real = false
+ c = c.conj
+ assert_equal(obj, c.real)
+ assert_equal(-1, c.imag)
+ end
+
+ def test_canonicalize_polar
+ error = "not a real"
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(1i)
+ end
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(1i, 0)
+ end
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(0, 1i)
+ end
+ n = Class.new(Numeric) do
+ def initialize(x = 1)
+ @x = x
+ end
+ def real?
+ (@x -= 1) > 0
+ end
+ end
+ obj = n.new
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(obj)
+ end
+ obj = n.new
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(obj, 0)
+ end
+ obj = n.new
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(1, obj)
+ end
+ end
end
diff --git a/test/ruby/test_complex2.rb b/test/ruby/test_complex2.rb
index 594fc3f45a..b89e83efb2 100644
--- a/test/ruby/test_complex2.rb
+++ b/test/ruby/test_complex2.rb
@@ -4,7 +4,7 @@ require 'test/unit'
class Complex_Test2 < Test::Unit::TestCase
def test_kumi
- skip unless defined?(Rational)
+ omit unless defined?(Rational)
assert_equal(Complex(1, 0), +Complex(1, 0))
assert_equal(Complex(-1, 0), -Complex(1, 0))
diff --git a/test/ruby/test_complexrational.rb b/test/ruby/test_complexrational.rb
index 0360f5ee42..31d11fe317 100644
--- a/test/ruby/test_complexrational.rb
+++ b/test/ruby/test_complexrational.rb
@@ -4,7 +4,7 @@ require 'test/unit'
class ComplexRational_Test < Test::Unit::TestCase
def test_rat_srat
- skip unless defined?(Rational)
+ omit unless defined?(Rational)
c = SimpleRat(1,3)
cc = Rational(3,2)
@@ -89,7 +89,7 @@ class ComplexRational_Test < Test::Unit::TestCase
end
def test_comp_srat
- skip unless defined?(Rational)
+ omit unless defined?(Rational)
c = Complex(SimpleRat(2,3),SimpleRat(1,2))
cc = Complex(Rational(3,2),Rational(2,1))
@@ -102,7 +102,7 @@ class ComplexRational_Test < Test::Unit::TestCase
assert_equal(Complex(SimpleRat(4,3),SimpleRat(1,1)), c * 2)
assert_equal(Complex(SimpleRat(1,3),SimpleRat(1,4)), c / 2)
assert_equal(Complex(SimpleRat(7,36),SimpleRat(2,3)), c ** 2)
- assert_raise(NoMethodError){c <=> 2}
+ assert_nil(c <=> 2)
assert_equal(Complex(SimpleRat(8,3),SimpleRat(1,2)), 2 + c)
assert_equal(Complex(SimpleRat(4,3),SimpleRat(-1,2)), 2 - c)
@@ -111,7 +111,7 @@ class ComplexRational_Test < Test::Unit::TestCase
r = 2 ** c
assert_in_delta(1.4940, r.real, 0.001)
assert_in_delta(0.5392, r.imag, 0.001)
- assert_raise(NoMethodError){2 <=> c}
+ assert_nil(2 <=> c)
assert_equal(Complex(SimpleRat(13,6),SimpleRat(5,2)), c + cc)
assert_equal(Complex(SimpleRat(-5,6),SimpleRat(-3,2)), c - cc)
@@ -120,7 +120,7 @@ class ComplexRational_Test < Test::Unit::TestCase
r = c ** cc
assert_in_delta(0.1732, r.real, 0.001)
assert_in_delta(0.1186, r.imag, 0.001)
- assert_raise(NoMethodError){c <=> cc}
+ assert_nil(c <=> cc)
assert_equal(Complex(SimpleRat(13,6),SimpleRat(5,2)), cc + c)
assert_equal(Complex(SimpleRat(5,6),SimpleRat(3,2)), cc - c)
@@ -129,7 +129,7 @@ class ComplexRational_Test < Test::Unit::TestCase
r = cc ** c
assert_in_delta(0.5498, r.real, 0.001)
assert_in_delta(1.0198, r.imag, 0.001)
- assert_raise(NoMethodError){cc <=> c}
+ assert_nil(cc <=> c)
assert_equal([SimpleRat,SimpleRat],
(+c).instance_eval{[real.class, imag.class]})
diff --git a/test/ruby/test_const.rb b/test/ruby/test_const.rb
index 8784e0e988..f6b9ea83d3 100644
--- a/test/ruby/test_const.rb
+++ b/test/ruby/test_const.rb
@@ -3,20 +3,30 @@
require 'test/unit'
class TestConst < Test::Unit::TestCase
- TEST1 = 1
- TEST2 = 2
- module Const
- TEST3 = 3
- TEST4 = 4
- end
+ Constants_Setup = -> do
+ remove_const :TEST1 if defined? ::TestConst::TEST1
+ remove_const :TEST2 if defined? ::TestConst::TEST2
+ remove_const :Const if defined? ::TestConst::Const
+ remove_const :Const2 if defined? ::TestConst::Const2
+
+ TEST1 = 1
+ TEST2 = 2
- module Const2
- TEST3 = 6
- TEST4 = 8
+ module Const
+ TEST3 = 3
+ TEST4 = 4
+ end
+
+ module Const2
+ TEST3 = 6
+ TEST4 = 8
+ end
end
def test_const
+ Constants_Setup.call
+
assert defined?(TEST1)
assert_equal 1, TEST1
assert defined?(TEST2)
@@ -48,6 +58,17 @@ class TestConst < Test::Unit::TestCase
assert_equal 8, TEST4
end
+ def test_const_access_from_nil
+ assert_raise(TypeError) { eval("nil::Object") }
+ assert_nil eval("defined?(nil::Object)")
+
+ assert_raise(TypeError) { eval("c = nil; c::Object") }
+ assert_nil eval("c = nil; defined?(c::Object)")
+
+ assert_raise(TypeError) { eval("sc = Class.new; sc::C = nil; sc::C::Object") }
+ assert_nil eval("sc = Class.new; sc::C = nil; defined?(sc::C::Object)")
+ end
+
def test_redefinition
c = Class.new
name = "X\u{5b9a 6570}"
diff --git a/test/ruby/test_continuation.rb b/test/ruby/test_continuation.rb
index a06ac98c8c..612dbf28c9 100644
--- a/test/ruby/test_continuation.rb
+++ b/test/ruby/test_continuation.rb
@@ -4,6 +4,10 @@ EnvUtil.suppress_warning {require 'continuation'}
require 'fiber'
class TestContinuation < Test::Unit::TestCase
+ def setup
+ omit 'requires callcc support' unless respond_to?(:callcc)
+ end
+
def test_create
assert_equal(:ok, callcc{:ok})
assert_equal(:ok, callcc{|c| c.call :ok})
@@ -88,11 +92,16 @@ class TestContinuation < Test::Unit::TestCase
@memo += 1
c = cont
cont = nil
- c.call(nil)
+ begin
+ c.call(nil)
+ rescue RuntimeError
+ set_trace_func(nil)
+ end
end
end
end
cont = callcc { |cc| cc }
+
if cont
set_trace_func(func)
else
@@ -100,12 +109,12 @@ class TestContinuation < Test::Unit::TestCase
end
end
- def test_tracing_with_set_trace_func
+ def _test_tracing_with_set_trace_func
@memo = 0
tracing_with_set_trace_func
tracing_with_set_trace_func
tracing_with_set_trace_func
- assert_equal 3, @memo
+ assert_equal 0, @memo
end
def tracing_with_thread_set_trace_func
@@ -115,7 +124,11 @@ class TestContinuation < Test::Unit::TestCase
@memo += 1
c = cont
cont = nil
- c.call(nil)
+ begin
+ c.call(nil)
+ rescue RuntimeError
+ Thread.current.set_trace_func(nil)
+ end
end
end
cont = callcc { |cc| cc }
diff --git a/test/ruby/test_data.rb b/test/ruby/test_data.rb
new file mode 100644
index 0000000000..fbc3205d63
--- /dev/null
+++ b/test/ruby/test_data.rb
@@ -0,0 +1,290 @@
+# -*- coding: us-ascii -*-
+# frozen_string_literal: false
+require 'test/unit'
+require 'timeout'
+
+class TestData < Test::Unit::TestCase
+ def test_define
+ klass = Data.define(:foo, :bar)
+ assert_kind_of(Class, klass)
+ assert_equal(%i[foo bar], klass.members)
+
+ assert_raise(NoMethodError) { Data.new(:foo) }
+ assert_raise(TypeError) { Data.define(0) }
+
+ # Because some code is shared with Struct, check we don't share unnecessary functionality
+ assert_raise(TypeError) { Data.define(:foo, keyword_init: true) }
+
+ assert_not_respond_to(Data.define, :define, "Cannot define from defined Data class")
+ end
+
+ def test_define_edge_cases
+ # non-ascii
+ klass = Data.define(:"r\u{e9}sum\u{e9}")
+ o = klass.new(1)
+ assert_equal(1, o.send(:"r\u{e9}sum\u{e9}"))
+
+ # junk string
+ klass = Data.define(:"a\000")
+ o = klass.new(1)
+ assert_equal(1, o.send(:"a\000"))
+
+ # special characters in attribute names
+ klass = Data.define(:a, :b?)
+ x = Object.new
+ o = klass.new("test", x)
+ assert_same(x, o.b?)
+
+ klass = Data.define(:a, :b!)
+ x = Object.new
+ o = klass.new("test", x)
+ assert_same(x, o.b!)
+
+ assert_raise(ArgumentError) { Data.define(:x=) }
+ assert_raise(ArgumentError, /duplicate member/) { Data.define(:x, :x) }
+ end
+
+ def test_define_with_block
+ klass = Data.define(:a, :b) do
+ def c
+ a + b
+ end
+ end
+
+ assert_equal(3, klass.new(1, 2).c)
+ end
+
+ def test_initialize
+ klass = Data.define(:foo, :bar)
+
+ # Regular
+ test = klass.new(1, 2)
+ assert_equal(1, test.foo)
+ assert_equal(2, test.bar)
+ assert_equal(test, klass.new(1, 2))
+ assert_predicate(test, :frozen?)
+
+ # Keywords
+ test_kw = klass.new(foo: 1, bar: 2)
+ assert_equal(1, test_kw.foo)
+ assert_equal(2, test_kw.bar)
+ assert_equal(test_kw, klass.new(foo: 1, bar: 2))
+ assert_equal(test_kw, test)
+
+ # Wrong protocol
+ assert_raise(ArgumentError) { klass.new(1) }
+ assert_raise(ArgumentError) { klass.new(1, 2, 3) }
+ assert_raise(ArgumentError) { klass.new(foo: 1) }
+ assert_raise(ArgumentError) { klass.new(foo: 1, bar: 2, baz: 3) }
+ # Could be converted to foo: 1, bar: 2, but too smart is confusing
+ assert_raise(ArgumentError) { klass.new(1, bar: 2) }
+ end
+
+ def test_initialize_redefine
+ klass = Data.define(:foo, :bar) do
+ attr_reader :passed
+
+ def initialize(*args, **kwargs)
+ @passed = [args, kwargs]
+ super(foo: 1, bar: 2) # so we can experiment with passing wrong numbers of args
+ end
+ end
+
+ assert_equal([[], {foo: 1, bar: 2}], klass.new(foo: 1, bar: 2).passed)
+
+ # Positional arguments are converted to keyword ones
+ assert_equal([[], {foo: 1, bar: 2}], klass.new(1, 2).passed)
+
+ # Missing arguments can be fixed in initialize
+ assert_equal([[], {foo: 1}], klass.new(foo: 1).passed)
+ assert_equal([[], {foo: 42}], klass.new(42).passed)
+
+ # Extra keyword arguments can be dropped in initialize
+ assert_equal([[], {foo: 1, bar: 2, baz: 3}], klass.new(foo: 1, bar: 2, baz: 3).passed)
+ end
+
+ def test_instance_behavior
+ klass = Data.define(:foo, :bar)
+
+ test = klass.new(1, 2)
+ assert_equal(1, test.foo)
+ assert_equal(2, test.bar)
+ assert_equal(%i[foo bar], test.members)
+ assert_equal(1, test.public_send(:foo))
+ assert_equal(0, test.method(:foo).arity)
+ assert_equal([], test.method(:foo).parameters)
+
+ assert_equal({foo: 1, bar: 2}, test.to_h)
+ assert_equal({"foo"=>"1", "bar"=>"2"}, test.to_h { [_1.to_s, _2.to_s] })
+
+ assert_equal([1, 2], test.deconstruct)
+ assert_equal({foo: 1, bar: 2}, test.deconstruct_keys(nil))
+ assert_equal({foo: 1}, test.deconstruct_keys(%i[foo]))
+ assert_equal({foo: 1}, test.deconstruct_keys(%i[foo baz]))
+ assert_equal({}, test.deconstruct_keys(%i[foo bar baz]))
+ assert_raise(TypeError) { test.deconstruct_keys(0) }
+
+ assert_kind_of(Integer, test.hash)
+ end
+
+ def test_hash
+ measure = Data.define(:amount, :unit)
+
+ assert_equal(measure[1, 'km'].hash, measure[1, 'km'].hash)
+ assert_not_equal(measure[1, 'km'].hash, measure[10, 'km'].hash)
+ assert_not_equal(measure[1, 'km'].hash, measure[1, 'm'].hash)
+ assert_not_equal(measure[1, 'km'].hash, measure[1.0, 'km'].hash)
+
+ # Structurally similar data class, but shouldn't be considered
+ # the same hash key
+ measurement = Data.define(:amount, :unit)
+
+ assert_not_equal(measure[1, 'km'].hash, measurement[1, 'km'].hash)
+ end
+
+ def test_inspect
+ klass = Data.define(:a)
+ o = klass.new(1)
+ assert_equal("#<data a=1>", o.inspect)
+
+ Object.const_set(:Foo, klass)
+ assert_equal("#<data Foo a=1>", o.inspect)
+ Object.instance_eval { remove_const(:Foo) }
+
+ klass = Data.define(:@a)
+ o = klass.new(1)
+ assert_equal("#<data :@a=1>", o.inspect)
+ end
+
+ def test_equal
+ klass1 = Data.define(:a)
+ klass2 = Data.define(:a)
+ o1 = klass1.new(1)
+ o2 = klass1.new(1)
+ o3 = klass2.new(1)
+ assert_equal(o1, o2)
+ assert_not_equal(o1, o3)
+ end
+
+ def test_eql
+ klass1 = Data.define(:a)
+ klass2 = Data.define(:a)
+ o1 = klass1.new(1)
+ o2 = klass1.new(1)
+ o3 = klass2.new(1)
+ assert_operator(o1, :eql?, o2)
+ assert_not_operator(o1, :eql?, o3)
+ end
+
+ def test_with
+ klass = Data.define(:foo, :bar)
+ source = klass.new(foo: 1, bar: 2)
+
+ # Simple
+ test = source.with
+ assert_equal(source.object_id, test.object_id)
+
+ # Changes
+ test = source.with(foo: 10)
+
+ assert_equal(1, source.foo)
+ assert_equal(2, source.bar)
+ assert_equal(source, klass.new(foo: 1, bar: 2))
+
+ assert_equal(10, test.foo)
+ assert_equal(2, test.bar)
+ assert_equal(test, klass.new(foo: 10, bar: 2))
+
+ test = source.with(foo: 10, bar: 20)
+
+ assert_equal(1, source.foo)
+ assert_equal(2, source.bar)
+ assert_equal(source, klass.new(foo: 1, bar: 2))
+
+ assert_equal(10, test.foo)
+ assert_equal(20, test.bar)
+ assert_equal(test, klass.new(foo: 10, bar: 20))
+
+ # Keyword splat
+ changes = { foo: 10, bar: 20 }
+ test = source.with(**changes)
+
+ assert_equal(1, source.foo)
+ assert_equal(2, source.bar)
+ assert_equal(source, klass.new(foo: 1, bar: 2))
+
+ assert_equal(10, test.foo)
+ assert_equal(20, test.bar)
+ assert_equal(test, klass.new(foo: 10, bar: 20))
+
+ # Wrong protocol
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do
+ source.with(10)
+ end
+ assert_raise_with_message(ArgumentError, "unknown keywords: :baz, :quux") do
+ source.with(foo: 1, bar: 2, baz: 3, quux: 4)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do
+ source.with(1, bar: 2)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given 2, expected 0)") do
+ source.with(1, 2)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do
+ source.with({ bar: 2 })
+ end
+ end
+
+ def test_with_initialize
+ oddclass = Data.define(:odd) do
+ def initialize(odd:)
+ raise ArgumentError, "Not odd" unless odd.odd?
+ super(odd: odd)
+ end
+ end
+ assert_raise_with_message(ArgumentError, "Not odd") {
+ oddclass.new(odd: 0)
+ }
+ odd = oddclass.new(odd: 1)
+ assert_raise_with_message(ArgumentError, "Not odd") {
+ odd.with(odd: 2)
+ }
+ end
+
+ def test_memberless
+ klass = Data.define
+
+ test = klass.new
+
+ assert_equal(klass.new, test)
+ assert_not_equal(Data.define.new, test)
+
+ assert_equal('#<data>', test.inspect)
+ assert_equal([], test.members)
+ assert_equal({}, test.to_h)
+ assert_predicate(test, :frozen?)
+ end
+
+ def test_dup
+ klass = Data.define(:foo, :bar)
+ test = klass.new(foo: 1, bar: 2)
+ assert_equal(klass.new(foo: 1, bar: 2), test.dup)
+ assert_predicate(test.dup, :frozen?)
+ end
+
+ Klass = Data.define(:foo, :bar)
+
+ def test_marshal
+ test = Klass.new(foo: 1, bar: 2)
+ loaded = Marshal.load(Marshal.dump(test))
+ assert_equal(test, loaded)
+ 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_default_gems.rb b/test/ruby/test_default_gems.rb
new file mode 100644
index 0000000000..b82e304cbd
--- /dev/null
+++ b/test/ruby/test_default_gems.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: false
+require 'rubygems'
+
+class TestDefaultGems < Test::Unit::TestCase
+ def self.load(file)
+ code = File.read(file, mode: "r:UTF-8:-", &:read)
+
+ # These regex patterns are from load_gemspec method of rbinstall.rb.
+ # - `git ls-files` is useless under ruby's repository
+ # - `2>/dev/null` works only on Unix-like platforms
+ code.gsub!(/(?:`git[^\`]*`|%x\[git[^\]]*\])\.split\([^\)]*\)/m, '[]')
+ code.gsub!(/IO\.popen\(.*git.*?\)/, '[] || itself')
+
+ eval(code, binding, file)
+ end
+
+ def test_validate_gemspec
+ srcdir = File.expand_path('../../..', __FILE__)
+ specs = 0
+ Dir.chdir(srcdir) do
+ all_assertions_foreach(nil, *Dir["{lib,ext}/**/*.gemspec"]) do |src|
+ specs += 1
+ assert_kind_of(Gem::Specification, self.class.load(src), "invalid spec in #{src}")
+ end
+ end
+ assert_operator specs, :>, 0, "gemspecs not found"
+ end
+
+end
diff --git a/test/ruby/test_defined.rb b/test/ruby/test_defined.rb
index 9976db3b6f..db1fdc8e25 100644
--- a/test/ruby/test_defined.rb
+++ b/test/ruby/test_defined.rb
@@ -23,40 +23,80 @@ class TestDefined < Test::Unit::TestCase
return !defined?(yield)
end
- def test_defined
+ def test_defined_global_variable
$x = nil
assert(defined?($x)) # global variable
assert_equal('global-variable', defined?($x))# returns description
+ end
+ def test_defined_local_variable
assert_nil(defined?(foo)) # undefined
foo=5
assert(defined?(foo)) # local variable
+ end
+ def test_defined_constant
assert(defined?(Array)) # constant
assert(defined?(::Array)) # toplevel constant
assert(defined?(File::Constants)) # nested constant
+ end
+
+ def test_defined_public_method
assert(defined?(Object.new)) # method
assert(defined?(Object::new)) # method
+ end
+
+ def test_defined_private_method
assert(!defined?(Object.print)) # private method
+ end
+
+ def test_defined_operator
assert(defined?(1 == 2)) # operator expression
+ end
+ def test_defined_protected_method
f = Foo.new
assert_nil(defined?(f.foo)) # protected method
f.bar(f) { |v| assert(v) }
+ f.bar(Class.new(Foo).new) { |v| assert(v, "inherited protected method") }
+ end
+
+ def test_defined_undefined_method
+ f = Foo.new
assert_nil(defined?(f.quux)) # undefined method
+ end
+
+ def test_defined_undefined_argument
+ f = Foo.new
assert_nil(defined?(f.baz(x))) # undefined argument
x = 0
assert(defined?(f.baz(x)))
assert_nil(defined?(f.quux(x)))
assert(defined?(print(x)))
assert_nil(defined?(quux(x)))
+ end
+
+ def test_defined_attrasgn
+ f = Foo.new
assert(defined?(f.attr = 1))
f.attrasgn_test { |v| assert(v) }
+ end
+ def test_defined_undef
+ x = Object.new
+ def x.foo; end
+ assert(defined?(x.foo))
+ x.instance_eval {undef :foo}
+ assert(!defined?(x.foo), "undefed method should not be defined?")
+ end
+
+ def test_defined_yield
assert(defined_test) # not iterator
assert(!defined_test{}) # called as iterator
+ end
+ def test_defined_matchdata
/a/ =~ ''
assert_equal nil, defined?($&)
assert_equal nil, defined?($`)
@@ -85,12 +125,112 @@ class TestDefined < Test::Unit::TestCase
assert_equal 'global-variable', defined?($+)
assert_equal 'global-variable', defined?($1)
assert_equal nil, defined?($2)
+ end
+
+ def test_defined_assignment
+ assert_equal("assignment", defined?(a = 1))
+ assert_equal("assignment", defined?(a += 1))
+ assert_equal("assignment", defined?(a &&= 1))
+ assert_equal("assignment", eval('defined?(A = 1)'))
+ assert_equal("assignment", eval('defined?(A += 1)'))
+ assert_equal("assignment", eval('defined?(A &&= 1)'))
+ assert_equal("assignment", eval('defined?(A::B = 1)'))
+ assert_equal("assignment", eval('defined?(A::B += 1)'))
+ assert_equal("assignment", eval('defined?(A::B &&= 1)'))
+ end
+ def test_defined_splat
+ assert_nil(defined?([*a]))
+ assert_nil(defined?(itself(*a)))
+ assert_equal("expression", defined?([*itself]))
+ assert_equal("method", defined?(itself(*itself)))
+ end
+
+ def test_defined_hash
+ assert_nil(defined?({a: a}))
+ assert_nil(defined?({a => 1}))
+ assert_nil(defined?({a => a}))
+ assert_nil(defined?({**a}))
+ assert_nil(defined?(itself(a: a)))
+ assert_nil(defined?(itself(a => 1)))
+ assert_nil(defined?(itself(a => a)))
+ assert_nil(defined?(itself(**a)))
+ assert_nil(defined?(itself({a: a})))
+ assert_nil(defined?(itself({a => 1})))
+ assert_nil(defined?(itself({a => a})))
+ assert_nil(defined?(itself({**a})))
+
+ assert_equal("expression", defined?({a: itself}))
+ assert_equal("expression", defined?({itself => 1}))
+ assert_equal("expression", defined?({itself => itself}))
+ assert_equal("expression", defined?({**itself}))
+ assert_equal("method", defined?(itself(a: itself)))
+ assert_equal("method", defined?(itself(itself => 1)))
+ assert_equal("method", defined?(itself(itself => itself)))
+ assert_equal("method", defined?(itself(**itself)))
+ assert_equal("method", defined?(itself({a: itself})))
+ assert_equal("method", defined?(itself({itself => 1})))
+ assert_equal("method", defined?(itself({itself => itself})))
+ assert_equal("method", defined?(itself({**itself})))
+ end
+
+ def test_defined_literal
assert_equal("nil", defined?(nil))
assert_equal("true", defined?(true))
assert_equal("false", defined?(false))
assert_equal("expression", defined?(1))
+ end
+
+ def test_defined_method
+ self_ = self
+ assert_equal("method", defined?(test_defined_method))
+ assert_equal("method", defined?(self.test_defined_method))
+ assert_equal("method", defined?(self_.test_defined_method))
+
+ assert_equal(nil, defined?(1.test_defined_method))
+ assert_equal("method", defined?(1.to_i))
+ assert_equal(nil, defined?(1.to_i.test_defined_method))
+ assert_equal(nil, defined?(1.test_defined_method.to_i))
+
+ assert_equal("method", defined?("x".reverse))
+ assert_equal("method", defined?("x".reverse(1)))
+ assert_equal("method", defined?("x".reverse.reverse))
+ assert_equal(nil, defined?("x".reverse(1).reverse))
+
+ assert_equal("method", defined?(1.to_i(10)))
+ assert_equal("method", defined?(1.to_i("x")))
+ assert_equal(nil, defined?(1.to_i("x").undefined))
+ assert_equal(nil, defined?(1.to_i(undefined).to_i))
+ assert_equal(nil, defined?(1.to_i("x").undefined.to_i))
+ assert_equal(nil, defined?(1.to_i(undefined).to_i.to_i))
+ end
+
+ def test_defined_method_single_call
+ times_called = 0
+ define_singleton_method(:t) do
+ times_called += 1
+ self
+ end
+ assert_equal("method", defined?(t))
+ assert_equal(0, times_called)
+
+ assert_equal("method", defined?(t.t))
+ assert_equal(1, times_called)
+
+ times_called = 0
+ assert_equal("method", defined?(t.t.t))
+ assert_equal(2, times_called)
+
+ times_called = 0
+ assert_equal("method", defined?(t.t.t.t))
+ assert_equal(3, times_called)
+
+ times_called = 0
+ assert_equal("method", defined?(t.t.t.t.t))
+ assert_equal(4, times_called)
+ end
+ def test_defined_empty_paren_expr
bug8224 = '[ruby-core:54024] [Bug #8224]'
(1..3).each do |level|
expr = "("*level+")"*level
@@ -103,6 +243,26 @@ class TestDefined < Test::Unit::TestCase
assert_nil(defined?(p () + 1))
end
+ def test_defined_paren_void_stmts
+ assert_equal("expression", defined? (;x))
+ assert_equal("expression", defined? (x;))
+ assert_nil(defined? (
+
+ x
+
+ ))
+
+ x = 1
+
+ assert_equal("expression", defined? (;x))
+ assert_equal("expression", defined? (x;))
+ assert_equal("local-variable", defined? (
+
+ x
+
+ ))
+ end
+
def test_defined_impl_specific
feature7035 = '[ruby-core:47558]' # not spec
assert_predicate(defined?(Foo), :frozen?, feature7035)
@@ -210,10 +370,41 @@ class TestDefined < Test::Unit::TestCase
assert_equal("super", o.x, bug8367)
end
+ def test_super_in_basic_object
+ BasicObject.class_eval do
+ def a
+ defined?(super)
+ end
+ end
+
+ assert_nil(a)
+ ensure
+ BasicObject.class_eval do
+ undef_method :a if defined?(a)
+ end
+ end
+
def test_super_toplevel
assert_separately([], "assert_nil(defined?(super))")
end
+ def test_respond_to
+ obj = "#{self.class.name}##{__method__}"
+ class << obj
+ def respond_to?(mid)
+ true
+ end
+ end
+ assert_warn(/deprecated method signature.*\n.*respond_to\? is defined here/) do
+ Warning[:deprecated] = true
+ defined?(obj.foo)
+ end
+ assert_warn('') do
+ Warning[:deprecated] = false
+ defined?(obj.foo)
+ end
+ end
+
class ExampleRespondToMissing
attr_reader :called
@@ -257,4 +448,53 @@ class TestDefined < Test::Unit::TestCase
def test_top_level_constant_not_defined
assert_nil(defined?(TestDefined::Object))
end
+
+ class RefinedClass
+ end
+
+ module RefiningModule
+ refine RefinedClass do
+ def pub
+ end
+
+ private
+
+ def priv
+ end
+ end
+
+ def self.call_without_using(x = RefinedClass.new)
+ defined?(x.pub)
+ end
+
+ def self.vcall_without_using(x = RefinedClass.new)
+ x.instance_eval {defined?(priv)}
+ end
+
+ using self
+
+ def self.call_with_using(x = RefinedClass.new)
+ defined?(x.pub)
+ end
+
+ def self.vcall_with_using(x = RefinedClass.new)
+ x.instance_eval {defined?(priv)}
+ end
+ end
+
+ def test_defined_refined_call_without_using
+ assert(!RefiningModule.call_without_using, "refined public method without using")
+ end
+
+ def test_defined_refined_vcall_without_using
+ assert(!RefiningModule.vcall_without_using, "refined private method without using")
+ end
+
+ def test_defined_refined_call_with_using
+ assert(RefiningModule.call_with_using, "refined public method with using")
+ end
+
+ def test_defined_refined_vcall_with_using
+ assert(RefiningModule.vcall_with_using, "refined private method with using")
+ end
end
diff --git a/test/ruby/test_dir.rb b/test/ruby/test_dir.rb
index 000bc24e85..edb5210af1 100644
--- a/test/ruby/test_dir.rb
+++ b/test/ruby/test_dir.rb
@@ -8,21 +8,32 @@ class TestDir < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
@root = File.realpath(Dir.mktmpdir('__test_dir__'))
@nodir = File.join(@root, "dummy")
+ @dirs = []
for i in "a".."z"
if i.ord % 2 == 0
FileUtils.touch(File.join(@root, i))
else
FileUtils.mkdir(File.join(@root, i))
+ @dirs << File.join(i, "")
end
end
+ @envs = nil
end
def teardown
$VERBOSE = @verbose
FileUtils.remove_entry_secure @root if File.directory?(@root)
+ ENV.update(@envs) if @envs
+ end
+
+ def setup_envs(envs = %w"HOME LOGDIR")
+ @envs ||= {}
+ envs.each do |e, v|
+ @envs[e] = ENV.delete(e)
+ ENV[e] = v if v
+ end
end
def test_seek
@@ -85,41 +96,136 @@ class TestDir < Test::Unit::TestCase
d.close
end
- def test_chdir
- @pwd = Dir.pwd
- @env_home = ENV["HOME"]
- @env_logdir = ENV["LOGDIR"]
- ENV.delete("HOME")
- ENV.delete("LOGDIR")
+ def test_class_chdir
+ pwd = Dir.pwd
+ setup_envs
assert_raise(Errno::ENOENT) { Dir.chdir(@nodir) }
assert_raise(ArgumentError) { Dir.chdir }
- ENV["HOME"] = @pwd
+ ENV["HOME"] = pwd
Dir.chdir do
- assert_equal(@pwd, Dir.pwd)
- Dir.chdir(@root)
+ conflicting = /conflicting chdir during another chdir block\n^#{Regexp.quote(__FILE__)}:#{__LINE__-1}:/
+ assert_warning(conflicting) { Dir.chdir(pwd) }
+
+ assert_warning(conflicting) { Dir.chdir(@root) }
assert_equal(@root, Dir.pwd)
+
+ assert_warning(conflicting) { Dir.chdir(pwd) }
+
+ assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; Dir.chdir(@root) }.join }
+ assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; Dir.chdir(@root) { } }.join }
+
+ assert_warning(conflicting) { Dir.chdir(pwd) }
+
+ assert_warning(conflicting) { Dir.chdir(@root) }
+ assert_equal(@root, Dir.pwd)
+
+ assert_warning(conflicting) { Dir.chdir(pwd) }
+ Dir.chdir(@root) do
+ assert_equal(@root, Dir.pwd)
+ end
+ assert_equal(pwd, Dir.pwd)
end
ensure
begin
- Dir.chdir(@pwd)
+ Dir.chdir(pwd)
rescue
- abort("cannot return the original directory: #{ @pwd }")
+ abort("cannot return the original directory: #{ pwd }")
end
- if @env_home
- ENV["HOME"] = @env_home
- else
- ENV.delete("HOME")
+ end
+
+ def test_instance_chdir
+ pwd = Dir.pwd
+ dir = Dir.new(pwd)
+ root_dir = Dir.new(@root)
+ setup_envs
+
+ ENV["HOME"] = pwd
+ ret = root_dir.chdir do |*a|
+ conflicting = /conflicting chdir during another chdir block\n^#{Regexp.quote(__FILE__)}:#{__LINE__-1}:/
+
+ assert_empty(a)
+
+ assert_warning(conflicting) { dir.chdir }
+ assert_warning(conflicting) { root_dir.chdir }
+
+ assert_equal(@root, Dir.pwd)
+
+ assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; dir.chdir }.join }
+ assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; dir.chdir{} }.join }
+
+ assert_warning(conflicting) { dir.chdir }
+ assert_equal(pwd, Dir.pwd)
+
+ assert_warning(conflicting) { root_dir.chdir }
+ assert_equal(@root, Dir.pwd)
+
+ assert_warning(conflicting) { dir.chdir }
+
+ root_dir.chdir do
+ assert_equal(@root, Dir.pwd)
+ end
+ assert_equal(pwd, Dir.pwd)
+
+ 42
end
- if @env_logdir
- ENV["LOGDIR"] = @env_logdir
- else
- ENV.delete("LOGDIR")
+
+ assert_separately(["-", @root], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ root = ARGV.shift
+
+ $dir_warnings = []
+
+ def Warning.warn(message)
+ $dir_warnings << message
+ end
+
+ line2 = line1 = __LINE__; Dir.chdir(root) do
+ line2 = __LINE__; Dir.chdir
+ end
+
+ message = $dir_warnings.shift
+ assert_include(message, "#{__FILE__}:#{line2}:")
+ assert_include(message, "#{__FILE__}:#{line1}:")
+ assert_empty($dir_warnings)
+ end;
+
+ assert_equal(42, ret)
+ ensure
+ begin
+ assert_equal(0, dir.chdir)
+ rescue
+ abort("cannot return the original directory: #{ pwd }")
+ end
+ dir.close
+ root_dir.close
+ end
+
+ def test_chdir_conflict
+ pwd = Dir.pwd
+ q = Thread::Queue.new
+ t = Thread.new do
+ q.pop
+ Dir.chdir(pwd) rescue $!
+ end
+ Dir.chdir(pwd) do
+ q.push nil
+ assert_instance_of(RuntimeError, t.value)
+ end
+
+ t = Thread.new do
+ q.pop
+ Dir.chdir(pwd){} rescue $!
+ end
+ Dir.chdir(pwd) do
+ q.push nil
+ assert_instance_of(RuntimeError, t.value)
end
end
def test_chroot_nodir
+ omit if RUBY_PLATFORM =~ /android/
assert_raise(NotImplementedError, Errno::ENOENT, Errno::EPERM
) { Dir.chroot(File.join(@nodir, "")) }
end
@@ -132,15 +238,26 @@ class TestDir < Test::Unit::TestCase
end
def test_glob
- assert_equal((%w(. ..) + ("a".."z").to_a).map{|f| File.join(@root, f) },
- Dir.glob(File.join(@root, "*"), File::FNM_DOTMATCH).sort)
- assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) }.sort,
- Dir.glob([@root, File.join(@root, "*")]).sort)
- assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) }.sort,
- Dir.glob(@root + "\0\0\0" + File.join(@root, "*")).sort)
-
- assert_equal(("a".."z").step(2).map {|f| File.join(File.join(@root, f), "") }.sort,
- Dir.glob(File.join(@root, "*/")).sort)
+ assert_equal((%w(.) + ("a".."z").to_a).map{|f| File.join(@root, f) },
+ Dir.glob(File.join(@root, "*"), File::FNM_DOTMATCH))
+ assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) },
+ Dir.glob([@root, File.join(@root, "*")]))
+ assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) },
+ Dir.glob([@root, File.join(@root, "*")], sort: false).sort)
+ assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) },
+ Dir.glob([@root, File.join(@root, "*")], sort: true))
+ assert_raise_with_message(ArgumentError, /nul-separated/) do
+ Dir.glob(@root + "\0\0\0" + File.join(@root, "*"))
+ end
+ assert_raise_with_message(ArgumentError, /expected true or false/) do
+ Dir.glob(@root, sort: 1)
+ end
+ assert_raise_with_message(ArgumentError, /expected true or false/) do
+ Dir.glob(@root, sort: nil)
+ end
+
+ assert_equal(("a".."z").each_slice(2).map {|f,_| File.join(File.join(@root, f), "") },
+ Dir.glob(File.join(@root, "*/")))
assert_equal([File.join(@root, '//a')], Dir.glob(@root + '//a'))
FileUtils.touch(File.join(@root, "{}"))
@@ -150,7 +267,7 @@ class TestDir < Test::Unit::TestCase
assert_equal([], Dir.glob(File.join(@root, '[a-\\')))
assert_equal([File.join(@root, "a")], Dir.glob(File.join(@root, 'a\\')))
- assert_equal(("a".."f").map {|f| File.join(@root, f) }.sort, Dir.glob(File.join(@root, '[abc/def]')).sort)
+ assert_equal(("a".."f").map {|f| File.join(@root, f) }, Dir.glob(File.join(@root, '[abc/def]')))
open(File.join(@root, "}}{}"), "wb") {}
open(File.join(@root, "}}a"), "wb") {}
@@ -167,6 +284,9 @@ class TestDir < Test::Unit::TestCase
Dir.chdir(@root) do
assert_include(Dir.glob("a/**/*", File::FNM_DOTMATCH), "a/.", bug8006)
+ Dir.mkdir("a/b")
+ assert_not_include(Dir.glob("a/**/*", File::FNM_DOTMATCH), "a/b/.")
+
FileUtils.mkdir_p("a/b/c/d/e/f")
assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/e/f"), bug6977)
assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/d/e/f"), bug6977)
@@ -180,13 +300,63 @@ class TestDir < Test::Unit::TestCase
dirs = ["a/.x", "a/b/.y"]
FileUtils.mkdir_p(dirs)
dirs.map {|dir| open("#{dir}/z", "w") {}}
- assert_equal([], Dir.glob("a/**/z").sort, bug8283)
+ assert_equal([], Dir.glob("a/**/z"), bug8283)
assert_equal(["a/.x/z"], Dir.glob("a/**/.x/z"), bug8283)
assert_equal(["a/.x/z"], Dir.glob("a/.x/**/z"), bug8283)
assert_equal(["a/b/.y/z"], Dir.glob("a/**/.y/z"), bug8283)
end
end
+ def test_glob_recursive_directory
+ Dir.chdir(@root) do
+ ['d', 'e'].each do |path|
+ FileUtils.mkdir_p("c/#{path}/a/b/c")
+ FileUtils.touch("c/#{path}/a/a.file")
+ FileUtils.touch("c/#{path}/a/b/b.file")
+ FileUtils.touch("c/#{path}/a/b/c/c.file")
+ end
+ bug15540 = '[ruby-core:91110] [Bug #15540]'
+ assert_equal(["c/d/a/", "c/d/a/b/", "c/d/a/b/c/", "c/e/a/", "c/e/a/b/", "c/e/a/b/c/"],
+ Dir.glob('c/{d,e}/a/**/'), bug15540)
+
+ assert_equal(["c/e/a/", "c/e/a/b/", "c/e/a/b/c/", "c/d/a/", "c/d/a/b/", "c/d/a/b/c/"],
+ Dir.glob('c/{e,d}/a/**/'))
+ end
+ end
+
+ def test_glob_starts_with_brace
+ Dir.chdir(@root) do
+ bug15649 = '[ruby-core:91728] [Bug #15649]'
+ assert_equal(["#{@root}/a", "#{@root}/b"],
+ Dir.glob("{#{@root}/a,#{@root}/b}"), bug15649)
+ end
+ end
+
+ def test_glob_recursive_with_brace
+ Dir.chdir(@root) do
+ bug19042 = '[ruby-core:110220] [Bug #19042]'
+ %w"c/dir_a c/dir_b c/dir_b/dir".each do |d|
+ Dir.mkdir(d)
+ end
+ expected = %w"c/dir_a/file c/dir_b/dir/file"
+ expected.each do |f|
+ File.write(f, "")
+ end
+ assert_equal(expected, Dir.glob("**/{dir_a,dir_b/dir}/file"), bug19042)
+ end
+ end
+
+ def test_glob_order
+ Dir.chdir(@root) do
+ assert_equal(["#{@root}/a", "#{@root}/b"], Dir.glob("#{@root}/[ba]"))
+ assert_equal(["#{@root}/b", "#{@root}/a"], Dir.glob(%W"#{@root}/b #{@root}/a"))
+ assert_equal(["#{@root}/b", "#{@root}/a"], Dir.glob("#{@root}/{b,a}"))
+ end
+ assert_equal(["a", "b"], Dir.glob("[ba]", base: @root))
+ assert_equal(["b", "a"], Dir.glob(%W"b a", base: @root))
+ assert_equal(["b", "a"], Dir.glob("{b,a}", base: @root))
+ end
+
if Process.const_defined?(:RLIMIT_NOFILE)
def test_glob_too_may_open_files
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", chdir: @root)
@@ -208,18 +378,74 @@ class TestDir < Test::Unit::TestCase
def test_glob_base
files = %w[a/foo.c c/bar.c]
files.each {|n| File.write(File.join(@root, n), "")}
- assert_equal(files, Dir.glob("*/*.c", base: @root).sort)
- assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: ".").sort})
- assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.glob("*.c", base: "a").sort})
- assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: "").sort})
- assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: nil).sort})
+ Dir.mkdir(File.join(@root, "a/dir"))
+ dirs = @dirs + %w[a/dir/]
+ dirs.sort!
+
+ assert_equal(files, Dir.glob("*/*.c", base: @root))
+ assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: ".")})
+ assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.glob("*.c", base: "a")})
+ assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: "")})
+ assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: nil)})
+ assert_equal(@dirs, Dir.glob("*/", base: @root))
+ assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: ".")})
+ assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("*/", base: "a")})
+ assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: "")})
+ assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: nil)})
+ assert_equal(dirs, Dir.glob("**/*/", base: @root))
+ assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: ".")})
+ assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("**/*/", base: "a")})
+ assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: "")})
+ assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: nil)})
+
+ assert_equal(files, Dir.glob("*/*.c", base: @root, sort: false).sort)
+ assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: ".", sort: false).sort})
+ assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.glob("*.c", base: "a", sort: false).sort})
+ assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: "", sort: false).sort})
+ assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: nil, sort: false).sort})
+ assert_equal(@dirs, Dir.glob("*/", base: @root))
+ assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: ".", sort: false).sort})
+ assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("*/", base: "a", sort: false).sort})
+ assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: "", sort: false).sort})
+ assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: nil, sort: false).sort})
+ assert_equal(dirs, Dir.glob("**/*/", base: @root))
+ assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: ".", sort: false).sort})
+ assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("**/*/", base: "a", sort: false).sort})
+ assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: "", sort: false).sort})
+ assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: nil, sort: false).sort})
end
def test_glob_base_dir
files = %w[a/foo.c c/bar.c]
files.each {|n| File.write(File.join(@root, n), "")}
- assert_equal(files, Dir.open(@root) {|d| Dir.glob("*/*.c", base: d)}.sort)
- assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*", base: d)}})
+ Dir.mkdir(File.join(@root, "a/dir"))
+ dirs = @dirs + %w[a/dir/]
+ dirs.sort!
+
+ assert_equal(files, Dir.open(@root) {|d| Dir.glob("*/*.c", base: d)})
+ assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*.c", base: d)}})
+ assert_equal(@dirs, Dir.open(@root) {|d| Dir.glob("*/", base: d)})
+ assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*/", base: d)}})
+ assert_equal(dirs, Dir.open(@root) {|d| Dir.glob("**/*/", base: d)})
+ assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("**/*/", base: d)}})
+
+ assert_equal(files, Dir.open(@root) {|d| Dir.glob("*/*.c", base: d, sort: false).sort})
+ assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*.c", base: d, sort: false).sort}})
+ assert_equal(@dirs, Dir.open(@root) {|d| Dir.glob("*/", base: d, sort: false).sort})
+ assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*/", base: d, sort: false).sort}})
+ assert_equal(dirs, Dir.open(@root) {|d| Dir.glob("**/*/", base: d, sort: false).sort})
+ assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("**/*/", base: d, sort: false).sort}})
+ end
+
+ def test_glob_ignore_casefold_invalid_encoding
+ bug14456 = "[ruby-core:85448]"
+ filename = "\u00AAa123".encode('ISO-8859-1')
+ File.write(File.join(@root, filename), "")
+ matches = Dir.chdir(@root) {|d| Dir.glob("*a123".encode('UTF-8'), File::FNM_CASEFOLD)}
+ assert_equal(1, matches.size, bug14456)
+ matches.each{|f| f.force_encoding('ISO-8859-1')}
+ # Handle MacOS/Windows, which saves under a different filename
+ assert_include([filename, "\u00C2\u00AAa123".encode('ISO-8859-1')], matches.first, bug14456)
end
def assert_entries(entries, children_only = false)
@@ -231,24 +457,52 @@ class TestDir < Test::Unit::TestCase
def test_entries
assert_entries(Dir.open(@root) {|dir| dir.entries})
- assert_entries(Dir.entries(@root).to_a)
+ assert_entries(Dir.entries(@root))
assert_raise(ArgumentError) {Dir.entries(@root+"\0")}
+ [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc|
+ assert_equal(enc, Dir.entries(@root, encoding: enc).first.encoding)
+ end
end
def test_foreach
assert_entries(Dir.open(@root) {|dir| dir.each.to_a})
assert_entries(Dir.foreach(@root).to_a)
assert_raise(ArgumentError) {Dir.foreach(@root+"\0").to_a}
+ newdir = @root+"/new"
+ e = Dir.foreach(newdir)
+ assert_raise(Errno::ENOENT) {e.to_a}
+ Dir.mkdir(newdir)
+ File.write(newdir+"/a", "")
+ assert_equal(%w[. .. a], e.to_a.sort)
+ [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc|
+ e = Dir.foreach(newdir, encoding: enc)
+ assert_equal(enc, e.to_a.first.encoding)
+ end
end
def test_children
+ assert_entries(Dir.open(@root) {|dir| dir.children}, true)
assert_entries(Dir.children(@root), true)
assert_raise(ArgumentError) {Dir.children(@root+"\0")}
+ [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc|
+ assert_equal(enc, Dir.children(@root, encoding: enc).first.encoding)
+ end
end
def test_each_child
+ assert_entries(Dir.open(@root) {|dir| dir.each_child.to_a}, true)
assert_entries(Dir.each_child(@root).to_a, true)
assert_raise(ArgumentError) {Dir.each_child(@root+"\0").to_a}
+ newdir = @root+"/new"
+ e = Dir.each_child(newdir)
+ assert_raise(Errno::ENOENT) {e.to_a}
+ Dir.mkdir(newdir)
+ File.write(newdir+"/a", "")
+ assert_equal(%w[a], e.to_a)
+ [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc|
+ e = Dir.each_child(newdir, encoding: enc)
+ assert_equal(enc, e.to_a.first.encoding)
+ end
end
def test_dir_enc
@@ -289,10 +543,10 @@ class TestDir < Test::Unit::TestCase
end
assert_equal([*"a".."z", *"symlink-a".."symlink-z"].each_slice(2).map {|f, _| File.join(@root, f + "/") }.sort,
- Dir.glob(File.join(@root, "*/")).sort)
+ Dir.glob(File.join(@root, "*/")))
- assert_equal([@root + "/", *[*"a".."z"].each_slice(2).map {|f, _| File.join(@root, f + "/") }.sort],
- Dir.glob(File.join(@root, "**/")).sort)
+ assert_equal([@root + "/", *[*"a".."z"].each_slice(2).map {|f, _| File.join(@root, f + "/") }],
+ Dir.glob(File.join(@root, "**/")))
end
def test_glob_metachar
@@ -323,9 +577,9 @@ class TestDir < Test::Unit::TestCase
def test_glob_legacy_short_name
bug10819 = '[ruby-core:67954] [Bug #10819]'
bug11206 = '[ruby-core:69435] [Bug #11206]'
- skip unless /\A\w:/ =~ ENV["ProgramFiles"]
+ omit unless /\A\w:/ =~ ENV["ProgramFiles"]
short = "#$&/PROGRA~1"
- skip unless File.directory?(short)
+ omit unless File.directory?(short)
entries = Dir.glob("#{short}/Common*")
assert_not_empty(entries, bug10819)
long = File.expand_path(short)
@@ -335,29 +589,104 @@ class TestDir < Test::Unit::TestCase
assert_include(Dir.glob(wild, File::FNM_SHORTNAME), long, bug10819)
assert_empty(entries - Dir.glob("#{wild}/Common*", File::FNM_SHORTNAME), bug10819)
end
+
+ def test_home_windows
+ setup_envs(%w[HOME USERPROFILE HOMEDRIVE HOMEPATH])
+
+ ENV['HOME'] = "C:\\ruby\\home"
+ assert_equal("C:/ruby/home", Dir.home)
+
+ ENV['USERPROFILE'] = "C:\\ruby\\userprofile"
+ assert_equal("C:/ruby/home", Dir.home)
+ ENV.delete('HOME')
+ assert_equal("C:/ruby/userprofile", Dir.home)
+
+ ENV['HOMEDRIVE'] = "C:"
+ ENV['HOMEPATH'] = "\\ruby\\homepath"
+ assert_equal("C:/ruby/userprofile", Dir.home)
+ ENV.delete('USERPROFILE')
+ assert_equal("C:/ruby/homepath", Dir.home)
+ end
+
+ def test_home_at_startup_windows
+ env = {'HOME' => "C:\\ruby\\home"}
+ args = [env]
+ assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_equal("C:/ruby/home", Dir.home)
+ end;
+
+ env['USERPROFILE'] = "C:\\ruby\\userprofile"
+ assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_equal("C:/ruby/home", Dir.home)
+ end;
+
+ env['HOME'] = nil
+ assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_equal("C:/ruby/userprofile", Dir.home)
+ end;
+
+ env['HOMEDRIVE'] = "C:"
+ env['HOMEPATH'] = "\\ruby\\homepath"
+ assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_equal("C:/ruby/userprofile", Dir.home)
+ end;
+
+ env['USERPROFILE'] = nil
+ assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_equal("C:/ruby/homepath", Dir.home)
+ end;
+ end
+
+ def test_children_long_name
+ Dir.mktmpdir do |dirname|
+ longest_possible_component = "b" * 255
+ long_path = File.join(dirname, longest_possible_component)
+ Dir.mkdir(long_path)
+ File.write("#{long_path}/c", "")
+ assert_equal(%w[c], Dir.children(long_path))
+ ensure
+ File.unlink("#{long_path}/c")
+ Dir.rmdir(long_path)
+ end
+ rescue Errno::ENOENT
+ omit "File system does not support long file name"
+ end
end
def test_home
- env_home = ENV["HOME"]
- env_logdir = ENV["LOGDIR"]
- ENV.delete("HOME")
- ENV.delete("LOGDIR")
+ setup_envs
ENV["HOME"] = @nodir
- assert_nothing_raised(ArgumentError) {
+ assert_nothing_raised(ArgumentError) do
assert_equal(@nodir, Dir.home)
+ end
+ assert_nothing_raised(ArgumentError) do
assert_equal(@nodir, Dir.home(""))
- if user = ENV["USER"]
- ENV["HOME"] = env_home
- assert_equal(File.expand_path(env_home), Dir.home(user))
+ end
+ if user = ENV["USER"]
+ tilde = windows? ? "~" : "~#{user}"
+ assert_nothing_raised(ArgumentError) do
+ assert_equal(File.expand_path(tilde), Dir.home(user))
end
- }
+ end
%W[no:such:user \u{7559 5b88}:\u{756a}].each do |user|
assert_raise_with_message(ArgumentError, /#{user}/) {Dir.home(user)}
end
- ensure
- ENV["HOME"] = env_home
- ENV["LOGDIR"] = env_logdir
+ end
+
+ if Encoding.find("filesystem") == Encoding::UTF_8
+ # On Windows and macOS, file system encoding is always UTF-8.
+ def test_home_utf8
+ setup_envs
+
+ ENV["HOME"] = "/\u{e4}~\u{1f3e0}"
+ assert_equal("/\u{e4}~\u{1f3e0}", Dir.home)
+ end
end
def test_symlinks_not_resolved
@@ -372,8 +701,8 @@ class TestDir < Test::Unit::TestCase
Dir.mkdir('some-dir')
File.write('some-dir/foo', 'some content')
- assert_equal [ 'dir-symlink', 'some-dir' ], Dir['*'].sort
- assert_equal [ 'dir-symlink', 'some-dir', 'some-dir/foo' ], Dir['**/*'].sort
+ assert_equal [ 'dir-symlink', 'some-dir' ], Dir['*']
+ assert_equal [ 'dir-symlink', 'some-dir', 'some-dir/foo' ], Dir['**/*']
end
end
end
@@ -388,6 +717,23 @@ class TestDir < Test::Unit::TestCase
}
end
+ def test_for_fd
+ if Dir.respond_to? :for_fd
+ begin
+ new_dir = Dir.new('..')
+ for_fd_dir = Dir.for_fd(new_dir.fileno)
+ assert_equal(new_dir.chdir{Dir.pwd}, for_fd_dir.chdir{Dir.pwd})
+ ensure
+ new_dir&.close
+ if for_fd_dir
+ assert_raise(Errno::EBADF) { for_fd_dir.close }
+ end
+ end
+ else
+ assert_raise(NotImplementedError) { Dir.for_fd(0) }
+ end
+ end
+
def test_empty?
assert_not_send([Dir, :empty?, @root])
a = File.join(@root, "a")
@@ -413,12 +759,27 @@ class TestDir < Test::Unit::TestCase
begin;
Process.setrlimit(Process::RLIMIT_NOFILE, 50)
begin
- tap {tap {tap {(0..100).map {open(IO::NULL)}}}}
+ fs = []
+ tap {tap {tap {(0..100).each {fs << open(IO::NULL)}}}}
rescue Errno::EMFILE
+ ensure
+ fs.clear
end
- list = Dir.glob("*").sort
+ list = Dir.glob("*")
assert_not_empty(list)
assert_equal([*"a".."z"], list)
end;
end if defined?(Process::RLIMIT_NOFILE)
+
+ def test_glob_array_with_destructive_element
+ args = Array.new(100, "")
+ pat = Struct.new(:ary).new(args)
+ args.push(pat, *Array.new(100) {"."*40})
+ def pat.to_path
+ ary.clear
+ GC.start
+ ""
+ end
+ assert_empty(Dir.glob(args))
+ end
end
diff --git a/test/ruby/test_dir_m17n.rb b/test/ruby/test_dir_m17n.rb
index 7584074c7e..cdf8b44ef2 100644
--- a/test/ruby/test_dir_m17n.rb
+++ b/test/ruby/test_dir_m17n.rb
@@ -17,27 +17,19 @@ class TestDir_M17N < Test::Unit::TestCase
assert_separately(["-E#{encoding}"], <<-EOS, :chdir=>dir)
filename = #{code}.chr('UTF-8').force_encoding("#{encoding}")
File.open(filename, "w") {}
- opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".")
+ if /mswin|mingw/ =~ RUBY_PLATFORM
+ filename = filename.encode("UTF-8")
+ end
assert_include(ents, filename)
EOS
return if /cygwin/ =~ RUBY_PLATFORM
assert_separately(%w[-EASCII-8BIT], <<-EOS, :chdir=>dir)
filename = #{code}.chr('UTF-8').force_encoding("ASCII-8BIT")
- opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
- expected_filename = #{code}.chr('UTF-8').encode(Encoding.find("filesystem")) rescue expected_filename = "?"
- expected_filename = expected_filename.force_encoding("ASCII-8BIT")
+ ents = Dir.entries(".")
if /mswin|mingw/ =~ RUBY_PLATFORM
- case
- when ents.include?(filename)
- when ents.include?(expected_filename)
- filename = expected_filename
- else
- ents = Dir.entries(".", {:encoding => Encoding.find("filesystem")})
- filename = expected_filename
- end
+ filename.force_encoding("UTF-8")
end
assert_include(ents, filename)
EOS
@@ -52,7 +44,7 @@ class TestDir_M17N < Test::Unit::TestCase
filename = "\u3042"
File.open(filename, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".", **(opts||{}))
assert_include(ents, filename)
EOS
}
@@ -64,18 +56,18 @@ class TestDir_M17N < Test::Unit::TestCase
return if Bug::File::Fs.fsname(Dir.tmpdir) == "apfs"
with_tmpdir {|d|
assert_separately(%w[-EASCII-8BIT], <<-'EOS', :chdir=>d)
- filename = "\xff".force_encoding("ASCII-8BIT") # invalid byte sequence as UTF-8
+ filename = "\xff".dup.force_encoding("ASCII-8BIT") # invalid byte sequence as UTF-8
File.open(filename, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".", **(opts||{}))
filename = "%FF" if /darwin/ =~ RUBY_PLATFORM && ents.include?("%FF")
assert_include(ents, filename)
EOS
assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d)
- filename = "\xff".force_encoding("UTF-8") # invalid byte sequence as UTF-8
+ filename = "\xff".dup.force_encoding("UTF-8") # invalid byte sequence as UTF-8
File.open(filename, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".", **(opts||{}))
filename = "%FF" if /darwin/ =~ RUBY_PLATFORM && ents.include?("%FF")
assert_include(ents, filename)
EOS
@@ -85,17 +77,17 @@ class TestDir_M17N < Test::Unit::TestCase
def test_filename_as_bytes_extutf8
with_tmpdir {|d|
assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d)
- filename = "\xc2\xa1".force_encoding("utf-8")
+ filename = "\xc2\xa1".dup.force_encoding("utf-8")
File.open(filename, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".", **(opts||{}))
assert_include(ents, filename)
EOS
assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d)
if /mswin|mingw|darwin/ =~ RUBY_PLATFORM
- filename = "\x8f\xa2\xc2".force_encoding("euc-jp")
+ filename = "\x8f\xa2\xc2".dup.force_encoding("euc-jp")
else
- filename = "\xc2\xa1".force_encoding("euc-jp")
+ filename = "\xc2\xa1".dup.force_encoding("euc-jp")
end
assert_nothing_raised(Errno::ENOENT) do
open(filename) {}
@@ -104,8 +96,8 @@ class TestDir_M17N < Test::Unit::TestCase
# no meaning test on windows
unless /mswin|mingw|darwin/ =~ RUBY_PLATFORM
assert_separately(%W[-EUTF-8], <<-'EOS', :chdir=>d)
- filename1 = "\xc2\xa1".force_encoding("utf-8")
- filename2 = "\xc2\xa1".force_encoding("euc-jp")
+ filename1 = "\xc2\xa1".dup.force_encoding("utf-8")
+ filename2 = "\xc2\xa1".dup.force_encoding("euc-jp")
filename3 = filename1.encode("euc-jp")
filename4 = filename2.encode("utf-8")
assert_file.stat(filename1)
@@ -125,17 +117,17 @@ class TestDir_M17N < Test::Unit::TestCase
filename = "\u3042"
File.open(filename, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".", **(opts||{}))
assert_include(ents, filename)
EOS
assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d)
- filename = "\xA4\xA2".force_encoding("euc-jp")
+ filename = "\xA4\xA2".dup.force_encoding("euc-jp")
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".", **(opts||{}))
assert_include(ents, filename)
EOS
assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d)
- filename = "\xA4\xA2".force_encoding("euc-jp")
+ filename = "\xA4\xA2".dup.force_encoding("euc-jp")
assert_nothing_raised(Errno::ENOENT) do
open(filename) {}
end
@@ -151,22 +143,22 @@ class TestDir_M17N < Test::Unit::TestCase
File.open(filename1, "w") {}
File.open(filename2, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".", **(opts||{}))
assert_include(ents, filename1)
assert_include(ents, filename2)
EOS
assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d)
filename1 = "\u2661" # WHITE HEART SUIT which is not representable in EUC-JP
- filename2 = "\xA4\xA2".force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP
+ filename2 = "\xA4\xA2".dup.force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".", **(opts||{}))
assert_include(ents, filename1)
assert_include(ents, filename2)
EOS
assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d)
filename1 = "\u2661" # WHITE HEART SUIT which is not representable in EUC-JP
filename2 = "\u3042" # HIRAGANA LETTER A which is representable in EUC-JP
- filename3 = "\xA4\xA2".force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP
+ filename3 = "\xA4\xA2".dup.force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP
assert_file.stat(filename1)
assert_file.stat(filename2)
assert_file.stat(filename3)
@@ -180,10 +172,10 @@ class TestDir_M17N < Test::Unit::TestCase
return if /cygwin/ =~ RUBY_PLATFORM
with_tmpdir {|d|
assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d)
- filename = "\xA4\xA2".force_encoding("euc-jp")
+ filename = "\xA4\xA2".dup.force_encoding("euc-jp")
File.open(filename, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".", **(opts||{}))
ents.each {|e| e.force_encoding("ASCII-8BIT") }
if /darwin/ =~ RUBY_PLATFORM
filename = filename.encode("utf-8")
@@ -197,29 +189,25 @@ class TestDir_M17N < Test::Unit::TestCase
return if /cygwin/ =~ RUBY_PLATFORM
with_tmpdir {|d|
assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d)
- filename = "\xA4\xA2".force_encoding("euc-jp")
+ filename = "\xA4\xA2".dup.force_encoding("euc-jp")
File.open(filename, "w") {}
- opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".")
if /darwin/ =~ RUBY_PLATFORM
filename = filename.encode("utf-8").force_encoding("euc-jp")
+ elsif /mswin|mingw/ =~ RUBY_PLATFORM
+ filename = filename.encode("utf-8")
end
assert_include(ents, filename)
EOS
assert_separately(%w[-EASCII-8BIT], <<-'EOS', :chdir=>d)
- filename = "\xA4\xA2".force_encoding('ASCII-8BIT')
- win_expected_filename = filename.encode(Encoding.find("filesystem"), "euc-jp") rescue "?"
- opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ filename = "\xA4\xA2".dup.force_encoding('ASCII-8BIT')
+ ents = Dir.entries(".")
unless ents.include?(filename)
case RUBY_PLATFORM
when /darwin/
filename = filename.encode("utf-8", "euc-jp").b
when /mswin|mingw/
- if ents.include?(win_expected_filename.b)
- ents = Dir.entries(".", {:encoding => Encoding.find("filesystem")})
- filename = win_expected_filename
- end
+ filename = filename.encode("utf-8", "euc-jp")
end
end
assert_include(ents, filename)
@@ -243,19 +231,19 @@ class TestDir_M17N < Test::Unit::TestCase
return if /cygwin/ =~ RUBY_PLATFORM
with_tmpdir {|d|
assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d)
- filename = "\xA4\xA2".force_encoding("euc-jp")
+ filename = "\xA4\xA2".dup.force_encoding("euc-jp")
File.open(filename, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".", **(opts||{}))
if /darwin/ =~ RUBY_PLATFORM
filename = filename.encode("utf-8", "euc-jp").force_encoding("euc-jp")
end
assert_include(ents, filename)
EOS
assert_separately(%w[-EEUC-JP:UTF-8], <<-'EOS', :chdir=>d)
- filename = "\u3042"
+ filename = "\u3042".dup
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
- ents = Dir.entries(".", opts)
+ ents = Dir.entries(".", **(opts||{}))
if /darwin/ =~ RUBY_PLATFORM
filename = filename.force_encoding("euc-jp")
end
@@ -330,7 +318,7 @@ class TestDir_M17N < Test::Unit::TestCase
def test_glob_warning_opendir
with_enc_path do |dir|
- open("#{dir}/x", "w") {}
+ File.binwrite("#{dir}/x", "")
File.chmod(0300, dir)
next if File.readable?(dir)
assert_warning(/#{dir}/) do
@@ -341,7 +329,7 @@ class TestDir_M17N < Test::Unit::TestCase
def test_glob_warning_match_all
with_enc_path do |dir|
- open("#{dir}/x", "w") {}
+ File.binwrite("#{dir}/x", "")
File.chmod(0000, dir)
next if File.readable?(dir)
assert_warning(/#{dir}/) do
@@ -362,7 +350,7 @@ class TestDir_M17N < Test::Unit::TestCase
end
def test_glob_escape_multibyte
- name = "\x81\\".force_encoding(Encoding::Shift_JIS)
+ name = "\x81\\".dup.force_encoding(Encoding::Shift_JIS)
with_tmpdir do
open(name, "w") {} rescue next
match, = Dir.glob("#{name}*")
@@ -374,9 +362,9 @@ class TestDir_M17N < Test::Unit::TestCase
def test_glob_encoding
with_tmpdir do
list = %W"file_one.ext file_two.ext \u{6587 4ef6}1.txt \u{6587 4ef6}2.txt"
- list.each {|f| open(f, "w") {}}
- a = "file_one*".force_encoding Encoding::IBM437
- b = "file_two*".force_encoding Encoding::EUC_JP
+ list.each {|f| File.binwrite(f, "")}
+ a = "file_one*".dup.force_encoding Encoding::IBM437
+ b = "file_two*".dup.force_encoding Encoding::EUC_JP
assert_equal([a, b].map(&:encoding), Dir[a, b].map(&:encoding))
if Bug::File::Fs.fsname(Dir.pwd) == "apfs"
# High Sierra's APFS cannot use filenames with undefined character
@@ -387,7 +375,7 @@ class TestDir_M17N < Test::Unit::TestCase
Dir.mkdir(dir)
list << dir
bug12081 = '[ruby-core:73868] [Bug #12081]'
- a = "*".force_encoding("us-ascii")
+ a = "*".dup.force_encoding("us-ascii")
result = Dir[a].map {|n|
if n.encoding == Encoding::ASCII_8BIT ||
n.encoding == Encoding::ISO_8859_1 ||
@@ -414,13 +402,8 @@ class TestDir_M17N < Test::Unit::TestCase
orig.each {|n| open(n, "w") {}}
enc = Encoding.find("filesystem")
enc = Encoding::ASCII_8BIT if enc == Encoding::US_ASCII
- if /mswin|mingw/ =~ RUBY_PLATFORM
- opts = {:encoding => enc}
- orig.map! {|o| o.encode("filesystem") rescue o.tr("^a-z", "?")}
- else
- orig.each {|o| o.force_encoding(enc) }
- end
- ents = Dir.entries(".", opts).reject {|n| /\A\./ =~ n}
+ orig.each {|o| o.force_encoding(enc) }
+ ents = Dir.entries(".").reject {|n| /\A\./ =~ n}
ents.sort!
PP.assert_equal(orig, ents, bug7267)
}
@@ -431,13 +414,9 @@ class TestDir_M17N < Test::Unit::TestCase
expected = []
results = []
orig.each {|o|
- if /mswin|mingw/ =~ RUBY_PLATFORM
- n = (o.encode("filesystem") rescue next)
- else
- enc = Encoding.find("filesystem")
- enc = Encoding::ASCII_8BIT if enc == Encoding::US_ASCII
- n = o.dup.force_encoding(enc)
- end
+ enc = Encoding.find("filesystem")
+ enc = Encoding::ASCII_8BIT if enc == Encoding::US_ASCII
+ n = o.dup.force_encoding(enc)
expected << n
with_tmpdir {
Dir.mkdir(o)
diff --git a/test/ruby/test_dup.rb b/test/ruby/test_dup.rb
new file mode 100644
index 0000000000..75c4fc0339
--- /dev/null
+++ b/test/ruby/test_dup.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestDup < Test::Unit::TestCase
+ module M001; end
+ module M002; end
+ module M003; include M002; end
+ module M002; include M001; end
+ module M003; include M002; end
+
+ def test_dup
+ foo = Object.new
+ def foo.test
+ "test"
+ end
+ bar = foo.dup
+ def bar.test2
+ "test2"
+ end
+
+ assert_equal("test2", bar.test2)
+ assert_raise(NoMethodError) { bar.test }
+ assert_equal("test", foo.test)
+
+ assert_raise(NoMethodError) {foo.test2}
+
+ assert_equal([M003, M002, M001], M003.ancestors)
+ end
+
+ def test_frozen_properties_not_retained_on_dup
+ obj = Object.new.freeze
+ duped_obj = obj.dup
+
+ assert_predicate(obj, :frozen?)
+ refute_predicate(duped_obj, :frozen?)
+ end
+
+ def test_ivar_retained_on_dup
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ duped_obj = obj.dup
+
+ assert_equal(obj.instance_variable_get(:@a), 1)
+ assert_equal(duped_obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_ivars_retained_on_extended_obj_dup
+ ivars = { :@a => 1, :@b => 2, :@c => 3, :@d => 4 }
+ obj = Object.new
+ ivars.each do |ivar_name, val|
+ obj.instance_variable_set(ivar_name, val)
+ end
+
+ duped_obj = obj.dup
+
+ ivars.each do |ivar_name, val|
+ assert_equal(obj.instance_variable_get(ivar_name), val)
+ assert_equal(duped_obj.instance_variable_get(ivar_name), val)
+ end
+ end
+
+ def test_frozen_properties_not_retained_on_dup_with_ivar
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ obj.freeze
+
+ duped_obj = obj.dup
+
+ assert_predicate(obj, :frozen?)
+ assert_equal(obj.instance_variable_get(:@a), 1)
+
+ refute_predicate(duped_obj, :frozen?)
+ assert_equal(duped_obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_user_flags
+ assert_separately([], <<-EOS)
+ #
+ class Array
+ undef initialize_copy
+ def initialize_copy(*); end
+ end
+ x = [1, 2, 3].dup
+ assert_equal [], x, '[Bug #14847]'
+ EOS
+
+ assert_separately([], <<-EOS)
+ #
+ class Array
+ undef initialize_copy
+ def initialize_copy(*); end
+ end
+ x = [1,2,3,4,5,6,7][1..-2].dup
+ x.push(1,1,1,1,1)
+ assert_equal [1, 1, 1, 1, 1], x, '[Bug #14847]'
+ EOS
+
+ assert_separately([], <<-EOS)
+ #
+ class Hash
+ undef initialize_copy
+ def initialize_copy(*); end
+ end
+ h = {}
+ h.default_proc = proc { raise }
+ h = h.dup
+ assert_equal nil, h[:not_exist], '[Bug #14847]'
+ EOS
+ end
+end
diff --git a/test/ruby/test_econv.rb b/test/ruby/test_econv.rb
index 6f098db454..1d0641e918 100644
--- a/test/ruby/test_econv.rb
+++ b/test/ruby/test_econv.rb
@@ -3,7 +3,12 @@ require 'test/unit'
class TestEncodingConverter < Test::Unit::TestCase
def check_ec(edst, esrc, eres, dst, src, ec, off, len, opts=nil)
- res = ec.primitive_convert(src, dst, off, len, opts)
+ case opts
+ when Hash
+ res = ec.primitive_convert(src, dst, off, len, **opts)
+ else
+ res = ec.primitive_convert(src, dst, off, len, opts)
+ end
assert_equal([edst.b, esrc.b, eres],
[dst.b, src.b, res])
end
@@ -680,7 +685,6 @@ class TestEncodingConverter < Test::Unit::TestCase
ec = Encoding::Converter.new("utf-8", "euc-jp")
assert_raise(Encoding::InvalidByteSequenceError) { ec.convert("a\x80") }
assert_raise(Encoding::UndefinedConversionError) { ec.convert("\ufffd") }
- assert_predicate(ec.convert("abc".taint), :tainted?)
ret = ec.primitive_convert(nil, "", nil, nil)
assert_equal(:finished, ret)
assert_raise(ArgumentError) { ec.convert("a") }
@@ -799,7 +803,7 @@ class TestEncodingConverter < Test::Unit::TestCase
assert_equal('', ec.finish)
ec = Encoding::Converter.new("", "xml_attr_content_escape")
- assert_equal('&amp;&lt;&gt;&quot;', ec.convert("&<>\""))
+ assert_equal('&amp;&lt;&gt;&quot;&apos;', ec.convert("&<>\"'"))
assert_equal('', ec.finish)
end
@@ -840,7 +844,7 @@ class TestEncodingConverter < Test::Unit::TestCase
def test_xml_hasharg
assert_equal("&amp;\e$B$&\e(B&#x2665;&amp;\"'".force_encoding("iso-2022-jp"),
"&\u3046\u2665&\"'".encode("iso-2022-jp", xml: :text))
- assert_equal("\"&amp;\e$B$&\e(B&#x2661;&amp;&quot;'\"".force_encoding("iso-2022-jp"),
+ assert_equal("\"&amp;\e$B$&\e(B&#x2661;&amp;&quot;&apos;\"".force_encoding("iso-2022-jp"),
"&\u3046\u2661&\"'".encode("iso-2022-jp", xml: :attr))
assert_equal("&amp;\u3046\u2661&amp;\"'".force_encoding("utf-8"),
@@ -908,11 +912,26 @@ class TestEncodingConverter < Test::Unit::TestCase
assert_raise_with_message(ArgumentError, /\u{3042}/) {
Encoding::Converter.new("", "", newline: "\u{3042}".to_sym)
}
+ newlines = %i[universal_newline crlf_newline cr_newline]
+ (2..newlines.size).each do |i|
+ newlines.combination(i) do |opts|
+ assert_raise(Encoding::ConverterNotFoundError, "#{opts} are mutually exclusive") do
+ Encoding::Converter.new("", "", **opts.inject({}) {|o,nl|o[nl]=true;o})
+ end
+ end
+ end
+ newlines.each do |nl|
+ opts = {newline: :universal, nl => true}
+ ec2 = assert_warning(/:newline option precedes/, opts.inspect) do
+ Encoding::Converter.new("", "", **opts)
+ end
+ assert_equal(ec1, ec2)
+ end
end
def test_default_external
Encoding.list.grep(->(enc) {/\AISO-8859-\d+\z/i =~ enc.name}) do |enc|
- assert_separately(%W[--disable=gems -d - #{enc.name}], <<-EOS, ignore_stderr: true)
+ assert_separately(%W[-d - #{enc.name}], <<-EOS, ignore_stderr: true)
Encoding.default_external = ext = ARGV[0]
Encoding.default_internal = int ='utf-8'
assert_nothing_raised do
diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb
index 8f73a8fce1..0cd5bf49dc 100644
--- a/test/ruby/test_encoding.rb
+++ b/test/ruby/test_encoding.rb
@@ -33,10 +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_not_predicate(e, :tainted?)
- Marshal.load(Marshal.dump(e).taint)
- assert_not_predicate(e, :tainted?, '[ruby-core:71793] [Bug #11760]')
+ assert_same(e, Marshal.load(Marshal.dump(e)))
end
end
@@ -58,14 +55,6 @@ class TestEncoding < Test::Unit::TestCase
assert_raise(TypeError, bug5150) {Encoding.find(1)}
end
- def test_replicate
- assert_instance_of(Encoding, Encoding::UTF_8.replicate('UTF-8-ANOTHER'))
- assert_instance_of(Encoding, Encoding::ISO_2022_JP.replicate('ISO-2022-JP-ANOTHER'))
- bug3127 = '[ruby-dev:40954]'
- assert_raise(TypeError, bug3127) {Encoding::UTF_8.replicate(0)}
- assert_raise(ArgumentError, bug3127) {Encoding::UTF_8.replicate("\0")}
- end
-
def test_dummy_p
assert_equal(true, Encoding::ISO_2022_JP.dummy?)
assert_equal(false, Encoding::UTF_8.dummy?)
@@ -117,12 +106,74 @@ class TestEncoding < Test::Unit::TestCase
end
def test_errinfo_after_autoload
- assert_separately(%w[--disable=gems], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
bug9038 = '[ruby-core:57949] [Bug #9038]'
begin;
- assert_raise_with_message(SyntaxError, /unknown regexp option - Q/, bug9038) {
+ e = assert_raise_with_message(SyntaxError, /unknown regexp option - Q/, bug9038) {
eval("/regexp/sQ")
}
+ assert_include(e.message, "/regexp/sQ\n")
+ end;
+ end
+
+ def test_nonascii_library_path
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}".force_encoding("US-ASCII"))
+ begin;
+ assert_equal(Encoding::US_ASCII, __ENCODING__)
+ $:.unshift("/\x80")
+ assert_raise_with_message(LoadError, /\[Bug #16382\]/) do
+ require "[Bug #16382]"
+ end
+ end;
+ end
+
+ def test_ractor_load_encoding
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ 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_enum.rb b/test/ruby/test_enum.rb
index 2167271886..237bdc8a4d 100644
--- a/test/ruby/test_enum.rb
+++ b/test/ruby/test_enum.rb
@@ -27,7 +27,6 @@ class TestEnumerable < Test::Unit::TestCase
end
end
@verbose = $VERBOSE
- $VERBOSE = nil
end
def teardown
@@ -63,11 +62,32 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal([[2, 1], [2, 4]], a)
end
+ def test_grep_optimization
+ bug17030 = '[ruby-core:99156]'
+ 'set last match' =~ /set last (.*)/
+ assert_equal([:a, 'b', :c], [:a, 'b', 'z', :c, 42, nil].grep(/[a-d]/), bug17030)
+ assert_equal(['z', 42, nil], [:a, 'b', 'z', :c, 42, nil].grep_v(/[a-d]/), bug17030)
+ assert_equal('match', $1, bug17030)
+
+ regexp = Regexp.new('x')
+ assert_equal([], @obj.grep(regexp), bug17030) # sanity check
+ def regexp.===(other)
+ true
+ end
+ assert_equal([1, 2, 3, 1, 2], @obj.grep(regexp), bug17030)
+
+ o = Object.new
+ def o.to_str
+ 'hello'
+ end
+ assert_same(o, [o].grep(/ll/).first, bug17030)
+ end
+
def test_count
assert_equal(5, @obj.count)
assert_equal(2, @obj.count(1))
assert_equal(3, @obj.count {|x| x % 2 == 1 })
- assert_equal(2, @obj.count(1) {|x| x % 2 == 1 })
+ assert_equal(2, assert_warning(/given block not used/) {@obj.count(1) {|x| x % 2 == 1 }})
assert_raise(ArgumentError) { @obj.count(0, 1) }
if RUBY_ENGINE == "ruby"
@@ -95,7 +115,7 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal(1, @obj.find_index {|x| x % 2 == 0 })
assert_equal(nil, @obj.find_index {|x| false })
assert_raise(ArgumentError) { @obj.find_index(0, 1) }
- assert_equal(1, @obj.find_index(2) {|x| x == 1 })
+ assert_equal(1, assert_warning(/given block not used/) {@obj.find_index(2) {|x| x == 1 }})
end
def test_find_all
@@ -114,6 +134,12 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal([1, 2, 3, 1, 2], @obj.to_a)
end
+ def test_to_a_keywords
+ @obj.singleton_class.remove_method(:each)
+ def @obj.each(foo:) yield foo end
+ assert_equal([1], @obj.to_a(foo: 1))
+ end
+
def test_to_a_size_symbol
sym = Object.new
class << sym
@@ -144,8 +170,7 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal([], inf.to_a)
end
- def test_to_h
- obj = Object.new
+ StubToH = Object.new.tap do |obj|
def obj.each(*args)
yield(*args)
yield [:key, :value]
@@ -157,6 +182,12 @@ class TestEnumerable < Test::Unit::TestCase
yield kvp
end
obj.extend Enumerable
+ obj.freeze
+ end
+
+ def test_to_h
+ obj = StubToH
+
assert_equal({
:hello => :world,
:key => :value,
@@ -175,13 +206,36 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal "element has wrong array length (expected 2, was 1)", e.message
end
+ def test_to_h_block
+ obj = StubToH
+
+ assert_equal({
+ "hello" => "world",
+ "key" => "value",
+ "other_key" => "other_value",
+ "obtained" => "via_to_ary",
+ }, obj.to_h(:hello, :world) {|k, v| [k.to_s, v.to_s]})
+
+ e = assert_raise(TypeError) {
+ obj.to_h {:not_an_array}
+ }
+ assert_equal "wrong element type Symbol (expected array)", e.message
+
+ e = assert_raise(ArgumentError) {
+ obj.to_h {[1]}
+ }
+ assert_equal "element has wrong array length (expected 2, was 1)", e.message
+ end
+
def test_inject
assert_equal(12, @obj.inject {|z, x| z * x })
assert_equal(48, @obj.inject {|z, x| z * 2 + x })
assert_equal(12, @obj.inject(:*))
assert_equal(24, @obj.inject(2) {|z, x| z * x })
- assert_equal(24, @obj.inject(2, :*) {|z, x| z * x })
+ assert_equal(24, assert_warning(/given block not used/) {@obj.inject(2, :*) {|z, x| z * x }})
assert_equal(nil, @empty.inject() {9})
+
+ assert_raise(ArgumentError) {@obj.inject}
end
FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN']
@@ -202,17 +256,75 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal(15, [3, 5, 7].inject(:+))
assert_float_equal(15.0, [3, 5, 7.0].inject(:+))
assert_equal(2*FIXNUM_MAX, Array.new(2, FIXNUM_MAX).inject(:+))
+ assert_equal(3*FIXNUM_MAX, Array.new(3, FIXNUM_MAX).inject(:+))
assert_equal(2*(FIXNUM_MAX+1), Array.new(2, FIXNUM_MAX+1).inject(:+))
assert_equal(10*FIXNUM_MAX, Array.new(10, FIXNUM_MAX).inject(:+))
assert_equal(0, ([FIXNUM_MAX, 1, -FIXNUM_MAX, -1]*10).inject(:+))
assert_equal(FIXNUM_MAX*10, ([FIXNUM_MAX+1, -1]*10).inject(:+))
assert_equal(2*FIXNUM_MIN, Array.new(2, FIXNUM_MIN).inject(:+))
+ assert_equal(3*FIXNUM_MIN, Array.new(3, FIXNUM_MIN).inject(:+))
assert_equal((FIXNUM_MAX+1).to_f, [FIXNUM_MAX, 1, 0.0].inject(:+))
assert_float_equal(10.0, [3.0, 5].inject(2.0, :+))
assert_float_equal((FIXNUM_MAX+1).to_f, [0.0, FIXNUM_MAX+1].inject(:+))
assert_equal(2.0+3.0i, [2.0, 3.0i].inject(:+))
end
+ def test_inject_op_redefined
+ assert_separately([], "#{<<~"end;"}\n""end")
+ k = Class.new do
+ include Enumerable
+ def each
+ yield 1
+ yield 2
+ yield 3
+ end
+ end
+ all_assertions_foreach("", *%i[+ * / - %]) do |op|
+ bug = '[ruby-dev:49510] [Bug#12178] should respect redefinition'
+ begin
+ Integer.class_eval do
+ alias_method :orig, op
+ define_method(op) do |x|
+ 0
+ end
+ end
+ assert_equal(0, k.new.inject(op), bug)
+ ensure
+ Integer.class_eval do
+ undef_method op
+ alias_method op, :orig
+ end
+ end
+ end;
+ end
+
+ def test_inject_op_private
+ assert_separately([], "#{<<~"end;"}\n""end")
+ k = Class.new do
+ include Enumerable
+ def each
+ yield 1
+ yield 2
+ yield 3
+ end
+ end
+ all_assertions_foreach("", *%i[+ * / - %]) do |op|
+ bug = '[ruby-core:81349] [Bug #13592] should respect visibility'
+ assert_raise_with_message(NoMethodError, /private method/, bug) do
+ begin
+ Integer.class_eval do
+ private op
+ end
+ k.new.inject(op)
+ ensure
+ Integer.class_eval do
+ public op
+ end
+ end
+ end
+ end;
+ end
+
def test_inject_array_op_redefined
assert_separately([], "#{<<~"end;"}\n""end")
all_assertions_foreach("", *%i[+ * / - %]) do |op|
@@ -253,6 +365,25 @@ class TestEnumerable < Test::Unit::TestCase
end;
end
+ def test_refine_Enumerable_then_include
+ assert_separately([], "#{<<~"end;"}\n")
+ module RefinementBug
+ refine Enumerable do
+ def refined_method
+ :rm
+ end
+ end
+ end
+ using RefinementBug
+
+ class A
+ include Enumerable
+ end
+
+ assert_equal(:rm, [].refined_method)
+ end;
+ end
+
def test_partition
assert_equal([[1, 3, 1], [2, 2]], @obj.partition {|x| x % 2 == 1 })
cond = ->(x, i) { x % 2 == 1 }
@@ -268,6 +399,50 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal(h, @obj.each_with_index.group_by(&cond))
end
+ def test_tally
+ h = {1 => 2, 2 => 2, 3 => 1}
+ assert_equal(h, @obj.tally)
+
+ h = {1 => 5, 2 => 2, 3 => 1, 4 => "x"}
+ assert_equal(h, @obj.tally({1 => 3, 4 => "x"}))
+
+ assert_raise(TypeError) do
+ @obj.tally({1 => ""})
+ end
+
+ h = {1 => 2, 2 => 2, 3 => 1}
+ assert_same(h, @obj.tally(h))
+
+ h = {1 => 2, 2 => 2, 3 => 1}.freeze
+ assert_raise(FrozenError) do
+ @obj.tally(h)
+ end
+ assert_equal({1 => 2, 2 => 2, 3 => 1}, h)
+
+ hash_convertible = Object.new
+ def hash_convertible.to_hash
+ {1 => 3, 4 => "x"}
+ end
+ assert_equal({1 => 5, 2 => 2, 3 => 1, 4 => "x"}, @obj.tally(hash_convertible))
+
+ hash_convertible = Object.new
+ def hash_convertible.to_hash
+ {1 => 3, 4 => "x"}.freeze
+ end
+ assert_raise(FrozenError) do
+ @obj.tally(hash_convertible)
+ end
+ assert_equal({1 => 3, 4 => "x"}, hash_convertible.to_hash)
+
+ assert_raise(TypeError) do
+ @obj.tally(BasicObject.new)
+ end
+
+ h = {1 => 2, 2 => 2, 3 => 1}
+ assert_equal(h, @obj.tally(Hash.new(100)))
+ assert_equal(h, @obj.tally(Hash.new {100}))
+ end
+
def test_first
assert_equal(1, @obj.first)
assert_equal([1, 2, 3], @obj.first(3))
@@ -288,6 +463,17 @@ class TestEnumerable < Test::Unit::TestCase
empty.first
empty.block.call
end;
+
+ bug18475 = '[ruby-dev:107059]'
+ assert_in_out_err([], <<-'end;', [], /unexpected break/, bug18475)
+ e = Enumerator.new do |g|
+ Thread.new do
+ g << 1
+ end.join
+ end
+
+ e.first
+ end;
end
def test_sort
@@ -310,10 +496,25 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal(false, [true, true, false].all?)
assert_equal(true, [].all?)
assert_equal(true, @empty.all?)
- assert_equal(true, @obj.all?(Fixnum))
+ assert_equal(true, @obj.all?(Integer))
assert_equal(false, @obj.all?(1..2))
end
+ def test_all_with_unused_block
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ [1, 2].all?(1) {|x| x == 3 }
+ EOS
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ (1..2).all?(1) {|x| x == 3 }
+ EOS
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ 3.times.all?(1) {|x| x == 3 }
+ EOS
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ {a: 1, b: 2}.all?([:b, 2]) {|x| x == 4 }
+ EOS
+ end
+
def test_any
assert_equal(true, @obj.any? {|x| x >= 3 })
assert_equal(false, @obj.any? {|x| x > 3 })
@@ -329,6 +530,21 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal(true, {a: 1, b: 2}.any?(->(kv) { kv == [:b, 2] }))
end
+ def test_any_with_unused_block
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ [1, 23].any?(1) {|x| x == 1 }
+ EOS
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ (1..2).any?(34) {|x| x == 2 }
+ EOS
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ 3.times.any?(1) {|x| x == 3 }
+ EOS
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ {a: 1, b: 2}.any?([:b, 2]) {|x| x == 4 }
+ EOS
+ end
+
def test_one
assert(@obj.one? {|x| x == 3 })
assert(!(@obj.one? {|x| x == 1 }))
@@ -348,6 +564,21 @@ class TestEnumerable < Test::Unit::TestCase
assert([ nil, true, 99 ].one?(Integer))
end
+ def test_one_with_unused_block
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ [1, 2].one?(1) {|x| x == 3 }
+ EOS
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ (1..2).one?(1) {|x| x == 3 }
+ EOS
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ 3.times.one?(1) {|x| x == 3 }
+ EOS
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ {a: 1, b: 2}.one?([:b, 2]) {|x| x == 4 }
+ EOS
+ end
+
def test_none
assert(@obj.none? {|x| x == 4 })
assert(!(@obj.none? {|x| x == 1 }))
@@ -365,6 +596,21 @@ class TestEnumerable < Test::Unit::TestCase
assert(@empty.none?)
end
+ def test_none_with_unused_block
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ [1, 2].none?(1) {|x| x == 3 }
+ EOS
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ (1..2).none?(1) {|x| x == 3 }
+ EOS
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ 3.times.none?(1) {|x| x == 3 }
+ EOS
+ assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"]
+ {a: 1, b: 2}.none?([:b, 2]) {|x| x == 4 }
+ EOS
+ end
+
def test_min
assert_equal(1, @obj.min)
assert_equal(3, @obj.min {|a,b| b <=> a })
@@ -504,6 +750,9 @@ class TestEnumerable < Test::Unit::TestCase
ary.clear
(1..10).each_slice(11) {|a| ary << a}
assert_equal([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]], ary)
+
+ assert_equal(1..10, (1..10).each_slice(3) { })
+ assert_equal([], [].each_slice(3) { })
end
def test_each_cons
@@ -523,6 +772,9 @@ class TestEnumerable < Test::Unit::TestCase
ary.clear
(1..5).each_cons(6) {|a| ary << a}
assert_empty(ary)
+
+ assert_equal(1..5, (1..5).each_cons(3) { })
+ assert_equal([], [].each_cons(3) { })
end
def test_zip
@@ -591,6 +843,8 @@ class TestEnumerable < Test::Unit::TestCase
end
def test_callcc
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
assert_raise(RuntimeError) do
c = nil
@obj.sort_by {|x| callcc {|c2| c ||= c2 }; x }
@@ -644,6 +898,19 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal([2,1,3,2,1], @obj.reverse_each.to_a)
end
+ def test_reverse_each_memory_corruption
+ bug16354 = '[ruby-dev:50867]'
+ assert_normal_exit %q{
+ size = 1000
+ (0...size).reverse_each do |i|
+ i.inspect
+ ObjectSpace.each_object(Array) do |a|
+ a.clear if a.length == size
+ end
+ end
+ }, bug16354
+ end
+
def test_chunk
e = [].chunk {|elt| true }
assert_equal([], e.to_a)
@@ -1029,4 +1296,64 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal([1, 2, 3, 4, 5, 10], (1..100).uniq{|x| (x**2) % 10 }.first(6))
assert_equal([1, [1, 2]], Foo.new.to_enum.uniq)
end
+
+ def test_compact
+ class << (enum = Object.new)
+ include Enumerable
+ def each
+ yield 3
+ yield nil
+ yield 7
+ yield 9
+ yield nil
+ end
+ end
+
+ assert_equal([3, 7, 9], enum.compact)
+ end
+
+ def test_transient_heap_sort_by
+ klass = Class.new do
+ include Comparable
+ attr_reader :i
+ def initialize e
+ @i = e
+ end
+ def <=> other
+ GC.start
+ i <=> other.i
+ end
+ end
+ assert_equal [1, 2, 3, 4, 5], (1..5).sort_by{|e| klass.new e}
+ end
+
+ def test_filter_map
+ @obj = (1..8).to_a
+ assert_equal([4, 8, 12, 16], @obj.filter_map { |i| i * 2 if i.even? })
+ assert_equal([2, 4, 6, 8, 10, 12, 14, 16], @obj.filter_map { |i| i * 2 })
+ assert_equal([0, 0, 0, 0, 0, 0, 0, 0], @obj.filter_map { 0 })
+ assert_equal([], @obj.filter_map { false })
+ assert_equal([], @obj.filter_map { nil })
+ assert_instance_of(Enumerator, @obj.filter_map)
+ end
+
+ def test_ruby_svar
+ klass = Class.new do
+ include Enumerable
+ def each
+ %w(bar baz).each{|e| yield e}
+ end
+ end
+ svars = []
+ klass.new.grep(/(b.)/) { svars << $1 }
+ assert_equal(["ba", "ba"], svars)
+ end
+
+ def test_all_fast
+ data = { "key" => { "key2" => 1 } }
+ kk = vv = nil
+ data.all? { |(k, v)| kk, vv = k, v }
+ assert_equal(kk, "key")
+ assert_equal(vv, { "key2" => 1 })
+ end
end
diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb
index 66a45cc14e..9b972d7b22 100644
--- a/test/ruby/test_enumerator.rb
+++ b/test/ruby/test_enumerator.rb
@@ -69,18 +69,17 @@ class TestEnumerator < Test::Unit::TestCase
def test_initialize
assert_equal([1, 2, 3], @obj.to_enum(:foo, 1, 2, 3).to_a)
- _, err = capture_io do
- assert_equal([1, 2, 3], Enumerator.new(@obj, :foo, 1, 2, 3).to_a)
- end
- assert_match 'Enumerator.new without a block is deprecated', err
+ assert_raise(ArgumentError) {
+ Enumerator.new(@obj, :foo, 1, 2, 3)
+ }
assert_equal([1, 2, 3], Enumerator.new { |y| i = 0; loop { y << (i += 1) } }.take(3))
assert_raise(ArgumentError) { Enumerator.new }
enum = @obj.to_enum
assert_raise(NoMethodError) { enum.each {} }
enum.freeze
- assert_raise(FrozenError) {
- capture_io do
+ assert_raise(ArgumentError) {
+ capture_output do
# warning: Enumerator.new without a block is deprecated; use Object#to_enum
enum.__send__(:initialize, @obj, :foo)
end
@@ -113,6 +112,11 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal([[1,2,3],[4,5,6],[7,8,9],[10]], (1..10).each_slice(3).to_a)
end
+ def test_each_slice_size
+ assert_equal(4, (1..10).each_slice(3).size)
+ assert_equal(Float::INFINITY, 1.step.each_slice(3).size)
+ end
+
def test_cons
a = [[1,2,3], [2,3,4], [3,4,5], [4,5,6], [5,6,7], [6,7,8], [7,8,9], [8,9,10]]
assert_equal(a, (1..10).each_cons(3).to_a)
@@ -123,6 +127,17 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal([[1,5],[2,6],[3,7]], @obj.to_enum(:foo, 1, 2, 3).with_index(5).to_a)
end
+ def test_with_index_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ assert_equal([[1, 0], [2, 1], [3, 2]], @obj.to_enum(:foo, 1, 2, 3).with_index.to_a)
+ assert_equal([[1, 5], [2, 6], [3, 7]], @obj.to_enum(:foo, 1, 2, 3).with_index(5).to_a)
+
+ s = 1 << (8 * 1.size - 2)
+ assert_equal([[1, s], [2, s + 1], [3, s + 2]], @obj.to_enum(:foo, 1, 2, 3).with_index(s).to_a)
+ end
+ end
+
def test_with_index_large_offset
bug8010 = '[ruby-dev:47131] [Bug #8010]'
s = 1 << (8*1.size-2)
@@ -240,6 +255,26 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal(res, exc.result)
end
+ def test_stopiteration_rescue
+ e = [1].each
+ res = e.each {}
+ e.next
+ exc0 = assert_raise(StopIteration) { e.peek }
+ assert_include(exc0.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:")
+ assert_nil(exc0.cause)
+ assert_equal(res, exc0.result)
+
+ exc1 = assert_raise(StopIteration) { e.next }
+ assert_include(exc1.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:")
+ assert_same(exc0, exc1.cause)
+ assert_equal(res, exc1.result)
+
+ exc2 = assert_raise(StopIteration) { e.next }
+ assert_include(exc2.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:")
+ assert_same(exc0, exc2.cause)
+ assert_equal(res, exc2.result)
+ end
+
def test_next_values
o = Object.new
def o.each
@@ -301,8 +336,11 @@ class TestEnumerator < Test::Unit::TestCase
yield
end
ary = []
- e = o.to_enum.each(ary)
- e.next
+ e = o.to_enum { 1 }
+ assert_equal(1, e.size)
+ e_arg = e.each(ary)
+ assert_equal(nil, e_arg.size)
+ e_arg.next
assert_equal([1], ary)
end
@@ -404,6 +442,12 @@ class TestEnumerator < Test::Unit::TestCase
e = (0..10).each_cons(2)
assert_equal("#<Enumerator: 0..10:each_cons(2)>", e.inspect)
+ e = (0..10).each_with_object({})
+ assert_equal("#<Enumerator: 0..10:each_with_object({})>", e.inspect)
+
+ e = (0..10).each_with_object(a: 1)
+ assert_equal("#<Enumerator: 0..10:each_with_object(a: 1)>", e.inspect)
+
e = Enumerator.new {|y| y.yield; 10 }
assert_match(/\A#<Enumerator: .*:each>/, e.inspect)
@@ -476,8 +520,29 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal([1], y.yield(1))
assert_equal([1, 2], y.yield(2))
assert_equal([1, 2, 3], y.yield(3))
+ assert_equal([1, 2, 3, 4], y.yield(4, 5))
+
+ a = []
+ y = Enumerator::Yielder.new {|*x| a.concat(x) }
+ assert_equal([1], y.yield(1))
+ assert_equal([1, 2, 3], y.yield(2, 3))
assert_raise(LocalJumpError) { Enumerator::Yielder.new }
+
+ # to_proc (explicit)
+ a = []
+ y = Enumerator::Yielder.new {|x| a << x }
+ b = y.to_proc
+ assert_kind_of(Proc, b)
+ assert_equal([1], b.call(1))
+ assert_equal([1], a)
+
+ # to_proc (implicit)
+ e = Enumerator.new { |y|
+ assert_kind_of(Enumerator::Yielder, y)
+ [1, 2, 3].each(&y)
+ }
+ assert_equal([1, 2, 3], e.to_a)
end
def test_size
@@ -505,14 +570,14 @@ class TestEnumerator < Test::Unit::TestCase
def test_size_for_enum_created_from_array
arr = %w[hello world]
%i[each each_with_index reverse_each sort_by! sort_by map map!
- keep_if reject! reject select! select delete_if].each do |method|
+ keep_if reject! reject select! select filter! filter delete_if].each do |method|
assert_equal arr.size, arr.send(method).size
end
end
def test_size_for_enum_created_from_enumerable
%i[find_all reject map flat_map partition group_by sort_by min_by max_by
- minmax_by each_with_index reverse_each each_entry].each do |method|
+ minmax_by each_with_index reverse_each each_entry filter_map].each do |method|
assert_equal nil, @obj.send(method).size
assert_equal 42, @sized.send(method).size
end
@@ -522,7 +587,7 @@ class TestEnumerator < Test::Unit::TestCase
def test_size_for_enum_created_from_hash
h = {a: 1, b: 2, c: 3}
- methods = %i[delete_if reject reject! select select! keep_if each each_key each_pair]
+ methods = %i[delete_if reject reject! select select! filter filter! keep_if each each_key each_pair]
enums = methods.map {|method| h.send(method)}
s = enums.group_by(&:size)
assert_equal([3], s.keys, ->{s.reject!{|k| k==3}.inspect})
@@ -532,7 +597,7 @@ class TestEnumerator < Test::Unit::TestCase
end
def test_size_for_enum_created_from_env
- %i[each_pair reject! delete_if select select! keep_if].each do |method|
+ %i[each_pair reject! delete_if select select! filter filter! keep_if].each do |method|
assert_equal ENV.size, ENV.send(method).size
end
end
@@ -632,7 +697,7 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal 4, (1..10).step(3).size
assert_equal 3, (1...10).step(3).size
assert_equal Float::INFINITY, (42..Float::INFINITY).step(2).size
- assert_raise(ArgumentError){ (1..10).step(-2).size }
+ assert_equal 0, (1..10).step(-2).size
end
def test_size_for_downup_to
@@ -661,5 +726,348 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal([0, 1], u.force)
assert_equal([0, 1], u.force)
end
-end
+ def test_compact
+ u = [0, 1, nil, 2, 3, nil].to_enum.lazy.compact
+ assert_equal([0, 1, 2, 3], u.force)
+ end
+
+ def test_enum_chain_and_plus
+ r = 1..5
+
+ e1 = r.chain()
+ assert_kind_of(Enumerator::Chain, e1)
+ assert_equal(5, e1.size)
+ ary = []
+ e1.each { |x| ary << x }
+ assert_equal([1, 2, 3, 4, 5], ary)
+
+ e2 = r.chain([6, 7, 8])
+ assert_kind_of(Enumerator::Chain, e2)
+ assert_equal(8, e2.size)
+ ary = []
+ e2.each { |x| ary << x }
+ assert_equal([1, 2, 3, 4, 5, 6, 7, 8], ary)
+
+ e3 = r.chain([6, 7], 8.step)
+ assert_kind_of(Enumerator::Chain, e3)
+ assert_equal(Float::INFINITY, e3.size)
+ ary = []
+ e3.take(10).each { |x| ary << x }
+ assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary)
+
+ # `a + b + c` should not return `Enumerator::Chain.new(a, b, c)`
+ # because it is expected that `(a + b).each` be called.
+ e4 = e2.dup
+ class << e4
+ attr_reader :each_is_called
+ def each
+ super
+ @each_is_called = true
+ end
+ end
+ e5 = e4 + 9.step
+ assert_kind_of(Enumerator::Chain, e5)
+ assert_equal(Float::INFINITY, e5.size)
+ ary = []
+ e5.take(10).each { |x| ary << x }
+ assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary)
+ assert_equal(true, e4.each_is_called)
+ end
+
+ def test_chained_enums
+ a = (1..5).each
+
+ e0 = Enumerator::Chain.new()
+ assert_kind_of(Enumerator::Chain, e0)
+ assert_equal(0, e0.size)
+ ary = []
+ e0.each { |x| ary << x }
+ assert_equal([], ary)
+
+ e1 = Enumerator::Chain.new(a)
+ assert_kind_of(Enumerator::Chain, e1)
+ assert_equal(5, e1.size)
+ ary = []
+ e1.each { |x| ary << x }
+ assert_equal([1, 2, 3, 4, 5], ary)
+
+ e2 = Enumerator::Chain.new(a, [6, 7, 8])
+ assert_kind_of(Enumerator::Chain, e2)
+ assert_equal(8, e2.size)
+ ary = []
+ e2.each { |x| ary << x }
+ assert_equal([1, 2, 3, 4, 5, 6, 7, 8], ary)
+
+ e3 = Enumerator::Chain.new(a, [6, 7], 8.step)
+ assert_kind_of(Enumerator::Chain, e3)
+ assert_equal(Float::INFINITY, e3.size)
+ ary = []
+ e3.take(10).each { |x| ary << x }
+ assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary)
+
+ e4 = Enumerator::Chain.new(a, Enumerator.new { |y| y << 6 << 7 << 8 })
+ assert_kind_of(Enumerator::Chain, e4)
+ assert_equal(nil, e4.size)
+ ary = []
+ e4.each { |x| ary << x }
+ assert_equal([1, 2, 3, 4, 5, 6, 7, 8], ary)
+
+ e5 = Enumerator::Chain.new(e1, e2)
+ assert_kind_of(Enumerator::Chain, e5)
+ assert_equal(13, e5.size)
+ ary = []
+ e5.each { |x| ary << x }
+ assert_equal([1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 7, 8], ary)
+
+ rewound = []
+ e1.define_singleton_method(:rewind) { rewound << object_id }
+ e2.define_singleton_method(:rewind) { rewound << object_id }
+ e5.rewind
+ assert_equal(rewound, [e2.object_id, e1.object_id])
+
+ rewound = []
+ a = [1]
+ e6 = Enumerator::Chain.new(a)
+ a.define_singleton_method(:rewind) { rewound << object_id }
+ e6.rewind
+ assert_equal(rewound, [])
+
+ assert_equal(
+ '#<Enumerator::Chain: [' +
+ '#<Enumerator::Chain: [' +
+ '#<Enumerator: 1..5:each>' +
+ ']>, ' +
+ '#<Enumerator::Chain: [' +
+ '#<Enumerator: 1..5:each>, ' +
+ '[6, 7, 8]' +
+ ']>' +
+ ']>',
+ e5.inspect
+ )
+ end
+
+ def test_chain_with_index
+ assert_equal([[3, 0], [4, 1]], [3].chain([4]).with_index.to_a)
+ end
+
+ def test_lazy_chain
+ ea = (10..).lazy.select(&:even?).take(10)
+ ed = (20..).lazy.select(&:odd?)
+ chain = (ea + ed).select{|x| x % 3 == 0}
+ assert_equal(12, chain.next)
+ assert_equal(18, chain.next)
+ assert_equal(24, chain.next)
+ assert_equal(21, chain.next)
+ assert_equal(27, chain.next)
+ assert_equal(33, chain.next)
+ end
+
+ def test_lazy_chain_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ ea = (10..).lazy.select(&:even?).take(10)
+ ed = (20..).lazy.select(&:odd?)
+ chain = (ea + ed).select{|x| x % 3 == 0}
+ assert_equal(12, chain.next)
+ assert_equal(18, chain.next)
+ assert_equal(24, chain.next)
+ assert_equal(21, chain.next)
+ assert_equal(27, chain.next)
+ assert_equal(33, chain.next)
+ end
+ end
+
+ def test_chain_undef_methods
+ chain = [1].to_enum + [2].to_enum
+ meths = (chain.methods & [:feed, :next, :next_values, :peek, :peek_values])
+ assert_equal(0, meths.size)
+ end
+
+ def test_produce
+ assert_raise(ArgumentError) { Enumerator.produce }
+ assert_raise(ArgumentError) { Enumerator.produce(a: 1, b: 1) {} }
+
+ # Without initial object
+ passed_args = []
+ enum = Enumerator.produce { |obj| passed_args << obj; (obj || 0).succ }
+ assert_instance_of(Enumerator, enum)
+ assert_equal Float::INFINITY, enum.size
+ assert_equal [1, 2, 3], enum.take(3)
+ assert_equal [nil, 1, 2], passed_args
+
+ # With initial object
+ passed_args = []
+ enum = Enumerator.produce(1) { |obj| passed_args << obj; obj.succ }
+ assert_instance_of(Enumerator, enum)
+ assert_equal Float::INFINITY, enum.size
+ assert_equal [1, 2, 3], enum.take(3)
+ assert_equal [1, 2], passed_args
+
+ # Raising StopIteration
+ words = "The quick brown fox jumps over the lazy dog.".scan(/\w+/)
+ enum = Enumerator.produce { words.shift or raise StopIteration }
+ assert_equal Float::INFINITY, enum.size
+ assert_instance_of(Enumerator, enum)
+ assert_equal %w[The quick brown fox jumps over the lazy dog], enum.to_a
+
+ # Raising StopIteration
+ object = [[[["abc", "def"], "ghi", "jkl"], "mno", "pqr"], "stuv", "wxyz"]
+ enum = Enumerator.produce(object) { |obj|
+ obj.respond_to?(:first) or raise StopIteration
+ obj.first
+ }
+ assert_equal Float::INFINITY, enum.size
+ assert_instance_of(Enumerator, enum)
+ assert_nothing_raised {
+ assert_equal [
+ [[[["abc", "def"], "ghi", "jkl"], "mno", "pqr"], "stuv", "wxyz"],
+ [[["abc", "def"], "ghi", "jkl"], "mno", "pqr"],
+ [["abc", "def"], "ghi", "jkl"],
+ ["abc", "def"],
+ "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
+ c = Class.new do
+ include Enumerable
+ attr_reader :is_lambda
+ def each(&block)
+ return to_enum unless block
+ @is_lambda = block.lambda?
+ end
+ end
+ e = c.new
+ e.chain.each{}
+ assert_equal(false, e.is_lambda)
+ e.chain.each(&->{})
+ assert_equal(true, e.is_lambda)
+ end
+
+ def test_product_new
+ # 0-dimensional
+ e = Enumerator::Product.new
+ assert_instance_of(Enumerator::Product, e)
+ assert_kind_of(Enumerator, e)
+ assert_equal(1, e.size)
+ elts = []
+ e.each { |x| elts << x }
+ assert_equal [[]], elts
+ assert_equal elts, e.to_a
+ heads = []
+ e.each { |x,| heads << x }
+ assert_equal [nil], heads
+
+ # 1-dimensional
+ e = Enumerator::Product.new(1..3)
+ assert_instance_of(Enumerator::Product, e)
+ assert_kind_of(Enumerator, e)
+ assert_equal(3, e.size)
+ elts = []
+ e.each { |x| elts << x }
+ assert_equal [[1], [2], [3]], elts
+ assert_equal elts, e.to_a
+
+ # 2-dimensional
+ e = Enumerator::Product.new(1..3, %w[a b])
+ assert_instance_of(Enumerator::Product, e)
+ assert_kind_of(Enumerator, e)
+ assert_equal(3 * 2, e.size)
+ elts = []
+ e.each { |x| elts << x }
+ assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]], elts
+ assert_equal elts, e.to_a
+ heads = []
+ e.each { |x,| heads << x }
+ assert_equal [1, 1, 2, 2, 3, 3], heads
+
+ # Any enumerable is 0 size
+ assert_equal(0, Enumerator::Product.new([], 1..).size)
+
+ # Reject keyword arguments
+ assert_raise(ArgumentError) {
+ Enumerator::Product.new(1..3, foo: 1, bar: 2)
+ }
+ end
+
+ def test_s_product
+ # without a block
+ e = Enumerator.product(1..3, %w[a b])
+ assert_instance_of(Enumerator::Product, e)
+
+ # with a block
+ elts = []
+ ret = Enumerator.product(1..3) { |x| elts << x }
+ assert_equal(nil, ret)
+ assert_equal [[1], [2], [3]], elts
+ assert_equal elts, Enumerator.product(1..3).to_a
+
+ # an infinite enumerator and a finite enumerable
+ e = Enumerator.product(1.., 'a'..'c')
+ assert_equal(Float::INFINITY, e.size)
+ assert_equal [[1, "a"], [1, "b"], [1, "c"], [2, "a"]], e.take(4)
+
+ # an infinite enumerator and an unknown enumerator
+ e = Enumerator.product(1.., Enumerator.new { |y| y << 'a' << 'b' })
+ assert_equal(Float::INFINITY, e.size)
+ assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4)
+
+ # an infinite enumerator and an unknown enumerator
+ e = Enumerator.product(1..3, Enumerator.new { |y| y << 'a' << 'b' })
+ assert_equal(nil, e.size)
+ assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4)
+
+ assert_equal(0, Enumerator.product([], 1..).size)
+
+ # Reject keyword arguments
+ assert_raise(ArgumentError) {
+ Enumerator.product(1..3, foo: 1, bar: 2)
+ }
+ end
+
+ def test_freeze
+ e = 3.times.freeze
+ assert_raise(FrozenError) { e.next }
+ assert_raise(FrozenError) { e.next_values }
+ assert_raise(FrozenError) { e.peek }
+ assert_raise(FrozenError) { e.peek_values }
+ assert_raise(FrozenError) { e.feed 1 }
+ assert_raise(FrozenError) { e.rewind }
+ end
+
+ def test_sum_of_numeric
+ num = Class.new(Numeric) do
+ attr_reader :to_f
+ def initialize(val)
+ @to_f = Float(val)
+ end
+ end
+
+ ary = [5, 10, 20].map {|i| num.new(i)}
+
+ assert_equal(35.0, ary.sum)
+ enum = ary.each
+ assert_equal(35.0, enum.sum)
+ end
+end
diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb
index ffed94efa6..d17e300bce 100644
--- a/test/ruby/test_env.rb
+++ b/test/ruby/test_env.rb
@@ -2,7 +2,9 @@
require 'test/unit'
class TestEnv < Test::Unit::TestCase
- IGNORE_CASE = /bccwin|mswin|mingw/ =~ RUBY_PLATFORM
+ windows = /bccwin|mswin|mingw/ =~ RUBY_PLATFORM
+ IGNORE_CASE = windows
+ ENCODING = windows ? Encoding::UTF_8 : Encoding.find("locale")
PATH_ENV = "PATH"
INVALID_ENVVARS = [
"foo\0bar",
@@ -22,7 +24,6 @@ class TestEnv < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
@backup = ENV.to_hash
ENV.delete('test')
ENV.delete('TEST')
@@ -46,7 +47,6 @@ class TestEnv < Test::Unit::TestCase
end
ENV['TEST'] = 'bar'
assert_equal('bar', ENV['TEST'])
- assert_predicate(ENV['TEST'], :tainted?)
if IGNORE_CASE
assert_equal('bar', ENV['test'])
else
@@ -64,6 +64,41 @@ class TestEnv < Test::Unit::TestCase
}
end
+ def test_dup
+ assert_raise(TypeError) {
+ ENV.dup
+ }
+ end
+
+ def test_clone
+ message = /Cannot clone ENV/
+ assert_raise_with_message(TypeError, message) {
+ ENV.clone
+ }
+ assert_raise_with_message(TypeError, message) {
+ ENV.clone(freeze: false)
+ }
+ assert_raise_with_message(TypeError, message) {
+ ENV.clone(freeze: nil)
+ }
+ assert_raise_with_message(TypeError, message) {
+ ENV.clone(freeze: true)
+ }
+
+ assert_raise(ArgumentError) {
+ ENV.clone(freeze: 1)
+ }
+ assert_raise(ArgumentError) {
+ ENV.clone(foo: false)
+ }
+ assert_raise(ArgumentError) {
+ ENV.clone(1)
+ }
+ assert_raise(ArgumentError) {
+ ENV.clone(1, foo: false)
+ }
+ end
+
def test_has_value
val = 'a'
val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase)
@@ -85,7 +120,6 @@ class TestEnv < Test::Unit::TestCase
ENV['test'] = val[0...-1]
assert_nil(ENV.key(val))
- assert_nil(ENV.index(val))
assert_nil(ENV.key(val.upcase))
ENV['test'] = val
if IGNORE_CASE
@@ -107,13 +141,13 @@ class TestEnv < Test::Unit::TestCase
assert_invalid_env {|v| ENV.delete(v)}
assert_nil(ENV.delete("TEST"))
assert_nothing_raised { ENV.delete(PATH_ENV) }
+ assert_equal("NO TEST", ENV.delete("TEST") {|name| "NO "+name})
end
def test_getenv
assert_invalid_env {|v| ENV[v]}
ENV[PATH_ENV] = ""
assert_equal("", ENV[PATH_ENV])
- assert_predicate(ENV[PATH_ENV], :tainted?)
assert_nil(ENV[""])
end
@@ -122,7 +156,7 @@ class TestEnv < Test::Unit::TestCase
assert_equal("foo", ENV.fetch("test"))
ENV.delete("test")
feature8649 = '[ruby-core:56062] [Feature #8649]'
- e = assert_raise_with_message(KeyError, 'key not found: "test"', feature8649) do
+ e = assert_raise_with_message(KeyError, /key not found: "test"/, feature8649) do
ENV.fetch("test")
end
assert_same(ENV, e.receiver)
@@ -136,7 +170,6 @@ class TestEnv < Test::Unit::TestCase
assert_nothing_raised { ENV.fetch(PATH_ENV, "foo") }
ENV[PATH_ENV] = ""
assert_equal("", ENV.fetch(PATH_ENV))
- assert_predicate(ENV.fetch(PATH_ENV), :tainted?)
end
def test_aset
@@ -154,9 +187,6 @@ class TestEnv < Test::Unit::TestCase
assert_equal("test", ENV["foo"])
rescue Errno::EINVAL
end
-
- ENV[PATH_ENV] = "/tmp/".taint
- assert_equal("/tmp/", ENV[PATH_ENV])
end
def test_keys
@@ -222,6 +252,18 @@ class TestEnv < Test::Unit::TestCase
assert_nil(ENV.select! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" })
end
+ def test_filter_bang
+ h1 = {}
+ ENV.each_pair {|k, v| h1[k] = v }
+ ENV["test"] = "foo"
+ ENV.filter! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" }
+ h2 = {}
+ ENV.each_pair {|k, v| h2[k] = v }
+ assert_equal(h1, h2)
+
+ assert_nil(ENV.filter! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" })
+ end
+
def test_keep_if
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
@@ -254,6 +296,43 @@ class TestEnv < Test::Unit::TestCase
end
end
+ def test_filter
+ ENV["test"] = "foo"
+ h = ENV.filter {|k| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }
+ assert_equal(1, h.size)
+ k = h.keys.first
+ v = h.values.first
+ if IGNORE_CASE
+ assert_equal("TEST", k.upcase)
+ assert_equal("FOO", v.upcase)
+ else
+ assert_equal("test", k)
+ assert_equal("foo", v)
+ end
+ end
+
+ def test_slice
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ ENV["bar"] = "rab"
+ assert_equal({}, ENV.slice())
+ assert_equal({}, ENV.slice(""))
+ assert_equal({}, ENV.slice("unknown"))
+ assert_equal({"foo"=>"bar", "baz"=>"qux"}, ENV.slice("foo", "baz"))
+ end
+
+ def test_except
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ ENV["bar"] = "rab"
+ assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, ENV.except())
+ assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, ENV.except(""))
+ assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, ENV.except("unknown"))
+ assert_equal({"bar"=>"rab"}, ENV.except("foo", "baz"))
+ end
+
def test_clear
ENV.clear
assert_equal(0, ENV.size)
@@ -268,12 +347,23 @@ class TestEnv < Test::Unit::TestCase
ENV["foo"] = "bar"
ENV["baz"] = "qux"
s = ENV.inspect
+ expected = [%("foo" => "bar"), %("baz" => "qux")]
+ unless s.start_with?(/\{"foo"/i)
+ expected.reverse!
+ end
+ expected = '{' + expected.join(', ') + '}'
if IGNORE_CASE
s = s.upcase
- assert(s == '{"FOO"=>"BAR", "BAZ"=>"QUX"}' || s == '{"BAZ"=>"QUX", "FOO"=>"BAR"}')
- else
- assert(s == '{"foo"=>"bar", "baz"=>"qux"}' || s == '{"baz"=>"qux", "foo"=>"bar"}')
+ expected = expected.upcase
end
+ assert_equal(expected, s)
+ end
+
+ def test_inspect_encoding
+ ENV.clear
+ key = "VAR\u{e5 e1 e2 e4 e3 101 3042}"
+ ENV[key] = "foo"
+ assert_equal(%{{#{(key.encode(ENCODING) rescue key.b).inspect} => "foo"}}, ENV.inspect)
end
def test_to_a
@@ -282,12 +372,7 @@ class TestEnv < Test::Unit::TestCase
ENV["baz"] = "qux"
a = ENV.to_a
assert_equal(2, a.size)
- if IGNORE_CASE
- a = a.map {|x| x.map {|y| y.upcase } }
- assert(a == [%w(FOO BAR), %w(BAZ QUX)] || a == [%w(BAZ QUX), %w(FOO BAR)])
- else
- assert(a == [%w(foo bar), %w(baz qux)] || a == [%w(baz qux), %w(foo bar)])
- end
+ check([%w(baz qux), %w(foo bar)], a)
end
def test_rehash
@@ -326,8 +411,7 @@ class TestEnv < Test::Unit::TestCase
assert_equal("foo", v)
end
assert_invalid_env {|var| ENV.assoc(var)}
- assert_predicate(v, :tainted?)
- assert_equal(Encoding.find("locale"), v.encoding)
+ assert_equal(ENCODING, v.encoding)
end
def test_has_value2
@@ -361,6 +445,8 @@ class TestEnv < Test::Unit::TestCase
def test_to_h
assert_equal(ENV.to_hash, ENV.to_h)
+ assert_equal(ENV.map {|k, v| ["$#{k}", v.size]}.to_h,
+ ENV.to_h {|k, v| ["$#{k}", v.size]})
end
def test_reject
@@ -371,13 +457,14 @@ class TestEnv < Test::Unit::TestCase
assert_equal(h1, h2)
end
- def check(as, bs)
+ def assert_equal_env(as, bs)
if IGNORE_CASE
- as = as.map {|xs| xs.map {|x| x.upcase } }
- bs = bs.map {|xs| xs.map {|x| x.upcase } }
+ as = as.map {|k, v| [k.upcase, v] }
+ bs = bs.map {|k, v| [k.upcase, v] }
end
assert_equal(as.sort, bs.sort)
end
+ alias check assert_equal_env
def test_shift
ENV.clear
@@ -400,6 +487,8 @@ class TestEnv < Test::Unit::TestCase
ENV["foo"] = "xxx"
ENV.replace({"foo"=>"bar", "baz"=>"qux"})
check(ENV.to_hash.to_a, [%w(foo bar), %w(baz qux)])
+ ENV.replace({"Foo"=>"Bar", "Baz"=>"Qux"})
+ check(ENV.to_hash.to_a, [%w(Foo Bar), %w(Baz Qux)])
end
def test_update
@@ -408,27 +497,39 @@ class TestEnv < Test::Unit::TestCase
ENV["baz"] = "qux"
ENV.update({"baz"=>"quux","a"=>"b"})
check(ENV.to_hash.to_a, [%w(foo bar), %w(baz quux), %w(a b)])
+ ENV.update
+ check(ENV.to_hash.to_a, [%w(foo bar), %w(baz quux), %w(a b)])
+ ENV.update({"foo"=>"zot"}, {"c"=>"d"})
+ check(ENV.to_hash.to_a, [%w(foo zot), %w(baz quux), %w(a b), %w(c d)])
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
- ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| v1 ? k + "_" + v1 + "_" + v2 : v2 }
+ ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 }
check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
+ ENV.update {|k, v1, v2| k + "_" + v1 + "_" + v2 }
+ check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
+ ENV.update({"foo"=>"zot"}, {"c"=>"d"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 }
+ check(ENV.to_hash.to_a, [%w(foo foo_bar_zot), %w(baz baz_qux_quux), %w(a b), %w(c d)])
end
def test_huge_value
- huge_value = "bar" * 40960
- ENV["foo"] = "bar"
- if /mswin|mingw/ =~ RUBY_PLATFORM
- assert_raise(Errno::EINVAL) { ENV["foo"] = huge_value }
- assert_equal("bar", ENV["foo"])
+ if /mswin|ucrt/ =~ RUBY_PLATFORM
+ # On Windows >= Vista each environment variable can be max 32768 characters
+ huge_value = "bar" * 10900
else
- assert_nothing_raised { ENV["foo"] = huge_value }
- assert_equal(huge_value, ENV["foo"])
+ huge_value = "bar" * 40960
end
+ ENV["foo"] = "overwritten"
+ assert_nothing_raised { ENV["foo"] = huge_value }
+ assert_equal(huge_value, ENV["foo"])
end
- if /mswin|mingw/ =~ RUBY_PLATFORM
+ if windows
+ def windows_version
+ @windows_version ||= %x[ver][/Version (\d+)/, 1].to_i
+ end
+
def test_win32_blocksize
keys = []
len = 32767 - ENV.to_a.flatten.inject(1) {|r,e| r + e.bytesize + 1}
@@ -438,14 +539,24 @@ class TestEnv < Test::Unit::TestCase
keys << key
ENV[key] = val
end
- 1.upto(12) {|i|
- assert_raise(Errno::EINVAL) { ENV[key] = val }
- }
+ if windows_version < 6
+ 1.upto(12) {|i|
+ assert_raise(Errno::EINVAL) { ENV[key] = val }
+ }
+ else
+ 1.upto(12) {|i|
+ assert_nothing_raised(Errno::EINVAL) { ENV[key] = val }
+ }
+ end
ensure
keys.each {|k| ENV.delete(k)}
end
end
+ def test_frozen_env
+ assert_raise(TypeError) { ENV.freeze }
+ end
+
def test_frozen
ENV[PATH_ENV] = "/"
ENV.each do |k, v|
@@ -478,6 +589,868 @@ class TestEnv < Test::Unit::TestCase
assert_nil(e1, bug12475)
end
+ def ignore_case_str
+ IGNORE_CASE ? "true" : "false"
+ end
+
+ def str_for_yielding_exception_class(code_str, exception_var: "raised_exception")
+ <<-"end;"
+ #{exception_var} = nil
+ begin
+ #{code_str}
+ rescue Exception => e
+ #{exception_var} = e
+ end
+ 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}.receive
+ assert_raise(#{expected_error_class}) do
+ if error_class < Exception
+ raise error_class
+ end
+ end
+ end;
+ end
+
+ def str_to_yield_invalid_envvar_errors(var_name, code_str)
+ <<-"end;"
+ envvars_to_check = [
+ "foo\0bar",
+ "#{'\xa1\xa1'}".dup.force_encoding(Encoding::UTF_16LE),
+ "foo".dup.force_encoding(Encoding::ISO_2022_JP),
+ ]
+ envvars_to_check.each do |#{var_name}|
+ #{str_for_yielding_exception_class(code_str)}
+ end
+ end;
+ end
+
+ def str_to_receive_invalid_envvar_errors(ractor_var)
+ <<-"end;"
+ 3.times do
+ #{str_for_assert_raise_on_yielded_exception_class(ArgumentError, ractor_var)}
+ end
+ end;
+ end
+
+ STR_DEFINITION_FOR_CHECK = %Q{
+ def check(as, bs)
+ if #{IGNORE_CASE ? "true" : "false"}
+ as = as.map {|k, v| [k.upcase, v] }
+ bs = bs.map {|k, v| [k.upcase, v] }
+ end
+ assert_equal(as.sort, bs.sort)
+ end
+ }
+
+ def test_bracket_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ port << ENV['test']
+ port << ENV['TEST']
+ ENV['test'] = 'foo'
+ port << ENV['test']
+ port << ENV['TEST']
+ ENV['TEST'] = 'bar'
+ 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(port.receive)
+ assert_nil(port.receive)
+ assert_equal('foo', port.receive)
+ if #{ignore_case_str}
+ assert_equal('foo', port.receive)
+ else
+ assert_nil(port.receive)
+ end
+ assert_equal('bar', port.receive)
+ if #{ignore_case_str}
+ assert_equal('bar', port.receive)
+ else
+ assert_equal('foo', port.receive)
+ end
+ 3.times do
+ #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")}
+ end
+ end;
+ end
+
+ def test_dup_in_ractor
+ assert_ractor(<<-"end;")
+ 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, "port")}
+ end;
+ end
+
+ def test_has_value_in_ractor
+ assert_ractor(<<-"end;")
+ 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]
+ port.send(ENV.has_value?(val))
+ port.send(ENV.has_value?(val.upcase))
+ ENV['test'] = val
+ port.send(ENV.has_value?(val))
+ port.send(ENV.has_value?(val.upcase))
+ ENV['test'] = val.upcase
+ 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;")
+ 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]
+ port.send ENV.key(val)
+ port.send ENV.key(val.upcase)
+ ENV['test'] = val
+ port.send ENV.key(val)
+ port.send ENV.key(val.upcase)
+ ENV['test'] = val.upcase
+ port.send ENV.key(val)
+ port.send ENV.key(val.upcase)
+ end
+ assert_nil(port.receive)
+ assert_nil(port.receive)
+ if #{ignore_case_str}
+ assert_equal('TEST', port.receive.upcase)
+ else
+ assert_equal('test', port.receive)
+ end
+ assert_nil(port.receive)
+ assert_nil(port.receive)
+ if #{ignore_case_str}
+ assert_equal('TEST', port.receive.upcase)
+ else
+ assert_equal('test', port.receive)
+ end
+ end;
+
+ end
+
+ def test_delete_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ #{str_to_yield_invalid_envvar_errors("v", "ENV.delete(v)")}
+ port.send ENV.delete("TEST")
+ #{str_for_yielding_exception_class("ENV.delete('#{PATH_ENV}')")}
+ port.send(ENV.delete("TEST"){|name| "NO "+name})
+ end
+ #{str_to_receive_invalid_envvar_errors("port")}
+ assert_nil(port.receive)
+ exception_class = port.receive
+ assert_equal(NilClass, exception_class)
+ assert_equal("NO TEST", port.receive)
+ end;
+ end
+
+ def test_getenv_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ #{str_to_yield_invalid_envvar_errors("v", "ENV[v]")}
+ ENV["#{PATH_ENV}"] = ""
+ port.send ENV["#{PATH_ENV}"]
+ port.send ENV[""]
+ end
+ #{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;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV["test"] = "foo"
+ port.send ENV.fetch("test")
+ ENV.delete("test")
+ #{str_for_yielding_exception_class("ENV.fetch('test')", exception_var: "ex")}
+ 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}'] = ""
+ 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("", port.receive)
+ end;
+ end
+
+ def test_aset_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ #{str_for_yielding_exception_class("ENV['test'] = nil")}
+ ENV["test"] = nil
+ 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 = port.receive
+ assert_equal(NilClass, exception_class)
+ 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;")
+ Ractor.new port = Ractor::Port.new do |port|
+ a = ENV.keys
+ port.send a
+ end
+ a = port.receive
+ assert_kind_of(Array, a)
+ a.each {|k| assert_kind_of(String, k) }
+ end;
+
+ end
+
+ def test_each_key_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.each_key {|k| port.send(k)}
+ port.send "finished"
+ end
+ while((x=port.receive) != "finished")
+ assert_kind_of(String, x)
+ end
+ end;
+ end
+
+ def test_values_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ a = ENV.values
+ port.send a
+ end
+ a = port.receive
+ assert_kind_of(Array, a)
+ a.each {|k| assert_kind_of(String, k) }
+ end;
+ end
+
+ def test_each_value_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.each_value {|k| port.send(k)}
+ port.send "finished"
+ end
+ while((x=port.receive) != "finished")
+ assert_kind_of(String, x)
+ end
+ end;
+ end
+
+ def test_each_pair_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.each_pair {|k, v| port.send([k,v])}
+ port.send "finished"
+ end
+ while((k,v=port.receive) != "finished")
+ assert_kind_of(String, k)
+ assert_kind_of(String, v)
+ end
+ end;
+ end
+
+ def test_reject_bang_in_ractor
+ assert_ractor(<<-"end;")
+ 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 }
+ port.send [h1, h2]
+ port.send(ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" })
+ end
+ h1, h2 = port.receive
+ assert_equal(h1, h2)
+ assert_nil(port.receive)
+ end;
+ end
+
+ def test_delete_if_in_ractor
+ assert_ractor(<<-"end;")
+ 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 }
+ port.send [h1, h2]
+ port.send (ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" })
+ end
+ h1, h2 = port.receive
+ assert_equal(h1, h2)
+ assert_same(ENV, port.receive)
+ end;
+ end
+
+ def test_select_bang_in_ractor
+ assert_ractor(<<-"end;")
+ 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 }
+ port.send [h1, h2]
+ port.send(ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
+ end
+ h1, h2 = port.receive
+ assert_equal(h1, h2)
+ assert_nil(port.receive)
+ end;
+ end
+
+ def test_filter_bang_in_ractor
+ assert_ractor(<<-"end;")
+ 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 }
+ port.send [h1, h2]
+ port.send(ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
+ end
+ h1, h2 = port.receive
+ assert_equal(h1, h2)
+ assert_nil(port.receive)
+ end;
+ end
+
+ def test_keep_if_in_ractor
+ assert_ractor(<<-"end;")
+ 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 }
+ port.send [h1, h2]
+ port.send (ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
+ end
+ h1, h2 = port.receive
+ assert_equal(h1, h2)
+ assert_equal(ENV, port.receive)
+ end;
+ end
+
+ def test_values_at_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV["test"] = "foo"
+ port.send ENV.values_at("test", "test")
+ end
+ assert_equal(["foo", "foo"], port.receive)
+ end;
+ end
+
+ def test_select_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV["test"] = "foo"
+ h = ENV.select {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
+ port.send h.size
+ k = h.keys.first
+ v = h.values.first
+ port.send [k, v]
+ end
+ assert_equal(1, port.receive)
+ k, v = port.receive
+ if #{ignore_case_str}
+ assert_equal("TEST", k.upcase)
+ assert_equal("FOO", v.upcase)
+ else
+ assert_equal("test", k)
+ assert_equal("foo", v)
+ end
+ end;
+ end
+
+ def test_filter_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV["test"] = "foo"
+ h = ENV.filter {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
+ port.send(h.size)
+ k = h.keys.first
+ v = h.values.first
+ port.send [k, v]
+ end
+ assert_equal(1, port.receive)
+ k, v = port.receive
+ if #{ignore_case_str}
+ assert_equal("TEST", k.upcase)
+ assert_equal("FOO", v.upcase)
+ else
+ assert_equal("test", k)
+ assert_equal("foo", v)
+ end
+ end;
+ end
+
+ def test_slice_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ ENV["bar"] = "rab"
+ 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;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ ENV["bar"] = "rab"
+ 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;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.clear
+ port.send ENV.size
+ end
+ assert_equal(0, port.receive)
+ end;
+ end
+
+ def test_to_s_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.to_s
+ end
+ assert_equal("ENV", r.value)
+ end;
+ end
+
+ def test_inspect_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ s = ENV.inspect
+ port.send s
+ end
+ s = port.receive
+ expected = ['"foo" => "bar"', '"baz" => "qux"']
+ unless s.start_with?(/\{"foo"/i)
+ expected.reverse!
+ end
+ expected = "{" + expected.join(', ') + "}"
+ if #{ignore_case_str}
+ s = s.upcase
+ expected = expected.upcase
+ end
+ assert_equal(expected, s)
+ end;
+ end
+
+ def test_to_a_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ a = ENV.to_a
+ port.send a
+ end
+ a = port.receive
+ assert_equal(2, a.size)
+ expected = [%w(baz qux), %w(foo bar)]
+ if #{ignore_case_str}
+ a = a.map {|x, y| [x.upcase, y]}
+ expected.map! {|x, y| [x.upcase, y]}
+ end
+ a.sort!
+ assert_equal(expected, a)
+ end;
+ end
+
+ def test_rehash_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.rehash
+ end
+ assert_nil(r.value)
+ end;
+ end
+
+ def test_size_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ s = ENV.size
+ ENV["test"] = "foo"
+ port.send [s, ENV.size]
+ end
+ s, s2 = port.receive
+ assert_equal(s + 1, s2)
+ end;
+ end
+
+ def test_empty_p_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.clear
+ port.send ENV.empty?
+ ENV["test"] = "foo"
+ port.send ENV.empty?
+ end
+ assert port.receive
+ assert !port.receive
+ end;
+ end
+
+ def test_has_key_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ port.send ENV.has_key?("test")
+ ENV["test"] = "foo"
+ port.send ENV.has_key?("test")
+ #{str_to_yield_invalid_envvar_errors("v", "ENV.has_key?(v)")}
+ end
+ assert !port.receive
+ assert port.receive
+ #{str_to_receive_invalid_envvar_errors("port")}
+ end;
+ end
+
+ def test_assoc_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ port.send ENV.assoc("test")
+ ENV["test"] = "foo"
+ port.send ENV.assoc("test")
+ #{str_to_yield_invalid_envvar_errors("v", "ENV.assoc(v)")}
+ end
+ assert_nil(port.receive)
+ k, v = port.receive
+ if #{ignore_case_str}
+ assert_equal("TEST", k.upcase)
+ assert_equal("FOO", v.upcase)
+ else
+ assert_equal("test", k)
+ assert_equal("foo", v)
+ end
+ #{str_to_receive_invalid_envvar_errors("port")}
+ encoding = /mswin|mingw/ =~ RUBY_PLATFORM ? Encoding::UTF_8 : Encoding.find("locale")
+ assert_equal(encoding, v.encoding)
+ end;
+ end
+
+ def test_has_value2_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.clear
+ port.send ENV.has_value?("foo")
+ ENV["test"] = "foo"
+ port.send ENV.has_value?("foo")
+ end
+ assert !port.receive
+ assert port.receive
+ end;
+ end
+
+ def test_rassoc_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.clear
+ port.send ENV.rassoc("foo")
+ ENV["foo"] = "bar"
+ ENV["test"] = "foo"
+ ENV["baz"] = "qux"
+ port.send ENV.rassoc("foo")
+ end
+ assert_nil(port.receive)
+ k, v = port.receive
+ if #{ignore_case_str}
+ assert_equal("TEST", k.upcase)
+ assert_equal("FOO", v.upcase)
+ else
+ assert_equal("test", k)
+ assert_equal("foo", v)
+ end
+ end;
+ end
+
+ def test_to_hash_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ h = {}
+ ENV.each {|k, v| h[k] = v }
+ port.send [h, ENV.to_hash]
+ end
+ h, h2 = port.receive
+ assert_equal(h, h2)
+ end;
+ end
+
+ def test_to_h_in_ractor
+ assert_ractor(<<-"end;")
+ 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 = port.receive
+ assert_equal(a,b)
+ c, d = port.receive
+ assert_equal(c,d)
+ end;
+ end
+
+ def test_reject_in_ractor
+ assert_ractor(<<-"end;")
+ 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" }
+ port.send [h1, h2]
+ end
+ h1, h2 = port.receive
+ assert_equal(h1, h2)
+ end;
+ end
+
+ def test_shift_in_ractor
+ assert_ractor(<<-"end;")
+ #{STR_DEFINITION_FOR_CHECK}
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ a = ENV.shift
+ b = ENV.shift
+ port.send [a,b]
+ port.send ENV.shift
+ end
+ a,b = port.receive
+ check([a, b], [%w(foo bar), %w(baz qux)])
+ assert_nil(port.receive)
+ end;
+ end
+
+ def test_invert_in_ractor
+ assert_ractor(<<-"end;")
+ #{STR_DEFINITION_FOR_CHECK}
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ port.send(ENV.invert)
+ end
+ 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}
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV["foo"] = "xxx"
+ ENV.replace({"foo"=>"bar", "baz"=>"qux"})
+ port.send ENV.to_hash
+ ENV.replace({"Foo"=>"Bar", "Baz"=>"Qux"})
+ port.send ENV.to_hash
+ end
+ 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}
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ ENV.update({"baz"=>"quux","a"=>"b"})
+ 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 }
+ port.send ENV.to_hash
+ end
+ 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
+ Ractor.new port = Ractor::Port.new, huge_value do |port, v|
+ ENV["foo"] = "bar"
+ #{str_for_yielding_exception_class("ENV['foo'] = v ")}
+ port.send ENV["foo"]
+ end
+
+ if /mswin|ucrt/ =~ RUBY_PLATFORM
+ #{str_for_assert_raise_on_yielded_exception_class(Errno::EINVAL, "port")}
+ result = port.receive
+ assert_equal("bar", result)
+ else
+ exception_class = port.receive
+ assert_equal(NilClass, exception_class)
+ result = port.receive
+ assert_equal(huge_value, result)
+ end
+ end;
+ end
+
+ def test_frozen_env_in_ractor
+ assert_ractor(<<-"end;")
+ 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, "port")}
+ end;
+ end
+
+ def test_frozen_in_ractor
+ assert_ractor(<<-"end;")
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV["#{PATH_ENV}"] = "/"
+ ENV.each do |k, v|
+ port.send [k]
+ port.send [v]
+ end
+ ENV.each_key do |k|
+ port.send [k]
+ end
+ ENV.each_value do |v|
+ port.send [v]
+ end
+ ENV.each_key do |k|
+ port.send [ENV[k], "[\#{k.dump}]"]
+ port.send [ENV.fetch(k), "fetch(\#{k.dump})"]
+ end
+ port.send "finished"
+ end
+ 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;")
+ Ractor.new port = Ractor::Port.new do |port|
+ bug12475 = '[ruby-dev:49655] [Bug #12475]'
+ n = [*"0".."9"].join("")*3
+ e0 = ENV[n0 = "E\#{n}"]
+ e1 = ENV[n1 = "E\#{n}."]
+ ENV[n0] = nil
+ ENV[n1] = nil
+ ENV[n1.chop] = "T\#{n}.".chop
+ ENV[n0], e0 = e0, ENV[n0]
+ ENV[n1], e1 = e1, ENV[n1]
+ port.send [n, e0, e1, bug12475]
+ end
+ n, e0, e1, bug12475 = port.receive
+ assert_equal("T\#{n}", e0, bug12475)
+ assert_nil(e1, bug12475)
+ end;
+ end
+
+ def test_ivar_in_env_should_not_be_access_from_non_main_ractors
+ assert_ractor <<~RUBY
+ ENV.instance_eval{ @a = "hello" }
+ assert_equal "hello", ENV.instance_variable_get(:@a)
+
+ r_get = Ractor.new do
+ ENV.instance_variable_get(:@a)
+ rescue Ractor::IsolationError => e
+ e
+ end
+ assert_equal Ractor::IsolationError, r_get.value.class
+
+ r_get = Ractor.new do
+ ENV.instance_eval{ @a }
+ rescue Ractor::IsolationError => e
+ e
+ end
+
+ assert_equal Ractor::IsolationError, r_get.value.class
+
+ r_set = Ractor.new do
+ ENV.instance_eval{ @b = "hello" }
+ rescue Ractor::IsolationError => e
+ e
+ end
+
+ assert_equal Ractor::IsolationError, r_set.value.class
+ RUBY
+ end
+
if RUBY_PLATFORM =~ /bccwin|mswin|mingw/
def test_memory_leak_aset
bug9977 = '[ruby-dev:48323] [Bug #9977]'
@@ -519,15 +1492,20 @@ class TestEnv < Test::Unit::TestCase
end;
end
- if Encoding.find("locale") == Encoding::UTF_8
- def test_utf8
- text = "testing \u{e5 e1 e2 e4 e3 101 3042}"
- test = ENV["test"]
- ENV["test"] = text
- assert_equal text, ENV["test"]
- ensure
- ENV["test"] = test
- end
+ def test_utf8
+ text = "testing \u{e5 e1 e2 e4 e3 101 3042}"
+ ENV["test"] = text
+ assert_equal text, ENV["test"]
+ end
+
+ def test_utf8_empty
+ key = "VAR\u{e5 e1 e2 e4 e3 101 3042}"
+ ENV[key] = "x"
+ assert_equal "x", ENV[key]
+ ENV[key] = ""
+ assert_equal "", ENV[key]
+ ENV[key] = nil
+ assert_nil ENV[key]
end
end
end
diff --git a/test/ruby/test_eval.rb b/test/ruby/test_eval.rb
index 0bc0390d64..d2145bec5d 100644
--- a/test/ruby/test_eval.rb
+++ b/test/ruby/test_eval.rb
@@ -219,6 +219,12 @@ class TestEval < Test::Unit::TestCase
end
end
+ def test_instance_exec_cvar
+ [Object.new, [], 7, :sym, true, false, nil].each do |obj|
+ assert_equal(13, obj.instance_exec{@@cvar})
+ end
+ end
+
def test_instance_eval_method
bug2788 = '[ruby-core:28324]'
[Object.new, [], nil, true, false].each do |o|
@@ -253,6 +259,70 @@ class TestEval < Test::Unit::TestCase
assert_equal(2, bar)
end
+ def test_instance_exec_block_basic
+ forall_TYPE do |o|
+ assert_equal nil, o.instance_exec { nil }
+ assert_equal true, o.instance_exec { true }
+ assert_equal false, o.instance_exec { false }
+ assert_equal o, o.instance_exec { self }
+ assert_equal 1, o.instance_exec { 1 }
+ assert_equal :sym, o.instance_exec { :sym }
+
+ assert_equal 11, o.instance_exec { 11 }
+ assert_equal 12, o.instance_exec { @ivar } unless o.frozen?
+ assert_equal 13, o.instance_exec { @@cvar }
+ assert_equal 14, o.instance_exec { $gvar__eval }
+ assert_equal 15, o.instance_exec { Const }
+ assert_equal 16, o.instance_exec { 7 + 9 }
+ assert_equal 17, o.instance_exec { 17.to_i }
+ assert_equal "18", o.instance_exec { "18" }
+ assert_equal "19", o.instance_exec { "1#{9}" }
+
+ 1.times {
+ assert_equal 12, o.instance_exec { @ivar } unless o.frozen?
+ assert_equal 13, o.instance_exec { @@cvar }
+ assert_equal 14, o.instance_exec { $gvar__eval }
+ assert_equal 15, o.instance_exec { Const }
+ }
+ end
+ end
+
+ def test_instance_exec_method_definition
+ klass = Class.new
+ o = klass.new
+
+ o.instance_exec do
+ def foo
+ :foo_result
+ end
+ end
+
+ assert_respond_to o, :foo
+ refute_respond_to klass, :foo
+ refute_respond_to klass.new, :foo
+
+ assert_equal :foo_result, o.foo
+ end
+
+ def test_instance_exec_eval_method_definition
+ klass = Class.new
+ o = klass.new
+
+ o.instance_exec do
+ eval %{
+ def foo
+ :foo_result
+ end
+ }
+ end
+
+ assert_respond_to o, :foo
+ refute_respond_to klass, :foo
+ refute_respond_to klass.new, :foo
+
+ assert_equal :foo_result, o.foo
+ end
+
#
# From ruby/test/ruby/test_eval.rb
#
@@ -320,20 +390,6 @@ class TestEval < Test::Unit::TestCase
end
assert(!bad)
- if false
- # Ruby 2.0 doesn't see Proc as Binding
- x = proc{}
- eval "i4 = 1", x
- assert_equal(1, eval("i4", x))
- x = proc{proc{}}.call
- eval "i4 = 22", x
- assert_equal(22, eval("i4", x))
- t = []
- x = proc{proc{}}.call
- eval "(0..9).each{|i5| t[i5] = proc{i5*2}}", x
- assert_equal(8, t[4].call)
- end
-
x = binding
eval "i = 1", x
assert_equal(1, eval("i", x))
@@ -359,27 +415,11 @@ class TestEval < Test::Unit::TestCase
# assert_equal(1, eval("foo11"))
assert_equal(eval("foo22"), eval("foo22", p))
assert_equal(55, eval("foo22"))
+ assert_equal(55, foo22)
}.call
- if false
- # Ruby 2.0 doesn't see Proc as Binding
- p1 = proc{i7 = 0; proc{i7}}.call
- assert_equal(0, p1.call)
- eval "i7=5", p1
- assert_equal(5, p1.call)
- assert(!defined?(i7))
- end
-
- if false
- # Ruby 2.0 doesn't see Proc as Binding
- p1 = proc{i7 = 0; proc{i7}}.call
- i7 = nil
- assert_equal(0, p1.call)
- eval "i7=1", p1
- assert_equal(1, p1.call)
- eval "i7=5", p1
- assert_equal(5, p1.call)
- assert_nil(i7)
+ self.class.class_eval do
+ remove_const :EvTest
end
end
@@ -448,6 +488,9 @@ class TestEval < Test::Unit::TestCase
end
end
assert_equal(feature6609, feature6609_method)
+ ensure
+ Object.undef_method(:feature6609_block) rescue nil
+ Object.undef_method(:feature6609_method) rescue nil
end
def test_eval_using_integer_as_binding
@@ -492,6 +535,12 @@ class TestEval < Test::Unit::TestCase
assert_equal(fname, eval("__FILE__", nil, fname, 1))
end
+ def test_eval_invalid_block_exit_bug_20597
+ assert_raise(SyntaxError){eval("break if false")}
+ assert_raise(SyntaxError){eval("next if false")}
+ assert_raise(SyntaxError){eval("redo if false")}
+ end
+
def test_eval_location_fstring
o = Object.new
o.instance_eval "def foo() end", "generated code"
@@ -503,6 +552,15 @@ class TestEval < Test::Unit::TestCase
assert_same a, b
end
+ def test_eval_location_binding
+ assert_equal(["(eval at #{__FILE__}:#{__LINE__})", 1], eval("[__FILE__, __LINE__]", nil))
+ assert_equal(["(eval at #{__FILE__}:#{__LINE__})", 1], eval("[__FILE__, __LINE__]", binding))
+ assert_equal(['foo', 1], eval("[__FILE__, __LINE__]", nil, 'foo'))
+ assert_equal(['foo', 1], eval("[__FILE__, __LINE__]", binding, 'foo'))
+ assert_equal(['foo', 2], eval("[__FILE__, __LINE__]", nil, 'foo', 2))
+ assert_equal(['foo', 2], eval("[__FILE__, __LINE__]", binding, 'foo', 2))
+ end
+
def test_fstring_instance_eval
bug = "[ruby-core:78116] [Bug #12930]".freeze
assert_same bug, (bug.instance_eval {self})
@@ -526,6 +584,17 @@ class TestEval < Test::Unit::TestCase
}, '[Bug #10368]'
end
+ def test_gced_eval_location
+ Dir.mktmpdir do |d|
+ File.write("#{d}/2.rb", "")
+ File.write("#{d}/1.rb", "require_relative '2'\n""__FILE__\n")
+ file = "1.rb"
+ path = File.expand_path(file, d)
+ assert_equal(path, eval(File.read(path), nil, File.expand_path(file, d)))
+ assert_equal(path, eval(File.read(path), nil, File.expand_path(file, d)))
+ end
+ end
+
def orphan_proc
proc {eval("return :ng")}
end
@@ -543,4 +612,44 @@ class TestEval < Test::Unit::TestCase
x = orphan_lambda
assert_equal(:ok, x.call)
end
+
+ def test_syntax_error_no_memory_leak
+ assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", rss: true)
+ begin;
+ 100_000.times do
+ eval("/[/=~s")
+ rescue SyntaxError
+ else
+ raise "Expected SyntaxError to be raised"
+ end
+ end;
+
+ assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", rss: true)
+ begin;
+ a = 1
+
+ 100_000.times do
+ eval("if a in [0, 0] | [0, a]; end")
+ rescue SyntaxError
+ else
+ raise "Expected SyntaxError to be raised"
+ end
+ end;
+ end
+
+ def test_outer_local_variable_under_gc_compact_stress
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+ omit "compaction is not supported on s390x" if /s390x/ =~ RUBY_PLATFORM
+
+ assert_separately([], <<~RUBY)
+ o = Object.new
+ def o.m = 1
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ EnvUtil.under_gc_compact_stress do
+ assert_equal(1, eval("o.m"))
+ end
+ RUBY
+ end
end
diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb
index 605248aebd..31e5aa9f6b 100644
--- a/test/ruby/test_exception.rb
+++ b/test/ruby/test_exception.rb
@@ -78,6 +78,77 @@ class TestException < Test::Unit::TestCase
assert(!bad)
end
+ def test_exception_in_ensure_with_next
+ string = "[ruby-core:82936] [Bug #13930]"
+ assert_raise_with_message(RuntimeError, string) do
+ lambda do
+ next
+ rescue
+ assert(false)
+ ensure
+ raise string
+ end.call
+ assert(false)
+ end
+
+ assert_raise_with_message(RuntimeError, string) do
+ flag = true
+ while flag
+ flag = false
+ begin
+ next
+ rescue
+ assert(false)
+ ensure
+ raise string
+ end
+ end
+ end
+
+ iseq = RubyVM::InstructionSequence.compile(<<-RUBY)
+ begin
+ while true
+ break
+ end
+ rescue
+ end
+ RUBY
+
+ assert_equal false, iseq.to_a[13].any?{|(e,_)| e == :throw}
+ end
+
+ def test_exception_in_ensure_with_redo
+ string = "[ruby-core:82936] [Bug #13930]"
+
+ assert_raise_with_message(RuntimeError, string) do
+ i = 0
+ lambda do
+ i += 1
+ redo if i < 2
+ rescue
+ assert(false)
+ ensure
+ raise string
+ end.call
+ assert(false)
+ end
+ end
+
+ def test_exception_in_ensure_with_return
+ @string = "[ruby-core:97104] [Bug #16618]"
+ def self.meow
+ return if true # This if modifier suppresses "warning: statement not reached"
+ assert(false)
+ rescue
+ assert(false)
+ ensure
+ raise @string
+ end
+ assert_raise_with_message(RuntimeError, @string) do
+ meow
+ end
+ end
+
def test_errinfo_in_debug
bug9568 = EnvUtil.labeled_class("[ruby-core:61091] [Bug #9568]", RuntimeError) do
def to_s
@@ -181,6 +252,27 @@ class TestException < Test::Unit::TestCase
}
end
+ def test_catch_throw_in_require_cant_be_rescued
+ bug18562 = '[ruby-core:107403]'
+ Tempfile.create(["dep", ".rb"]) {|t|
+ t.puts("throw :extdep, 42")
+ t.close
+
+ rescue_all = Class.new(Exception)
+ def rescue_all.===(_)
+ raise "should not reach here"
+ end
+
+ v = assert_throw(:extdep, bug18562) do
+ require t.path
+ rescue rescue_all
+ assert(false, "should not reach here")
+ end
+
+ assert_equal(42, v, bug18562)
+ }
+ end
+
def test_throw_false
bug12743 = '[ruby-core:77229] [Bug #12743]'
Thread.start {
@@ -324,7 +416,7 @@ class TestException < Test::Unit::TestCase
assert_in_out_err([], "$@ = 1", [], /\$! not set \(ArgumentError\)$/)
- assert_in_out_err([], <<-INPUT, [], /backtrace must be Array of String \(TypeError\)$/)
+ assert_in_out_err([], <<-INPUT, [], /backtrace must be an Array of String or an Array of Thread::Backtrace::Location \(TypeError\)$/)
begin
raise
rescue
@@ -352,6 +444,7 @@ class TestException < Test::Unit::TestCase
end
def test_thread_signal_location
+ # pend('TODO: a known bug [Bug #14474]')
_, stderr, _ = EnvUtil.invoke_ruby(%w"--disable-gems -d", <<-RUBY, false, true)
Thread.start do
Thread.current.report_on_exception = false
@@ -385,6 +478,12 @@ end.join
def to_s; ""; end
end
assert_equal(e.inspect, e.new.inspect)
+
+ # https://bugs.ruby-lang.org/issues/18170#note-13
+ assert_equal('#<Exception:"foo\nbar">', Exception.new("foo\nbar").inspect)
+ assert_equal('#<Exception: foo bar>', Exception.new("foo bar").inspect)
+ assert_equal('#<Exception: foo\bar>', Exception.new("foo\\bar").inspect)
+ assert_equal('#<Exception: "foo\nbar">', Exception.new('"foo\nbar"').inspect)
end
def test_to_s
@@ -409,6 +508,16 @@ end.join
assert_raise(TypeError) { e.set_backtrace(1) }
assert_raise(TypeError) { e.set_backtrace([1]) }
+
+ error = assert_raise(TypeError) do
+ e.set_backtrace(caller_locations(1, 1) + ["foo"])
+ end
+ assert_include error.message, "backtrace must be an Array of String or an Array of Thread::Backtrace::Location"
+
+ error = assert_raise(TypeError) do
+ e.set_backtrace(["foo"] + caller_locations(1, 1))
+ end
+ assert_include error.message, "backtrace must be an Array of String or an Array of Thread::Backtrace::Location"
end
def test_exit_success_p
@@ -437,20 +546,18 @@ end.join
assert_not_send([e, :success?], "abort means failure")
end
- def test_nomethoderror
- bug3237 = '[ruby-core:29948]'
- str = "\u2600"
- id = :"\u2604"
- msg = "undefined method `#{id}' for \"#{str}\":String"
- assert_raise_with_message(NoMethodError, msg, bug3237) do
- str.__send__(id)
- end
- end
-
def test_errno
assert_equal(Encoding.find("locale"), Errno::EINVAL.new.message.encoding)
end
+ def test_errno_constants
+ assert_equal [:NOERROR], Errno.constants.grep_v(/\AE/)
+ all_assertions_foreach("should be a subclass of SystemCallError", *Errno.constants) do |c|
+ e = Errno.const_get(c)
+ assert_operator e, :<, SystemCallError, proc {e.ancestors.inspect}
+ end
+ end
+
def test_too_many_args_in_eval
bug5720 = '[ruby-core:41520]'
arg_string = (0...140000).to_a.join(", ")
@@ -487,20 +594,44 @@ end.join
end
def test_exception_in_name_error_to_str
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
bug5575 = '[ruby-core:41612]'
- Tempfile.create(["test_exception_in_name_error_to_str", ".rb"]) do |t|
- t.puts <<-EOC
+ begin;
begin
BasicObject.new.inspect
rescue
- $!.inspect
- end
- EOC
- t.close
- assert_nothing_raised(NameError, bug5575) do
- load(t.path)
+ assert_nothing_raised(NameError, bug5575) {$!.inspect}
end
+ end;
+ end
+
+ def test_ensure_after_nomemoryerror
+ omit "Forcing NoMemoryError causes problems in some environments"
+ assert_separately([], "$_ = 'a' * 1_000_000_000_000_000_000")
+ rescue NoMemoryError
+ assert_raise(NoMemoryError) do
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ bug15779 = bug15779 = '[ruby-core:92342]'
+ begin;
+ require 'open-uri'
+
+ begin
+ 'a' * 1_000_000_000_000_000_000
+ ensure
+ URI.open('http://www.ruby-lang.org/')
+ end
+ end;
end
+ rescue Test::Unit::AssertionFailedError
+ # Possibly compiled with -DRUBY_DEBUG, in which
+ # case rb_bug is used instead of NoMemoryError,
+ # and we cannot test ensure after NoMemoryError.
+ rescue RangeError
+ # MingW can raise RangeError instead of NoMemoryError,
+ # so we cannot test this case.
+ rescue Timeout::Error
+ # Solaris 11 CI times out instead of raising NoMemoryError,
+ # so we cannot test this case.
end
def test_equal
@@ -510,19 +641,28 @@ end.join
end
def test_exception_in_exception_equal
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
bug5865 = '[ruby-core:41979]'
- Tempfile.create(["test_exception_in_exception_equal", ".rb"]) do |t|
- t.puts <<-EOC
+ begin;
o = Object.new
def o.exception(arg)
end
- _ = RuntimeError.new("a") == o
- EOC
- t.close
assert_nothing_raised(ArgumentError, bug5865) do
- load(t.path)
+ RuntimeError.new("a") == o
end
+ end;
+ end
+
+ def test_backtrace_by_exception
+ begin
+ line = __LINE__; raise "foo"
+ rescue => e
end
+ e2 = e.exception("bar")
+ assert_not_equal(e.message, e2.message)
+ assert_equal(e.backtrace, e2.backtrace)
+ loc = e2.backtrace_locations[0]
+ assert_equal([__FILE__, line], [loc.path, loc.lineno])
end
Bug4438 = '[ruby-core:35364]'
@@ -545,28 +685,6 @@ end.join
end
end
- def test_to_s_taintness_propagation
- for exc in [Exception, NameError]
- m = "abcdefg"
- e = exc.new(m)
- e.taint
- s = e.to_s
- assert_equal(false, m.tainted?,
- "#{exc}#to_s should not propagate taintness")
- assert_equal(false, s.tainted?,
- "#{exc}#to_s should not propagate taintness")
- end
-
- o = Object.new
- def o.to_str
- "foo"
- end
- o.taint
- e = NameError.new(o)
- s = e.to_s
- assert_equal(false, s.tainted?)
- end
-
def m
m(&->{return 0})
42
@@ -585,7 +703,7 @@ end.join
def test_machine_stackoverflow
bug9109 = '[ruby-dev:47804] [Bug #9109]'
- assert_separately(%w[--disable-gem], <<-SRC)
+ assert_separately([], <<-SRC)
assert_raise(SystemStackError, #{bug9109.dump}) {
h = {a: ->{h[:a].call}}
h[:a].call
@@ -596,7 +714,7 @@ end.join
def test_machine_stackoverflow_by_define_method
bug9454 = '[ruby-core:60113] [Bug #9454]'
- assert_separately(%w[--disable-gem], <<-SRC)
+ assert_separately([], <<-SRC)
assert_raise(SystemStackError, #{bug9454.dump}) {
define_method(:foo) {self.foo}
self.foo
@@ -699,7 +817,11 @@ end.join
end
def test_cause_at_end
- assert_in_out_err([], <<-'end;', [], [/-: unexpected return\n/, /.*undefined local variable or method `n'.*\n/])
+ errs = [
+ /-: unexpected return\n/,
+ /.*undefined local variable or method 'n'.*\n/,
+ ]
+ assert_in_out_err([], <<-'end;', [], errs)
END{n}; END{return}
end;
end
@@ -709,6 +831,7 @@ end.join
cause = ArgumentError.new("foobar")
e = assert_raise(RuntimeError) {raise msg, cause: cause}
assert_same(cause, e.cause)
+ assert_raise(TypeError) {raise msg, {cause: cause}}
end
def test_cause_with_no_arguments
@@ -766,7 +889,7 @@ end.join
bug12741 = '[ruby-core:77222] [Bug #12741]'
x = Thread.current
- q = Queue.new
+ q = Thread::Queue.new
y = Thread.start do
q.pop
begin
@@ -803,7 +926,7 @@ end.join
e = assert_raise(exc, bug) {raise exc, "foo" => "bar", foo: "bar"}
assert_equal({"foo" => "bar", foo: "bar"}, e.arg, bug)
- e = assert_raise(exc, bug) {raise exc, "foo" => "bar", foo: "bar", cause: "zzz"}
+ e = assert_raise(exc, bug) {raise exc, "foo" => "bar", foo: "bar", cause: RuntimeError.new("zzz")}
assert_equal({"foo" => "bar", foo: "bar"}, e.arg, bug)
e = assert_raise(exc, bug) {raise exc, {}}
@@ -837,101 +960,28 @@ end.join
}
end
- def test_anonymous_message
- assert_in_out_err([], "raise Class.new(RuntimeError), 'foo'", [], /foo\n/)
- end
-
- PrettyObject =
- Class.new(BasicObject) do
- alias object_id __id__
- def pretty_inspect; "`obj'"; end
- alias inspect pretty_inspect
- end
-
- def test_name_error_info_const
- obj = PrettyObject.new
-
- e = assert_raise(NameError) {
- obj.instance_eval("Object")
- }
- assert_equal(:Object, e.name)
-
- e = assert_raise(NameError) {
- BasicObject::X
- }
- assert_same(BasicObject, e.receiver)
- assert_equal(:X, e.name)
- end
-
- def test_name_error_info_method
- obj = PrettyObject.new
-
- e = assert_raise(NameError) {
- obj.instance_eval {foo}
- }
- assert_equal(:foo, e.name)
- assert_same(obj, e.receiver)
-
- e = assert_raise(NoMethodError) {
- obj.foo(1, 2)
- }
- assert_equal(:foo, e.name)
- assert_equal([1, 2], e.args)
- assert_same(obj, e.receiver)
- assert_not_predicate(e, :private_call?)
-
- e = assert_raise(NoMethodError) {
- obj.instance_eval {foo(1, 2)}
- }
- assert_equal(:foo, e.name)
- assert_equal([1, 2], e.args)
- assert_same(obj, e.receiver)
- assert_predicate(e, :private_call?)
- end
-
- def test_name_error_info_local_variables
- obj = PrettyObject.new
- def obj.test(a, b=nil, *c, &d)
- e = a
- 1.times {|f| g = foo; g}
- e
- end
-
- e = assert_raise(NameError) {
- obj.test(3)
- }
- assert_equal(:foo, e.name)
- assert_same(obj, e.receiver)
- assert_equal(%i[a b c d e f g], e.local_variables.sort)
- end
-
- def test_name_error_info_method_missing
- obj = PrettyObject.new
- def obj.method_missing(*)
- super
+ def test_cause_exception_in_cause_message
+ assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}") do |outs, errs, status|
+ begin;
+ exc = Class.new(StandardError) do
+ def initialize(obj, cnt)
+ super(obj)
+ @errcnt = cnt
+ end
+ def to_s
+ return super if @errcnt <= 0
+ @errcnt -= 1
+ raise "xxx"
+ end
+ end.new("ok", 10)
+ raise "[Bug #17033]", cause: exc
+ end;
+ assert_equal(1, errs.count {|m| m.include?("[Bug #17033]")}, proc {errs.pretty_inspect})
end
-
- e = assert_raise(NoMethodError) {
- obj.foo(1, 2)
- }
- assert_equal(:foo, e.name)
- assert_equal([1, 2], e.args)
- assert_same(obj, e.receiver)
- assert_not_predicate(e, :private_call?)
-
- e = assert_raise(NoMethodError) {
- obj.instance_eval {foo(1, 2)}
- }
- assert_equal(:foo, e.name)
- assert_equal([1, 2], e.args)
- assert_same(obj, e.receiver)
- assert_predicate(e, :private_call?)
end
- def test_name_error_info_parent_iseq_mark
- assert_separately(['-', File.join(__dir__, 'bug-11928.rb')], <<-'end;')
- -> {require ARGV[0]}.call
- end;
+ def test_anonymous_message
+ assert_in_out_err([], "raise Class.new(RuntimeError), 'foo'", [], /foo\n/)
end
def test_output_string_encoding
@@ -942,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
@@ -959,6 +1009,44 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end;
end
+ def assert_null_char(src, *args, **opts)
+ begin
+ eval(src)
+ rescue => e
+ end
+ assert_not_nil(e)
+ assert_include(e.message, "\0")
+ # Disabled by [Feature #18367]
+ #assert_in_out_err([], src, [], [], *args, **opts) do |_, err,|
+ # err.each do |e|
+ # assert_not_include(e, "\0")
+ # end
+ #end
+ e
+ end
+
+ def test_control_in_message
+ bug7574 = '[ruby-dev:46749]'
+ assert_null_char("#{<<~"begin;"}\n#{<<~'end;'}", bug7574)
+ begin;
+ Object.const_defined?("String\0")
+ end;
+ assert_null_char("#{<<~"begin;"}\n#{<<~'end;'}", bug7574)
+ begin;
+ Object.const_get("String\0")
+ end;
+ end
+
+ def test_encoding_in_message
+ name = "\u{e9}t\u{e9}"
+ e = EnvUtil.with_default_external("US-ASCII") do
+ assert_raise(NameError) do
+ Object.const_get(name)
+ end
+ end
+ assert_include(e.message, name)
+ end
+
def test_method_missing_reason_clear
bug10969 = '[ruby-core:68515] [Bug #10969]'
a = Class.new {def method_missing(*) super end}.new
@@ -967,32 +1055,41 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
def test_message_of_name_error
- assert_raise_with_message(NameError, /\Aundefined method `foo' for module `#<Module:.*>'$/) do
+ assert_raise_with_message(NameError, /\Aundefined method 'foo' for module '#<Module:.*>'$/) do
Module.new do
module_function :foo
end
end
end
- def capture_warning_warn
+ def capture_warning_warn(category: false)
verbose = $VERBOSE
+ categories = Warning.categories.to_h {|cat| [cat, Warning[cat]]}
warning = []
::Warning.class_eval do
alias_method :warn2, :warn
remove_method :warn
- define_method(:warn) do |str|
- warning << str
+ if category
+ define_method(:warn) do |str, category: nil|
+ warning << [str, category]
+ end
+ else
+ define_method(:warn) do |str, category: nil|
+ warning << str
+ end
end
end
$VERBOSE = true
+ Warning.categories.each {|cat| Warning[cat] = true}
yield
return warning
ensure
$VERBOSE = verbose
+ categories.each {|cat, flag| Warning[cat] = flag}
::Warning.class_eval do
remove_method :warn
@@ -1002,19 +1099,41 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
def test_warning_warn
- warning = capture_warning_warn {@a}
- assert_match(/instance variable @a not initialized/, warning[0])
+ warning = capture_warning_warn {$asdfasdsda_test_warning_warn}
+ assert_match(/global variable '\$asdfasdsda_test_warning_warn' not initialized/, warning[0])
assert_equal(["a\nz\n"], capture_warning_warn {warn "a\n", "z"})
assert_equal([], capture_warning_warn {warn})
assert_equal(["\n"], capture_warning_warn {warn ""})
end
+ def test_warn_deprecated_backwards_compatibility_category
+ (message, category), = capture_warning_warn(category: true) do
+ $; = "www"
+ $; = nil
+ end
+
+ assert_include message, 'deprecated'
+ assert_equal :deprecated, category
+ end
+
def test_kernel_warn_uplevel
warning = capture_warning_warn {warn("test warning", uplevel: 0)}
assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0])
+ def (obj = Object.new).w(n) warn("test warning", uplevel: n) end
+ warning = capture_warning_warn {obj.w(0)}
+ assert_equal("#{__FILE__}:#{__LINE__-2}: warning: test warning\n", warning[0])
+ warning = capture_warning_warn {obj.w(1)}
+ assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0])
assert_raise(ArgumentError) {warn("test warning", uplevel: -1)}
assert_in_out_err(["-e", "warn 'ok', uplevel: 1"], '', [], /warning:/)
+ warning = capture_warning_warn {warn("test warning", {uplevel: 0})}
+ assert_match(/test warning.*{uplevel: 0}/m, warning[0])
+ warning = capture_warning_warn {warn("test warning", **{uplevel: 0})}
+ assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0])
+ warning = capture_warning_warn {warn("test warning", {uplevel: 0}, **{})}
+ assert_equal("test warning\n{uplevel: 0}\n", warning[0])
+ assert_raise(ArgumentError) {warn("test warning", foo: 1)}
end
def test_warning_warn_invalid_argument
@@ -1038,10 +1157,12 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
t.puts "require '#{basename}'"
t.close
$LOAD_PATH.push(File.dirname(t))
- warning = capture_warning_warn {require basename}
+ warning = capture_warning_warn {
+ assert require(basename)
+ }
ensure
$LOAD_PATH.pop
- $LOADED_FEATURES.delete(t)
+ $LOADED_FEATURES.delete(t.path)
end
assert_equal(1, warning.size)
assert_match(/circular require/, warning.first)
@@ -1049,7 +1170,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
def test_warning_warn_super
- assert_in_out_err(%[-W0], "#{<<~"{#"}\n#{<<~'};'}", [], /instance variable @a not initialized/)
+ assert_in_out_err(%[-W0], "#{<<~"{#"}\n#{<<~'};'}", [], /global variable '\$asdfiasdofa_test_warning_warn_super' not initialized/)
{#
module Warning
def warn(message)
@@ -1058,10 +1179,41 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
$VERBOSE = true
- @a
+ $asdfiasdofa_test_warning_warn_super
};
end
+ def test_warning_category
+ assert_raise(TypeError) {Warning[nil]}
+ assert_raise(ArgumentError) {Warning[:XXXX]}
+
+ all_assertions_foreach("categories", *Warning.categories) do |cat|
+ value = Warning[cat]
+ assert_include([true, false], value)
+
+ enabled = EnvUtil.verbose_warning do
+ Warning[cat] = true
+ Warning.warn "#{cat} feature", category: cat
+ end
+ disabled = EnvUtil.verbose_warning do
+ Warning[cat] = false
+ Warning.warn "#{cat} feature", category: cat
+ end
+ ensure
+ Warning[cat] = value
+ assert_equal "#{cat} feature", enabled
+ assert_empty disabled
+ end
+ end
+
+ def test_undef_Warning_warn
+ assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ Warning.undef_method(:warn)
+ assert_raise(NoMethodError) { warn "" }
+ end;
+ end
+
def test_undefined_backtrace
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
@@ -1099,7 +1251,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
begin;
class Bug < RuntimeError
def backtrace
- IO.readlines(IO::NULL)
+ File.readlines(IO::NULL)
end
end
bug = Bug.new '[ruby-core:85939] [Bug #14577]'
@@ -1141,16 +1293,39 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
end
+ def test_backtrace_in_eval
+ bug = '[ruby-core:84434] [Bug #14229]'
+ assert_in_out_err(['-e', 'eval("raise")'], "", [], /^\(eval at .*\):1:/, bug)
+ end
+
def test_full_message
+ message = RuntimeError.new("testerror").full_message
+ assert_operator(message, :end_with?, "\n")
+
test_method = "def foo; raise 'testerror'; end"
out1, err1, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; rescue => e; puts e.full_message; end"], '', true, true)
- assert(status1.success?)
- assert(err1.empty?, "expected nothing wrote to $stdout by #long_message")
+ assert_predicate(status1, :success?)
+ assert_empty(err1, "expected nothing wrote to $stdout by #full_message")
_, err2, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; end"], '', true, true)
assert_equal(err2, out1)
+ e = RuntimeError.new("a\n")
+ message = assert_nothing_raised(ArgumentError, proc {e.pretty_inspect}) do
+ e.full_message
+ end
+ assert_operator(message, :end_with?, "\n")
+ message = message.gsub(/\e\[[\d;]*m/, '')
+ assert_not_operator(message, :end_with?, "\n\n")
+ e = RuntimeError.new("a\n\nb\n\nc")
+ message = assert_nothing_raised(ArgumentError, proc {e.pretty_inspect}) do
+ e.full_message
+ end
+ assert_all?(message.lines) do |m|
+ /\e\[\d[;\d]*m[^\e]*\n/ !~ m
+ end
+
e = RuntimeError.new("testerror")
message = e.full_message(highlight: false)
assert_not_match(/\e/, message)
@@ -1158,20 +1333,41 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
bt = ["test:100", "test:99", "test:98", "test:1"]
e = assert_raise(RuntimeError) {raise RuntimeError, "testerror", bt}
+ bottom = "test:100: testerror (RuntimeError)\n"
+ top = "test:1\n"
+ remark = "Traceback (most recent call last):"
+
message = e.full_message(highlight: false, order: :top)
assert_not_match(/\e/, message)
assert_operator(message.count("\n"), :>, 2)
- assert_operator(message, :start_with?, "test:100: testerror (RuntimeError)\n")
- assert_operator(message, :end_with?, "test:1\n")
+ assert_operator(message, :start_with?, bottom)
+ assert_operator(message, :end_with?, top)
message = e.full_message(highlight: false, order: :bottom)
assert_not_match(/\e/, message)
assert_operator(message.count("\n"), :>, 2)
- assert_operator(message, :start_with?, "Traceback (most recent call last):")
- assert_operator(message, :end_with?, "test:100: testerror (RuntimeError)\n")
+ assert_operator(message, :start_with?, remark)
+ assert_operator(message, :end_with?, bottom)
+
+ assert_raise_with_message(ArgumentError, /:top or :bottom/) {
+ e.full_message(highlight: false, order: :middle)
+ }
message = e.full_message(highlight: true)
assert_match(/\e/, message)
+ assert_not_match(/(\e\[1)m\1/, message)
+ e2 = assert_raise(RuntimeError) {raise RuntimeError, "", bt}
+ assert_not_match(/(\e\[1)m\1/, e2.full_message(highlight: true))
+
+ message = e.full_message
+ if Exception.to_tty?
+ assert_match(/\e/, message)
+ message = message.gsub(/\e\[[\d;]*m/, '')
+ else
+ assert_not_match(/\e/, message)
+ end
+ assert_operator(message, :start_with?, bottom)
+ assert_operator(message, :end_with?, top)
end
def test_exception_in_message
@@ -1182,6 +1378,178 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
raise Bug14566
end;
- assert_in_out_err([], code, [], /Bug14566/, success: false, timeout: 1)
+ assert_in_out_err([], code, [], /Bug14566/, success: false, timeout: 2)
+ end
+
+ def test_non_exception_cause
+ assert_raise_with_message(TypeError, /exception/) do
+ raise "foo", cause: 1
+ end;
+ end
+
+ def test_circular_cause_handle
+ assert_raise_with_message(ArgumentError, /circular cause/) do
+ begin
+ raise "error 1"
+ rescue => e1
+ raise "error 2" rescue raise e1, cause: $!
+ end
+ end;
+ end
+
+ def test_marshal_circular_cause
+ dump = "\x04\bo:\x11RuntimeError\b:\tmesgI\"\berr\x06:\x06ET:\abt[\x00:\ncause@\x05"
+ assert_raise_with_message(ArgumentError, /circular cause/, ->{dump.inspect}) do
+ e = Marshal.load(dump)
+ assert_same(e, e.cause)
+ end
+ end
+
+ def test_super_in_method_missing
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $VERBOSE = nil
+ class Object
+ def method_missing(name, *args, &block)
+ super
+ end
+ end
+
+ bug14670 = '[ruby-dev:50522] [Bug #14670]'
+ assert_raise_with_message(NoMethodError, /'foo'/, bug14670) do
+ Object.new.foo
+ end
+ end;
+ end
+
+ def test_detailed_message
+ e = RuntimeError.new("message")
+ assert_equal("message (RuntimeError)", e.detailed_message)
+ assert_equal("\e[1mmessage (\e[1;4mRuntimeError\e[m\e[1m)\e[m", e.detailed_message(highlight: true))
+
+ e = RuntimeError.new("foo\nbar\nbaz")
+ assert_equal("foo (RuntimeError)\nbar\nbaz", e.detailed_message)
+ assert_equal("\e[1mfoo (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n\e[1mbar\e[m\n\e[1mbaz\e[m", e.detailed_message(highlight: true))
+
+ e = RuntimeError.new("")
+ assert_equal("unhandled exception", e.detailed_message)
+ assert_equal("\e[1;4munhandled exception\e[m", e.detailed_message(highlight: true))
+
+ e = RuntimeError.new
+ assert_equal("RuntimeError (RuntimeError)", e.detailed_message)
+ assert_equal("\e[1mRuntimeError (\e[1;4mRuntimeError\e[m\e[1m)\e[m", e.detailed_message(highlight: true))
+ end
+
+ def test_detailed_message_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ e = RuntimeError.new("foo\nbar\nbaz")
+ assert_equal("foo (RuntimeError)\nbar\nbaz", e.detailed_message)
+ assert_equal("\e[1mfoo (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n\e[1mbar\e[m\n\e[1mbaz\e[m", e.detailed_message(highlight: true))
+ end
+ end
+
+ def test_full_message_with_custom_detailed_message
+ e = RuntimeError.new("message")
+ opt_ = nil
+ e.define_singleton_method(:detailed_message) do |**opt|
+ opt_ = opt
+ "BOO!"
+ end
+ assert_match("BOO!", e.full_message.lines.first)
+ assert_equal({ highlight: Exception.to_tty? }, opt_)
+ end
+
+ def test_full_message_with_encoding
+ message = "\u{dc}bersicht"
+ begin
+ begin
+ raise message
+ rescue => e
+ raise "\n#{e.message}"
+ end
+ rescue => e
+ end
+ assert_include(e.full_message, message)
+ end
+
+ def test_syntax_error_detailed_message
+ Dir.mktmpdir do |dir|
+ File.write(File.join(dir, "detail.rb"), "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class SyntaxError
+ def detailed_message(**)
+ Thread.new {}.join
+ "<#{super}>\n""<#{File.basename(__FILE__)}>"
+ rescue ThreadError => e
+ e.message
+ end
+ end
+ end;
+ pattern = /^<detail\.rb>/
+ assert_in_out_err(%W[-r#{dir}/detail -], "1+", [], pattern)
+
+ File.write(File.join(dir, "main.rb"), "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ 1 +
+ end;
+ assert_in_out_err(%W[-r#{dir}/detail #{dir}/main.rb]) do |stdout, stderr,|
+ assert_empty(stdout)
+ assert_not_empty(stderr.grep(pattern))
+ error, = stderr.grep(/unexpected end-of-input/)
+ assert_not_nil(error)
+ assert_match(/<.*unexpected end-of-input.*>|\^ unexpected end-of-input,/, error)
+ end
+ end
+ end
+
+ def test_syntax_error_path
+ e = assert_raise(SyntaxError) {
+ eval("1+", nil, "test_syntax_error_path.rb")
+ }
+ assert_equal("test_syntax_error_path.rb", e.path)
+
+ Dir.mktmpdir do |dir|
+ lib = File.join(dir, "syntax_error-path.rb")
+ File.write(lib, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class SyntaxError
+ def detailed_message(**)
+ STDERR.puts "\n""path=#{path}\n"
+ super
+ end
+ end
+ end;
+ main = File.join(dir, "syntax_error.rb")
+ File.write(main, "1+\n")
+ 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 0d070dcbc4..b7d2b71c19 100644
--- a/test/ruby/test_fiber.rb
+++ b/test/ruby/test_fiber.rb
@@ -34,7 +34,7 @@ class TestFiber < Test::Unit::TestCase
end
def test_many_fibers
- max = 10_000
+ max = 1000
assert_equal(max, max.times{
Fiber.new{}
})
@@ -81,12 +81,14 @@ class TestFiber < Test::Unit::TestCase
f.resume
f.resume
}
- assert_raise(RuntimeError){
- Fiber.new{
- @c = callcc{|c| @c = c}
- }.resume
- @c.call # cross fiber callcc
- }
+ if respond_to?(:callcc)
+ assert_raise(RuntimeError){
+ Fiber.new{
+ @c = callcc{|c| @c = c}
+ }.resume
+ @c.call # cross fiber callcc
+ }
+ end
assert_raise(RuntimeError){
Fiber.new{
raise
@@ -109,6 +111,15 @@ class TestFiber < Test::Unit::TestCase
}
fib.resume
}
+ assert_raise(FiberError){
+ fib = Fiber.new{}
+ fib.raise "raise in unborn fiber"
+ }
+ assert_raise(FiberError){
+ fib = Fiber.new{}
+ fib.resume
+ fib.raise "raise in dead fiber"
+ }
end
def test_return
@@ -127,6 +138,48 @@ class TestFiber < Test::Unit::TestCase
}
end
+ def test_raise
+ assert_raise(ZeroDivisionError){
+ Fiber.new do
+ 1/0
+ end.resume
+ }
+ assert_raise(RuntimeError){
+ fib = Fiber.new{ Fiber.yield }
+ fib.resume
+ fib.raise "raise and propagate"
+ }
+ assert_nothing_raised{
+ fib = Fiber.new do
+ begin
+ Fiber.yield
+ rescue
+ end
+ end
+ fib.resume
+ fib.raise "rescue in fiber"
+ }
+ fib = Fiber.new do
+ begin
+ Fiber.yield
+ rescue
+ Fiber.yield :ok
+ end
+ end
+ fib.resume
+ assert_equal(:ok, fib.raise)
+ end
+
+ def test_raise_transferring_fiber
+ root = Fiber.current
+ fib = Fiber.new { root.transfer }
+ fib.transfer
+ assert_raise(RuntimeError){
+ fib.raise "can raise with transfer: true"
+ }
+ assert_not_predicate(fib, :alive?)
+ end
+
def test_transfer
ary = []
f2 = nil
@@ -142,6 +195,33 @@ class TestFiber < Test::Unit::TestCase
assert_equal([:baz], ary)
end
+ def test_terminate_transferred_fiber
+ log = []
+ fa1 = fa2 = fb1 = r1 = nil
+
+ fa1 = Fiber.new{
+ fa2 = Fiber.new{
+ log << :fa2_terminate
+ }
+ fa2.resume
+ log << :fa1_terminate
+ }
+ fb1 = Fiber.new{
+ fa1.transfer
+ log << :fb1_terminate
+ }
+
+ r1 = Fiber.new{
+ fb1.transfer
+ log << :r1_terminate
+ }
+
+ r1.resume
+ log << :root_terminate
+
+ assert_equal [:fa2_terminate, :fa1_terminate, :r1_terminate, :root_terminate], log
+ end
+
def test_tls
#
def tvar(var, val)
@@ -171,6 +251,18 @@ class TestFiber < Test::Unit::TestCase
assert_equal(nil, Thread.current[:v]);
end
+ def test_fiber_variables
+ assert_equal "bar", Fiber.new {Fiber[:foo] = "bar"; Fiber[:foo]}.resume
+
+ key = :"#{self.class.name}#.#{self.object_id}"
+ Fiber[key] = 42
+ assert_equal 42, Fiber[key]
+
+ key = Object.new
+ def key.to_str; "foo"; end
+ assert_equal "Bar", Fiber.new {Fiber[key] = "Bar"; Fiber[key]}.resume
+ end
+
def test_alive
fib = Fiber.new{Fiber.yield}
assert_equal(true, fib.alive?)
@@ -236,48 +328,103 @@ class TestFiber < Test::Unit::TestCase
assert_instance_of(Class, Fiber.new(&Class.new.method(:undef_method)).resume(:to_s), bug5083)
end
- def test_prohibit_resume_transferred_fiber
+ def test_prohibit_transfer_to_resuming_fiber
+ root_fiber = Fiber.current
+
assert_raise(FiberError){
- root_fiber = Fiber.current
- f = Fiber.new{
- root_fiber.transfer
- }
- f.transfer
- f.resume
+ fiber = Fiber.new{ root_fiber.transfer }
+ fiber.resume
+ }
+
+ fa1 = Fiber.new{
+ _fa2 = Fiber.new{ root_fiber.transfer }
+ }
+ fb1 = Fiber.new{
+ _fb2 = Fiber.new{ root_fiber.transfer }
}
+ fa1.transfer
+ fb1.transfer
+
assert_raise(FiberError){
- g=nil
- f=Fiber.new{
- g.resume
- g.resume
- }
- g=Fiber.new{
- f.resume
- f.resume
+ fa1.transfer
+ }
+ assert_raise(FiberError){
+ fb1.transfer
+ }
+ end
+
+ def test_prohibit_transfer_to_yielding_fiber
+ f1 = f2 = f3 = nil
+
+ f1 = Fiber.new{
+ f2 = Fiber.new{
+ f3 = Fiber.new{
+ p f3: Fiber.yield
+ }
+ f3.resume
}
- f.transfer
+ f2.resume
}
+ f1.resume
+
+ assert_raise(FiberError){ f3.transfer 10 }
end
- def test_fork_from_fiber
- begin
- pid = Process.fork{}
- rescue NotImplementedError
- return
- else
- Process.wait(pid)
+ def test_prohibit_resume_to_transferring_fiber
+ root_fiber = Fiber.current
+
+ assert_raise(FiberError){
+ Fiber.new{
+ root_fiber.resume
+ }.transfer
+ }
+
+ f1 = f2 = nil
+ f1 = Fiber.new do
+ f2.transfer
+ end
+ f2 = Fiber.new do
+ f1.resume # attempt to resume transferring fiber
end
+
+ assert_raise(FiberError){
+ f1.transfer
+ }
+ end
+
+
+ def test_fork_from_fiber
+ omit 'fork not supported' unless Process.respond_to?(:fork)
+ pid = nil
bug5700 = '[ruby-core:41456]'
assert_nothing_raised(bug5700) do
Fiber.new do
pid = fork do
- Fiber.new {}.transfer
+ xpid = nil
+ Fiber.new {
+ xpid = fork do
+ # enough to trigger GC on old root fiber
+ count = 1000
+ count.times do
+ Fiber.new {}.transfer
+ Fiber.new { Fiber.yield }
+ end
+ exit!(true)
+ end
+ }.transfer
+ _, status = Process.waitpid2(xpid)
+ exit!(status.success?)
end
end.resume
end
pid, status = Process.waitpid2(pid)
- assert_equal(0, status.exitstatus, bug5700)
- assert_equal(false, status.signaled?, bug5700)
+ assert_not_predicate(status, :signaled?, bug5700)
+ assert_predicate(status, :success?, bug5700)
+
+ pid = Fiber.new {fork}.resume
+ pid, status = Process.waitpid2(pid)
+ assert_not_predicate(status, :signaled?)
+ assert_predicate(status, :success?)
end
def test_exit_in_fiber
@@ -288,56 +435,14 @@ class TestFiber < Test::Unit::TestCase
end
def test_fatal_in_fiber
- assert_in_out_err(["-r-test-/fatal/rb_fatal", "-e", <<-EOS], "", [], /ok/)
+ assert_in_out_err(["-r-test-/fatal", "-e", <<-EOS], "", [], /ok/)
Fiber.new{
- rb_fatal "ok"
+ Bug.rb_fatal "ok"
}.resume
puts :ng # unreachable.
EOS
end
- def invoke_rec script, vm_stack_size, machine_stack_size, use_length = true
- env = {}
- env['RUBY_FIBER_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size
- env['RUBY_FIBER_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size
- out, _ = Dir.mktmpdir("test_fiber") {|tmpdir|
- EnvUtil.invoke_ruby([env, '-e', script], '', true, true, chdir: tmpdir, timeout: 30)
- }
- use_length ? out.length : out
- end
-
- def test_stack_size
- h_default = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', nil, nil, false))
- h_0 = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', 0, 0, false))
- h_large = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', 1024 * 1024 * 10, 1024 * 1024 * 10, false))
-
- assert_operator(h_default[:fiber_vm_stack_size], :>, h_0[:fiber_vm_stack_size])
- assert_operator(h_default[:fiber_vm_stack_size], :<, h_large[:fiber_vm_stack_size])
- assert_operator(h_default[:fiber_machine_stack_size], :>=, h_0[:fiber_machine_stack_size])
- assert_operator(h_default[:fiber_machine_stack_size], :<=, h_large[:fiber_machine_stack_size])
-
- # check VM machine stack size
- script = '$stdout.sync=true; def rec; print "."; rec; end; Fiber.new{rec}.resume'
- size_default = invoke_rec script, nil, nil
- assert_operator(size_default, :>, 0)
- size_0 = invoke_rec script, 0, nil
- assert_operator(size_default, :>, size_0)
- size_large = invoke_rec script, 1024 * 1024 * 10, nil
- assert_operator(size_default, :<, size_large)
-
- return if /mswin|mingw/ =~ RUBY_PLATFORM
-
- # check machine stack size
- # Note that machine stack size may not change size (depend on OSs)
- script = '$stdout.sync=true; def rec; print "."; 1.times{1.times{1.times{rec}}}; end; Fiber.new{rec}.resume'
- vm_stack_size = 1024 * 1024
- size_default = invoke_rec script, vm_stack_size, nil
- size_0 = invoke_rec script, vm_stack_size, 0
- assert_operator(size_default, :>=, size_0)
- size_large = invoke_rec script, vm_stack_size, 1024 * 1024 * 10
- assert_operator(size_default, :<=, size_large)
- end
-
def test_separate_lastmatch
bug7678 = '[ruby-core:51331]'
/a/ =~ "a"
@@ -383,8 +488,17 @@ class TestFiber < Test::Unit::TestCase
assert_match(/resumed/, Fiber.current.to_s)
end
+ def test_create_fiber_in_new_thread
+ ret = Thread.new{
+ Thread.new{
+ Fiber.new{Fiber.yield :ok}.resume
+ }.value
+ }.value
+ assert_equal :ok, ret, '[Bug #14642]'
+ 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 10bfbd9ae0..a3d6221c0f 100644
--- a/test/ruby/test_file.rb
+++ b/test/ruby/test_file.rb
@@ -147,8 +147,8 @@ class TestFile < Test::Unit::TestCase
end
def test_read_all_extended_file
- [nil, {:textmode=>true}, {:binmode=>true}].each do |mode|
- Tempfile.create("test-extended-file", mode) {|f|
+ [{}, {:textmode=>true}, {:binmode=>true}].each do |mode|
+ Tempfile.create("test-extended-file", **mode) {|f|
assert_nil(f.getc)
f.print "a"
f.rewind
@@ -158,8 +158,8 @@ class TestFile < Test::Unit::TestCase
end
def test_gets_extended_file
- [nil, {:textmode=>true}, {:binmode=>true}].each do |mode|
- Tempfile.create("test-extended-file", mode) {|f|
+ [{}, {:textmode=>true}, {:binmode=>true}].each do |mode|
+ Tempfile.create("test-extended-file", **mode) {|f|
assert_nil(f.getc)
f.print "a"
f.rewind
@@ -169,8 +169,8 @@ class TestFile < Test::Unit::TestCase
end
def test_gets_para_extended_file
- [nil, {:textmode=>true}, {:binmode=>true}].each do |mode|
- Tempfile.create("test-extended-file", mode) {|f|
+ [{}, {:textmode=>true}, {:binmode=>true}].each do |mode|
+ Tempfile.create("test-extended-file", **mode) {|f|
assert_nil(f.getc)
f.print "\na"
f.rewind
@@ -180,8 +180,8 @@ class TestFile < Test::Unit::TestCase
end
def test_each_char_extended_file
- [nil, {:textmode=>true}, {:binmode=>true}].each do |mode|
- Tempfile.create("test-extended-file", mode) {|f|
+ [{}, {:textmode=>true}, {:binmode=>true}].each do |mode|
+ Tempfile.create("test-extended-file", **mode) {|f|
assert_nil(f.getc)
f.print "a"
f.rewind
@@ -193,8 +193,8 @@ class TestFile < Test::Unit::TestCase
end
def test_each_byte_extended_file
- [nil, {:textmode=>true}, {:binmode=>true}].each do |mode|
- Tempfile.create("test-extended-file", mode) {|f|
+ [{}, {:textmode=>true}, {:binmode=>true}].each do |mode|
+ Tempfile.create("test-extended-file", **mode) {|f|
assert_nil(f.getc)
f.print "a"
f.rewind
@@ -206,8 +206,8 @@ class TestFile < Test::Unit::TestCase
end
def test_getc_extended_file
- [nil, {:textmode=>true}, {:binmode=>true}].each do |mode|
- Tempfile.create("test-extended-file", mode) {|f|
+ [{}, {:textmode=>true}, {:binmode=>true}].each do |mode|
+ Tempfile.create("test-extended-file", **mode) {|f|
assert_nil(f.getc)
f.print "a"
f.rewind
@@ -217,8 +217,8 @@ class TestFile < Test::Unit::TestCase
end
def test_getbyte_extended_file
- [nil, {:textmode=>true}, {:binmode=>true}].each do |mode|
- Tempfile.create("test-extended-file", mode) {|f|
+ [{}, {:textmode=>true}, {:binmode=>true}].each do |mode|
+ Tempfile.create("test-extended-file", **mode) {|f|
assert_nil(f.getc)
f.print "a"
f.rewind
@@ -233,11 +233,9 @@ class TestFile < Test::Unit::TestCase
end
def test_chown
- assert_nothing_raised {
- File.open(__FILE__) {|f| f.chown(-1, -1) }
- }
- assert_nothing_raised("[ruby-dev:27140]") {
- File.open(__FILE__) {|f| f.chown nil, nil }
+ Tempfile.create("test-chown") {|f|
+ assert_nothing_raised {f.chown(-1, -1)}
+ assert_nothing_raised("[ruby-dev:27140]") {f.chown(nil, nil)}
}
end
@@ -252,6 +250,10 @@ class TestFile < Test::Unit::TestCase
tst = realdir + (File::SEPARATOR*3 + ".")
assert_equal(realdir, File.realpath(tst))
assert_equal(realdir, File.realpath(".", tst))
+ assert_equal(realdir, Dir.chdir(realdir) {File.realpath(".")})
+ realpath = File.join(realdir, "test")
+ File.write(realpath, "")
+ assert_equal(realpath, Dir.chdir(realdir) {File.realpath("test")})
if File::ALT_SEPARATOR
bug2961 = '[ruby-core:28653]'
assert_equal(realdir, File.realpath(realdir.tr(File::SEPARATOR, File::ALT_SEPARATOR)), bug2961)
@@ -271,7 +273,7 @@ class TestFile < Test::Unit::TestCase
begin
File.symlink(tst, a)
rescue Errno::EACCES, Errno::EPERM
- skip "need privilege"
+ omit "need privilege"
end
assert_equal(File.join(realdir, tst), File.realpath(a))
File.unlink(a)
@@ -283,26 +285,6 @@ class TestFile < Test::Unit::TestCase
}
end
- def test_realpath_taintedness
- Dir.mktmpdir('rubytest-realpath') {|tmpdir|
- dir = File.realpath(tmpdir).untaint
- File.write(File.join(dir, base = "test.file"), '')
- base.taint
- dir.taint
- assert_predicate(File.realpath(base, dir), :tainted?)
- base.untaint
- dir.taint
- assert_predicate(File.realpath(base, dir), :tainted?)
- base.taint
- dir.untaint
- assert_predicate(File.realpath(base, dir), :tainted?)
- base.untaint
- dir.untaint
- assert_predicate(File.realpath(base, dir), :tainted?)
- assert_predicate(Dir.chdir(dir) {File.realpath(base)}, :tainted?)
- }
- end
-
def test_realpath_special_symlink
IO.pipe do |r, w|
if File.pipe?(path = "/dev/fd/#{r.fileno}")
@@ -318,6 +300,8 @@ class TestFile < Test::Unit::TestCase
assert_equal(realdir, File.realdirpath(tst))
assert_equal(realdir, File.realdirpath(".", tst))
assert_equal(File.join(realdir, "foo"), File.realdirpath("foo", tst))
+ assert_equal(realdir, Dir.chdir(realdir) {File.realdirpath(".")})
+ assert_equal(File.join(realdir, "foo"), Dir.chdir(realdir) {File.realdirpath("foo")})
}
begin
result = File.realdirpath("bar", "//:/foo")
@@ -333,7 +317,7 @@ class TestFile < Test::Unit::TestCase
Dir.mktmpdir('rubytest-realpath') {|tmpdir|
Dir.chdir(tmpdir) do
Dir.mkdir('foo')
- skip "cannot run mklink" unless system('mklink /j bar foo > nul')
+ omit "cannot run mklink" unless system('mklink /j bar foo > nul')
assert_equal(File.realpath('foo'), File.realpath('bar'))
end
}
@@ -374,33 +358,58 @@ class TestFile < Test::Unit::TestCase
assert_equal(mod_time_contents, stats.mtime, bug6385)
end
+ def measure_time
+ log = []
+ 30.times do
+ t1 = Process.clock_gettime(Process::CLOCK_REALTIME)
+ yield
+ t2 = Process.clock_gettime(Process::CLOCK_REALTIME)
+ log << (t2 - t1)
+ return (t1 + t2) / 2 if t2 - t1 < 1
+ sleep 1
+ end
+ omit "failed to setup; the machine is stupidly slow #{log.inspect}"
+ 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
- t0 = Process.clock_gettime(Process::CLOCK_REALTIME)
- File.write(path, "foo")
+ measure_time do
+ File.write(path, "foo")
+ end
+
sleep 2
- File.write(path, "bar")
+
+ mtime = measure_time do
+ File.write(path, "bar")
+ end
+
sleep 2
- File.read(path)
- File.chmod(0644, path)
+
+ ctime = measure_time do
+ File.chmod(0644, path)
+ end
+
sleep 2
- File.read(path)
+
+ 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 t0+2, 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 t0+4, 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 t0+6, stat.atime.to_f, delta
+ assert_in_delta atime, stat.atime.to_f, delta
end
}
rescue NotImplementedError
@@ -465,17 +474,7 @@ class TestFile < Test::Unit::TestCase
end
end
- def test_untainted_path
- bug5374 = '[ruby-core:39745]'
- cwd = ("./"*40+".".taint).dup.untaint
- in_safe = proc {|safe| $SAFE = safe; File.stat(cwd)}
- assert_not_send([cwd, :tainted?])
- (0..1).each do |level|
- assert_nothing_raised(SecurityError, bug5374) {in_safe[level]}
- end
- end
-
- if /(bcc|ms|cyg)win|mingw|emx/ =~ RUBY_PLATFORM
+ if /mswin|mingw/ =~ RUBY_PLATFORM
def test_long_unc
feature3399 = '[ruby-core:30623]'
path = File.expand_path(__FILE__)
@@ -486,6 +485,81 @@ class TestFile < Test::Unit::TestCase
end
end
+ def test_initialize
+ Dir.mktmpdir(__method__.to_s) do |tmpdir|
+ path = File.join(tmpdir, "foo")
+
+ assert_raise(Errno::ENOENT) {File.new(path)}
+ f = File.new(path, "w")
+ f.write("FOO\n")
+ f.close
+ f = File.new(path)
+ data = f.read
+ f.close
+ assert_equal("FOO\n", data)
+
+ f = File.new(path, File::WRONLY)
+ f.write("BAR\n")
+ f.close
+ f = File.new(path, File::RDONLY)
+ data = f.read
+ f.close
+ assert_equal("BAR\n", data)
+
+ data = File.open(path) {|file|
+ File.new(file.fileno, mode: File::RDONLY, autoclose: false).read
+ }
+ assert_equal("BAR\n", data)
+
+ data = File.open(path) {|file|
+ File.new(file.fileno, File::RDONLY, autoclose: false).read
+ }
+ assert_equal("BAR\n", data)
+ end
+ end
+
+ def test_file_open_newline_option
+ Dir.mktmpdir(__method__.to_s) do |tmpdir|
+ path = File.join(tmpdir, "foo")
+ test = lambda do |newline|
+ File.open(path, "wt", newline: newline) do |f|
+ f.write "a\n"
+ f.puts "b"
+ end
+ File.binread(path)
+ end
+ assert_equal("a\nb\n", test.(:lf))
+ assert_equal("a\nb\n", test.(:universal))
+ assert_equal("a\r\nb\r\n", test.(:crlf))
+ assert_equal("a\rb\r", test.(:cr))
+
+ test = lambda do |newline|
+ File.open(path, "rt", newline: newline) do |f|
+ f.read
+ end
+ end
+
+ File.binwrite(path, "a\nb\n")
+ assert_equal("a\nb\n", test.(:lf))
+ assert_equal("a\nb\n", test.(:universal))
+ assert_equal("a\nb\n", test.(:crlf))
+ assert_equal("a\nb\n", test.(:cr))
+
+ File.binwrite(path, "a\r\nb\r\n")
+ assert_equal("a\r\nb\r\n", test.(:lf))
+ assert_equal("a\nb\n", test.(:universal))
+ # Work on both Windows and non-Windows
+ assert_include(["a\r\nb\r\n", "a\nb\n"], test.(:crlf))
+ assert_equal("a\r\nb\r\n", test.(:cr))
+
+ File.binwrite(path, "a\rb\r")
+ assert_equal("a\rb\r", test.(:lf))
+ assert_equal("a\nb\n", test.(:universal))
+ assert_equal("a\rb\r", test.(:crlf))
+ assert_equal("a\rb\r", test.(:cr))
+ end
+ end
+
def test_open_nul
Dir.mktmpdir(__method__.to_s) do |tmpdir|
path = File.join(tmpdir, "foo")
@@ -501,18 +575,287 @@ class TestFile < Test::Unit::TestCase
begin
io = File.open(tmpdir, File::RDWR | File::TMPFILE)
rescue Errno::EINVAL
- skip 'O_TMPFILE not supported (EINVAL)'
+ omit 'O_TMPFILE not supported (EINVAL)'
+ rescue Errno::EISDIR
+ omit 'O_TMPFILE not supported (EISDIR)'
rescue Errno::EOPNOTSUPP
- skip 'O_TMPFILE not supported (EOPNOTSUPP)'
+ omit 'O_TMPFILE not supported (EOPNOTSUPP)'
end
io.write "foo"
io.flush
assert_equal 3, io.size
- assert_raise(IOError) { io.path }
+ assert_nil io.path
ensure
io&.close
end
end if File::Constants.const_defined?(:TMPFILE)
+ def test_absolute_path?
+ assert_file.absolute_path?(File.absolute_path(__FILE__))
+ assert_file.absolute_path?("//foo/bar\\baz")
+ assert_file.not_absolute_path?(File.basename(__FILE__))
+ assert_file.not_absolute_path?("C:foo\\bar")
+ assert_file.not_absolute_path?("~")
+ assert_file.not_absolute_path?("~user")
+
+ if /cygwin|mswin|mingw/ =~ RUBY_PLATFORM
+ assert_file.absolute_path?("C:\\foo\\bar")
+ assert_file.absolute_path?("C:/foo/bar")
+ else
+ assert_file.not_absolute_path?("C:\\foo\\bar")
+ assert_file.not_absolute_path?("C:/foo/bar")
+ end
+ if /mswin|mingw/ =~ RUBY_PLATFORM
+ assert_file.not_absolute_path?("/foo/bar\\baz")
+ else
+ assert_file.absolute_path?("/foo/bar\\baz")
+ end
+ end
+
+ class NewlineConvTests < Test::Unit::TestCase
+ TEST_STRING_WITH_CRLF = "line1\r\nline2\r\n".freeze
+ TEST_STRING_WITH_LF = "line1\nline2\n".freeze
+
+ def setup
+ @tmpdir = Dir.mktmpdir(self.class.name)
+ @read_path_with_crlf = File.join(@tmpdir, "read_path_with_crlf")
+ File.binwrite(@read_path_with_crlf, TEST_STRING_WITH_CRLF)
+ @read_path_with_lf = File.join(@tmpdir, "read_path_with_lf")
+ File.binwrite(@read_path_with_lf, TEST_STRING_WITH_LF)
+ @write_path = File.join(@tmpdir, "write_path")
+ File.binwrite(@write_path, '')
+ end
+
+ def teardown
+ FileUtils.rm_rf @tmpdir
+ end
+
+ def windows?
+ /cygwin|mswin|mingw/ =~ RUBY_PLATFORM
+ end
+
+ def open_file_with(method, filename, mode)
+ read_or_write = mode.include?('w') ? :write : :read
+ binary_or_text = mode.include?('b') ? :binary : :text
+
+ f = case method
+ when :ruby_file_open
+ File.open(filename, mode)
+ when :c_rb_file_open
+ Bug::File::NewlineConv.rb_file_open(filename, read_or_write, binary_or_text)
+ when :c_rb_io_fdopen
+ Bug::File::NewlineConv.rb_io_fdopen(filename, read_or_write, binary_or_text)
+ else
+ raise "Don't know how to open with #{method}"
+ end
+
+ begin
+ yield f
+ ensure
+ f.close
+ end
+ end
+
+ def assert_file_contents_has_lf(f)
+ assert_equal TEST_STRING_WITH_LF, f.read
+ end
+
+ def assert_file_contents_has_crlf(f)
+ assert_equal TEST_STRING_WITH_CRLF, f.read
+ end
+
+ def assert_file_contents_has_lf_on_windows(f)
+ if windows?
+ assert_file_contents_has_lf(f)
+ else
+ assert_file_contents_has_crlf(f)
+ end
+ end
+
+ def assert_file_contents_has_crlf_on_windows(f)
+ if windows?
+ assert_file_contents_has_crlf(f)
+ else
+ assert_file_contents_has_lf(f)
+ end
+ end
+
+ def test_ruby_file_open_text_mode_read_crlf
+ open_file_with(:ruby_file_open, @read_path_with_crlf, 'r') { |f| assert_file_contents_has_lf_on_windows(f) }
+ end
+
+ def test_ruby_file_open_bin_mode_read_crlf
+ open_file_with(:ruby_file_open, @read_path_with_crlf, 'rb') { |f| assert_file_contents_has_crlf(f) }
+ end
+
+ def test_ruby_file_open_text_mode_read_lf
+ open_file_with(:ruby_file_open, @read_path_with_lf, 'r') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_ruby_file_open_bin_mode_read_lf
+ open_file_with(:ruby_file_open, @read_path_with_lf, 'rb') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_ruby_file_open_text_mode_read_crlf_with_utf8_encoding
+ open_file_with(:ruby_file_open, @read_path_with_crlf, 'r') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf_on_windows(f)
+ end
+ end
+
+ def test_ruby_file_open_bin_mode_read_crlf_with_utf8_encoding
+ open_file_with(:ruby_file_open, @read_path_with_crlf, 'rb') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_crlf(f)
+ end
+ end
+
+ def test_ruby_file_open_text_mode_read_lf_with_utf8_encoding
+ open_file_with(:ruby_file_open, @read_path_with_lf, 'r') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf(f)
+ end
+ end
+
+ def test_ruby_file_open_bin_mode_read_lf_with_utf8_encoding
+ open_file_with(:ruby_file_open, @read_path_with_lf, 'rb') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf(f)
+ end
+ end
+
+ def test_ruby_file_open_text_mode_write_lf
+ open_file_with(:ruby_file_open, @write_path, 'w') { |f| f.write TEST_STRING_WITH_LF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf_on_windows(f) }
+ end
+
+ def test_ruby_file_open_bin_mode_write_lf
+ open_file_with(:ruby_file_open, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_LF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_ruby_file_open_bin_mode_write_crlf
+ open_file_with(:ruby_file_open, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_CRLF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf(f) }
+ end
+
+ def test_c_rb_file_open_text_mode_read_crlf
+ open_file_with(:c_rb_file_open, @read_path_with_crlf, 'r') { |f| assert_file_contents_has_lf_on_windows(f) }
+ end
+
+ def test_c_rb_file_open_bin_mode_read_crlf
+ open_file_with(:c_rb_file_open, @read_path_with_crlf, 'rb') { |f| assert_file_contents_has_crlf(f) }
+ end
+
+ def test_c_rb_file_open_text_mode_read_lf
+ open_file_with(:c_rb_file_open, @read_path_with_lf, 'r') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_c_rb_file_open_bin_mode_read_lf
+ open_file_with(:c_rb_file_open, @read_path_with_lf, 'rb') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_c_rb_file_open_text_mode_write_lf
+ open_file_with(:c_rb_file_open, @write_path, 'w') { |f| f.write TEST_STRING_WITH_LF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf_on_windows(f) }
+ end
+
+ def test_c_rb_file_open_bin_mode_write_lf
+ open_file_with(:c_rb_file_open, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_LF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_c_rb_file_open_bin_mode_write_crlf
+ open_file_with(:c_rb_file_open, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_CRLF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf(f) }
+ end
+
+ def test_c_rb_file_open_text_mode_read_crlf_with_utf8_encoding
+ open_file_with(:c_rb_file_open, @read_path_with_crlf, 'r') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf_on_windows(f)
+ end
+ end
+
+ def test_c_rb_file_open_bin_mode_read_crlf_with_utf8_encoding
+ open_file_with(:c_rb_file_open, @read_path_with_crlf, 'rb') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_crlf(f)
+ end
+ end
+
+ def test_c_rb_file_open_text_mode_read_lf_with_utf8_encoding
+ open_file_with(:c_rb_file_open, @read_path_with_lf, 'r') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf(f)
+ end
+ end
+
+ def test_c_rb_file_open_bin_mode_read_lf_with_utf8_encoding
+ open_file_with(:c_rb_file_open, @read_path_with_lf, 'rb') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf(f)
+ end
+ end
+
+ def test_c_rb_io_fdopen_text_mode_read_crlf
+ open_file_with(:c_rb_io_fdopen, @read_path_with_crlf, 'r') { |f| assert_file_contents_has_lf_on_windows(f) }
+ end
+
+ def test_c_rb_io_fdopen_bin_mode_read_crlf
+ open_file_with(:c_rb_io_fdopen, @read_path_with_crlf, 'rb') { |f| assert_file_contents_has_crlf(f) }
+ end
+
+ def test_c_rb_io_fdopen_text_mode_read_lf
+ open_file_with(:c_rb_io_fdopen, @read_path_with_lf, 'r') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_c_rb_io_fdopen_bin_mode_read_lf
+ open_file_with(:c_rb_io_fdopen, @read_path_with_lf, 'rb') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_c_rb_io_fdopen_text_mode_write_lf
+ open_file_with(:c_rb_io_fdopen, @write_path, 'w') { |f| f.write TEST_STRING_WITH_LF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf_on_windows(f) }
+ end
+
+ def test_c_rb_io_fdopen_bin_mode_write_lf
+ open_file_with(:c_rb_io_fdopen, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_LF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_c_rb_io_fdopen_bin_mode_write_crlf
+ open_file_with(:c_rb_io_fdopen, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_CRLF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf(f) }
+ end
+
+ def test_c_rb_io_fdopen_text_mode_read_crlf_with_utf8_encoding
+ open_file_with(:c_rb_io_fdopen, @read_path_with_crlf, 'r') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf_on_windows(f)
+ end
+ end
+
+ def test_c_rb_io_fdopen_bin_mode_read_crlf_with_utf8_encoding
+ open_file_with(:c_rb_io_fdopen, @read_path_with_crlf, 'rb') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_crlf(f)
+ end
+ end
+
+ def test_c_rb_io_fdopen_text_mode_read_lf_with_utf8_encoding
+ open_file_with(:c_rb_io_fdopen, @read_path_with_lf, 'r') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf(f)
+ end
+ end
+
+ def test_c_rb_io_fdopen_bin_mode_read_lf_with_utf8_encoding
+ open_file_with(:c_rb_io_fdopen, @read_path_with_lf, 'rb') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf(f)
+ end
+ end
+ end
end
diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb
index 817c3580d1..394dc47603 100644
--- a/test/ruby/test_file_exhaustive.rb
+++ b/test/ruby/test_file_exhaustive.rb
@@ -6,7 +6,8 @@ require "socket"
require '-test-/file'
class TestFileExhaustive < Test::Unit::TestCase
- DRIVE = Dir.pwd[%r'\A(?:[a-z]:|//[^/]+/[^/]+)'i]
+ ROOT_REGEXP = %r'\A(?:[a-z]:(?=(/))|//[^/]+/[^/]+)'i
+ DRIVE = Dir.pwd[ROOT_REGEXP]
POSIX = /cygwin|mswin|bccwin|mingw|emx/ !~ RUBY_PLATFORM
NTFS = !(/mingw|mswin|bccwin/ !~ RUBY_PLATFORM)
@@ -18,7 +19,7 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def setup
- @dir = Dir.mktmpdir("rubytest-file")
+ @dir = Dir.mktmpdir("ruby-test")
File.chown(-1, Process.gid, @dir)
end
@@ -70,7 +71,7 @@ class TestFileExhaustive < Test::Unit::TestCase
def notownedfile
return @notownedfile if defined? @notownedfile
- if Process.euid != 0
+ if Process.euid != File.stat("/").uid
@notownedfile = '/'
else
@notownedfile = nil
@@ -78,6 +79,19 @@ class TestFileExhaustive < Test::Unit::TestCase
@notownedfile
end
+ def grpownedfile
+ return nil unless POSIX
+ return @grpownedfile if defined? @grpownedfile
+ if group = (Process.groups - [Process.egid]).last
+ grpownedfile = make_tmp_filename("grpownedfile")
+ make_file("grpowned", grpownedfile)
+ File.chown(nil, group, grpownedfile)
+ return @grpownedfile = grpownedfile
+ end
+ rescue
+ @grpownedfile = nil
+ end
+
def suidfile
return @suidfile if defined? @suidfile
if POSIX
@@ -130,7 +144,7 @@ class TestFileExhaustive < Test::Unit::TestCase
@hardlinkfile = make_tmp_filename("hardlinkfile")
begin
File.link(regular_file, @hardlinkfile)
- rescue NotImplementedError, Errno::EINVAL # EINVAL for Windows Vista
+ rescue NotImplementedError, Errno::EINVAL, Errno::EACCES # EINVAL for Windows Vista, EACCES for Android Termux
@hardlinkfile = nil
end
@hardlinkfile
@@ -160,9 +174,7 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def chardev
- return @chardev if defined? @chardev
- @chardev = File::NULL == "/dev/null" ? "/dev/null" : nil
- @chardev
+ File::NULL
end
def blockdev
@@ -175,16 +187,42 @@ class TestFileExhaustive < Test::Unit::TestCase
@blockdev
end
+ def root_without_capabilities?
+ return false unless Process.uid == 0
+ return false unless system('command', '-v', 'capsh', out: File::NULL)
+ !system('capsh', '--has-p=CAP_DAC_OVERRIDE', out: File::NULL, err: File::NULL)
+ end
+
def test_path
[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)
@@ -319,7 +357,7 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_file.not_chardev?(regular_file)
assert_file.not_chardev?(utf8_file)
assert_file.not_chardev?(nofile)
- assert_file.chardev?(chardev) if chardev
+ assert_file.chardev?(chardev)
end
def test_exist_p
@@ -493,24 +531,44 @@ class TestFileExhaustive < Test::Unit::TestCase
def test_grpowned_p ## xxx
assert_file.grpowned?(regular_file)
assert_file.grpowned?(utf8_file)
+ if file = grpownedfile
+ assert_file.grpowned?(file)
+ end
end if POSIX
+ def io_open(file_name)
+ # avoid File.open since we do not want #to_path
+ io = IO.for_fd(IO.sysopen(file_name))
+ yield io
+ ensure
+ io&.close
+ end
+
def test_suid
assert_file.not_setuid?(regular_file)
assert_file.not_setuid?(utf8_file)
- assert_file.setuid?(suidfile) if suidfile
+ if suidfile
+ assert_file.setuid?(suidfile)
+ io_open(suidfile) { |io| assert_file.setuid?(io) }
+ end
end
def test_sgid
assert_file.not_setgid?(regular_file)
assert_file.not_setgid?(utf8_file)
- assert_file.setgid?(sgidfile) if sgidfile
+ if sgidfile
+ assert_file.setgid?(sgidfile)
+ io_open(sgidfile) { |io| assert_file.setgid?(io) }
+ end
end
def test_sticky
assert_file.not_sticky?(regular_file)
assert_file.not_sticky?(utf8_file)
- assert_file.sticky?(stickyfile) if stickyfile
+ if stickyfile
+ assert_file.sticky?(stickyfile)
+ io_open(stickyfile) { |io| assert_file.sticky?(io) }
+ end
end
def test_path_identical_p
@@ -606,6 +664,23 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_raise(Errno::ENOENT) { File.ctime(nofile) }
end
+ def test_birthtime
+ omit if RUBY_PLATFORM =~ /android/
+ [regular_file, utf8_file].each do |file|
+ t1 = File.birthtime(file)
+ t2 = File.open(file) {|f| f.birthtime}
+ assert_kind_of(Time, t1)
+ assert_kind_of(Time, t2)
+ assert_equal(t1, t2)
+ rescue Errno::ENOSYS, NotImplementedError
+ # ignore unsupporting filesystems
+ rescue Errno::EPERM
+ # Docker prohibits statx syscall by the default.
+ omit("statx(2) is prohibited by seccomp")
+ end
+ assert_raise(Errno::ENOENT) { File.birthtime(nofile) }
+ end if File.respond_to?(:birthtime)
+
def test_chmod
[regular_file, utf8_file].each do |file|
assert_equal(1, File.chmod(0444, file))
@@ -645,12 +720,18 @@ class TestFileExhaustive < Test::Unit::TestCase
File.utime(t + 1, t + 2, zerofile)
assert_equal(t + 1, File.atime(zerofile))
assert_equal(t + 2, File.mtime(zerofile))
+ Dir.mktmpdir do |dir|
+ Dir.chdir(dir) do
+ path = "foo\u{30b3 30d4 30fc}"
+ File.write(path, "") rescue next
+ assert_equal(1, File.utime(nil, nil, path))
+ end
+ end
end
def test_utime_symlinkfile
return unless symlinkfile
t = Time.local(2000)
- stat = File.lstat(symlinkfile)
assert_equal(1, File.utime(t, t, symlinkfile))
assert_equal(t, File.stat(regular_file).atime)
assert_equal(t, File.stat(regular_file).mtime)
@@ -717,7 +798,7 @@ class TestFileExhaustive < Test::Unit::TestCase
def test_readlink_junction
base = File.basename(nofile)
err = IO.popen(%W"cmd.exe /c mklink /j #{base} .", chdir: @dir, err: %i[child out], &:read)
- skip err unless $?.success?
+ omit err unless $?.success?
assert_equal(@dir, File.readlink(nofile))
end
@@ -764,7 +845,10 @@ class TestFileExhaustive < Test::Unit::TestCase
def test_expand_path
assert_equal(regular_file, File.expand_path(File.basename(regular_file), File.dirname(regular_file)))
assert_equal(utf8_file, File.expand_path(File.basename(utf8_file), File.dirname(utf8_file)))
- if NTFS
+ end
+
+ if NTFS
+ def test_expand_path_ntfs
[regular_file, utf8_file].each do |file|
assert_equal(file, File.expand_path(file + " "))
assert_equal(file, File.expand_path(file + "."))
@@ -775,8 +859,11 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_match(/\Ae:\//i, File.expand_path('e:foo', 'd:/bar'))
assert_match(%r'\Ac:/bar/foo\z'i, File.expand_path('c:foo', 'c:/bar'))
end
- case RUBY_PLATFORM
- when /darwin/
+ end
+
+ case RUBY_PLATFORM
+ when /darwin/
+ def test_expand_path_hfs
["\u{feff}", *"\u{2000}"..."\u{2100}"].each do |c|
file = regular_file + c
full_path = File.expand_path(file)
@@ -792,11 +879,16 @@ class TestFileExhaustive < Test::Unit::TestCase
end
end
end
- if DRIVE
+ end
+
+ if DRIVE
+ def test_expand_path_absolute
assert_match(%r"\Az:/foo\z"i, File.expand_path('/foo', "z:/bar"))
assert_match(%r"\A//host/share/foo\z"i, File.expand_path('/foo', "//host/share/bar"))
assert_match(%r"\A#{DRIVE}/foo\z"i, File.expand_path('/foo'))
- else
+ end
+ else
+ def test_expand_path_absolute
assert_equal("/foo", File.expand_path('/foo'))
end
end
@@ -805,9 +897,10 @@ class TestFileExhaustive < Test::Unit::TestCase
bug9934 = '[ruby-core:63114] [Bug #9934]'
require "objspace"
path = File.expand_path("/foo")
- assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE], bug9934)
+ assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE], bug9934)
path = File.expand_path("/a"*25)
- assert_equal(path.bytesize+1 + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE], ObjectSpace.memsize_of(path), bug9934)
+ assert_operator(ObjectSpace.memsize_of(path), :<=,
+ (path.bytesize + 1) * 2 + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE], bug9934)
end
def test_expand_path_encoding
@@ -836,6 +929,8 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_equal("#{Dir.pwd}/#{path}", File.expand_path(path))
assert_incompatible_encoding {|d| File.expand_path(d)}
+
+ assert_equal(Encoding::UTF_8, File.expand_path("foo", "#{drive}/").encoding)
end
def test_expand_path_encoding_filesystem
@@ -1015,32 +1110,6 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_match(%r"\A#{DRIVE}/foo\z"i, File.expand_path('/foo'))
end
- def test_expand_path_returns_tainted_strings_or_not
- assert_equal(true, File.expand_path('foo').tainted?)
- assert_equal(true, File.expand_path('foo'.taint).tainted?)
- assert_equal(true, File.expand_path('/foo'.taint).tainted?)
- assert_equal(true, File.expand_path('foo', 'bar').tainted?)
- assert_equal(true, File.expand_path('foo', '/bar'.taint).tainted?)
- assert_equal(true, File.expand_path('foo'.taint, '/bar').tainted?)
- assert_equal(true, File.expand_path('~').tainted?) if ENV["HOME"]
-
- if DRIVE
- assert_equal(true, File.expand_path('/foo').tainted?)
- assert_equal(false, File.expand_path('//foo').tainted?)
- assert_equal(true, File.expand_path('C:/foo'.taint).tainted?)
- assert_equal(false, File.expand_path('C:/foo').tainted?)
- assert_equal(true, File.expand_path('foo', '/bar').tainted?)
- assert_equal(true, File.expand_path('foo', 'C:/bar'.taint).tainted?)
- assert_equal(true, File.expand_path('foo'.taint, 'C:/bar').tainted?)
- assert_equal(false, File.expand_path('foo', 'C:/bar').tainted?)
- assert_equal(false, File.expand_path('C:/foo/../bar').tainted?)
- assert_equal(false, File.expand_path('foo', '//bar').tainted?)
- else
- assert_equal(false, File.expand_path('/foo').tainted?)
- assert_equal(false, File.expand_path('foo', '/bar').tainted?)
- end
- end
-
def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_home_as_base
old_home = ENV["HOME"]
home = ENV["HOME"] = "#{DRIVE}/UserHome"
@@ -1076,7 +1145,7 @@ class TestFileExhaustive < Test::Unit::TestCase
def test_expand_path_for_existent_username
user = ENV['USER']
- skip "ENV['USER'] is not set" unless user
+ omit "ENV['USER'] is not set" unless user
assert_equal(ENV['HOME'], File.expand_path("~#{user}"))
end unless DRIVE
@@ -1166,7 +1235,10 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_equal("foo", File.basename("foo", ".ext"))
assert_equal("foo", File.basename("foo.ext", ".ext"))
assert_equal("foo", File.basename("foo.ext", ".*"))
- if NTFS
+ end
+
+ if NTFS
+ def test_basename_strip
[regular_file, utf8_file].each do |file|
basename = File.basename(file)
assert_equal(basename, File.basename(file + " "))
@@ -1180,7 +1252,9 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_equal(basename, File.basename(file + ".", ".*"))
assert_equal(basename, File.basename(file + "::$DATA", ".*"))
end
- else
+ end
+ else
+ def test_basename_strip
[regular_file, utf8_file].each do |file|
basename = File.basename(file)
assert_equal(basename + " ", File.basename(file + " "))
@@ -1195,13 +1269,18 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_equal(basename, File.basename(file + "::$DATA", ".*"))
end
end
- if File::ALT_SEPARATOR == '\\'
+ end
+
+ if File::ALT_SEPARATOR == '\\'
+ def test_basename_backslash
a = "foo/\225\\\\"
[%W"cp437 \225", %W"cp932 \225\\"].each do |cp, expected|
assert_equal(expected.force_encoding(cp), File.basename(a.dup.force_encoding(cp)), cp)
end
end
+ end
+ def test_basename_encoding
assert_incompatible_encoding {|d| File.basename(d)}
assert_incompatible_encoding {|d| File.basename(d, ".*")}
assert_raise(Encoding::CompatibilityError) {File.basename("foo.ext", ".*".encode("utf-16le"))}
@@ -1217,8 +1296,21 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_equal(@dir, File.dirname(regular_file))
assert_equal(@dir, File.dirname(utf8_file))
assert_equal(".", File.dirname(""))
+ assert_equal(regular_file, File.dirname(regular_file, 0))
+ assert_equal(@dir, File.dirname(regular_file, 1))
+ assert_equal(File.dirname(@dir), File.dirname(regular_file, 2))
+ assert_raise(ArgumentError) {File.dirname(regular_file, -1)}
+ root = "#{@dir[ROOT_REGEXP]||?/}#{$1}"
+ assert_equal(root, File.dirname(regular_file, regular_file.count('/')))
+ assert_equal(root, File.dirname(regular_file, regular_file.count('/') + 100))
+ end
+
+ def test_dirname_encoding
assert_incompatible_encoding {|d| File.dirname(d)}
- if File::ALT_SEPARATOR == '\\'
+ end
+
+ if File::ALT_SEPARATOR == '\\'
+ def test_dirname_backslash
a = "\225\\\\foo"
[%W"cp437 \225", %W"cp932 \225\\"].each do |cp, expected|
assert_equal(expected.force_encoding(cp), File.dirname(a.dup.force_encoding(cp)), cp)
@@ -1230,21 +1322,23 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_equal(".test", File.extname(regular_file))
assert_equal(".test", File.extname(utf8_file))
prefixes = ["", "/", ".", "/.", "bar/.", "/bar/."]
- infixes = ["", " ", "."]
+ infixes = ["", " "]
infixes2 = infixes + [".ext "]
appendixes = [""]
if NTFS
appendixes << " " << "." << "::$DATA" << "::$DATA.bar"
+ else
+ appendixes << [".", "."]
end
prefixes.each do |prefix|
- appendixes.each do |appendix|
+ appendixes.each do |appendix, ext = ""|
infixes.each do |infix|
path = "#{prefix}foo#{infix}#{appendix}"
- assert_equal("", File.extname(path), "File.extname(#{path.inspect})")
+ assert_equal(ext, File.extname(path), "File.extname(#{path.inspect})")
end
infixes2.each do |infix|
path = "#{prefix}foo#{infix}.ext#{appendix}"
- assert_equal(".ext", File.extname(path), "File.extname(#{path.inspect})")
+ assert_equal(ext.empty? ? ".ext" : appendix, File.extname(path), "File.extname(#{path.inspect})")
end
end
end
@@ -1341,21 +1435,26 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def test_flock_exclusive
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
+ timeout = EnvUtil.apply_timeout_scale(1).to_s
File.open(regular_file, "r+") do |f|
f.flock(File::LOCK_EX)
- assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}")
- begin
+ assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ timeout = ARGV[1].to_f
open(ARGV[0], "r") do |f|
- Timeout.timeout(0.1) do
+ Timeout.timeout(timeout) do
assert(!f.flock(File::LOCK_SH|File::LOCK_NB))
end
end
end;
- assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}")
- begin
+ assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ timeout = ARGV[1].to_f
open(ARGV[0], "r") do |f|
assert_raise(Timeout::Error) do
- Timeout.timeout(0.1) do
+ Timeout.timeout(timeout) do
f.flock(File::LOCK_SH)
end
end
@@ -1367,21 +1466,26 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def test_flock_shared
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
+ timeout = EnvUtil.apply_timeout_scale(1).to_s
File.open(regular_file, "r+") do |f|
f.flock(File::LOCK_SH)
- assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}")
- begin
+ assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ timeout = ARGV[1].to_f
open(ARGV[0], "r") do |f|
- Timeout.timeout(0.1) do
+ Timeout.timeout(timeout) do
assert(f.flock(File::LOCK_SH))
end
end
end;
- assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}")
- begin
+ assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ timeout = ARGV[1].to_f
open(ARGV[0], "r+") do |f|
assert_raise(Timeout::Error) do
- Timeout.timeout(0.1) do
+ Timeout.timeout(timeout) do
f.flock(File::LOCK_EX)
end
end
@@ -1393,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)
@@ -1403,6 +1508,7 @@ class TestFileExhaustive < Test::Unit::TestCase
fn1,
zerofile,
notownedfile,
+ grpownedfile,
suidfile,
sgidfile,
stickyfile,
@@ -1413,31 +1519,68 @@ class TestFileExhaustive < Test::Unit::TestCase
fifo,
socket
].compact.each do |f|
- assert_equal(File.atime(f), test(?A, f))
- assert_equal(File.ctime(f), test(?C, f))
- assert_equal(File.mtime(f), test(?M, f))
- assert_equal(File.blockdev?(f), test(?b, f))
- assert_equal(File.chardev?(f), test(?c, f))
- assert_equal(File.directory?(f), test(?d, f))
- assert_equal(File.exist?(f), test(?e, f))
- assert_equal(File.file?(f), test(?f, f))
- assert_equal(File.setgid?(f), test(?g, f))
- assert_equal(File.grpowned?(f), test(?G, f))
- assert_equal(File.sticky?(f), test(?k, f))
- assert_equal(File.symlink?(f), test(?l, f))
- assert_equal(File.owned?(f), test(?o, f))
- assert_nothing_raised { test(?O, f) }
- assert_equal(File.pipe?(f), test(?p, f))
- assert_equal(File.readable?(f), test(?r, f))
- assert_equal(File.readable_real?(f), test(?R, f))
- assert_equal(File.size?(f), test(?s, f))
- assert_equal(File.socket?(f), test(?S, f))
- assert_equal(File.setuid?(f), test(?u, f))
- assert_equal(File.writable?(f), test(?w, f))
- assert_equal(File.writable_real?(f), test(?W, f))
- assert_equal(File.executable?(f), test(?x, f))
- assert_equal(File.executable_real?(f), test(?X, f))
- assert_equal(File.zero?(f), test(?z, f))
+ assert_equal(File.atime(f), test(?A, f), f)
+ assert_equal(File.ctime(f), test(?C, f), f)
+ assert_equal(File.mtime(f), test(?M, f), f)
+ assert_equal(File.blockdev?(f), test(?b, f), f)
+ assert_equal(File.chardev?(f), test(?c, f), f)
+ assert_equal(File.directory?(f), test(?d, f), f)
+ assert_equal(File.exist?(f), test(?e, f), f)
+ assert_equal(File.file?(f), test(?f, f), f)
+ assert_equal(File.setgid?(f), test(?g, f), f)
+ assert_equal(File.grpowned?(f), test(?G, f), f)
+ assert_equal(File.sticky?(f), test(?k, f), f)
+ assert_equal(File.symlink?(f), test(?l, f), f)
+ assert_equal(File.owned?(f), test(?o, f), f)
+ assert_nothing_raised(f) { test(?O, f) }
+ assert_equal(File.pipe?(f), test(?p, f), f)
+ assert_equal(File.readable?(f), test(?r, f), f)
+ assert_equal(File.readable_real?(f), test(?R, f), f)
+ assert_equal(File.size?(f), test(?s, f), f)
+ assert_equal(File.socket?(f), test(?S, f), f)
+ assert_equal(File.setuid?(f), test(?u, f), f)
+ assert_equal(File.writable?(f), test(?w, f), f)
+ assert_equal(File.writable_real?(f), test(?W, f), f)
+ assert_equal(File.executable?(f), test(?x, f), f)
+ assert_equal(File.executable_real?(f), test(?X, f), f)
+ assert_equal(File.zero?(f), test(?z, f), f)
+
+ stat = File.stat(f)
+ unless stat.chardev?
+ # null device may be accessed by other processes
+ assert_equal(stat.atime, File.atime(f), f)
+ assert_equal(stat.ctime, File.ctime(f), f)
+ assert_equal(stat.mtime, File.mtime(f), f)
+ end
+ assert_bool_equal(stat.blockdev?, File.blockdev?(f), f)
+ assert_bool_equal(stat.chardev?, File.chardev?(f), f)
+ assert_bool_equal(stat.directory?, File.directory?(f), f)
+ assert_bool_equal(stat.file?, File.file?(f), f)
+ assert_bool_equal(stat.setgid?, File.setgid?(f), f)
+ assert_bool_equal(stat.grpowned?, File.grpowned?(f), f)
+ assert_bool_equal(stat.sticky?, File.sticky?(f), f)
+ assert_bool_equal(File.lstat(f).symlink?, File.symlink?(f), f)
+ assert_bool_equal(stat.owned?, File.owned?(f), f)
+ assert_bool_equal(stat.pipe?, File.pipe?(f), f)
+ assert_bool_equal(stat.readable?, File.readable?(f), f)
+ assert_bool_equal(stat.readable_real?, File.readable_real?(f), f)
+ assert_equal(stat.size?, File.size?(f), f)
+ assert_bool_equal(stat.socket?, File.socket?(f), f)
+ assert_bool_equal(stat.setuid?, File.setuid?(f), f)
+ # It's possible in Linux to be uid 0, but not to have the CAP_DAC_OVERRIDE
+ # capability that allows skipping file permissions checks (e.g. some kinds
+ # of "rootless" container setups). In these cases, stat.writable? will be
+ # true (because it always returns true if Process.uid == 0), but
+ # File.writeable? will be false (because it actually asks the kernel to do
+ # an access check).
+ # Skip these two assertions in that case.
+ unless root_without_capabilities?
+ assert_bool_equal(stat.writable?, File.writable?(f), f)
+ assert_bool_equal(stat.writable_real?, File.writable_real?(f), f)
+ end
+ assert_bool_equal(stat.executable?, File.executable?(f), f)
+ assert_bool_equal(stat.executable_real?, File.executable_real?(f), f)
+ assert_bool_equal(stat.zero?, File.zero?(f), f)
end
assert_equal(false, test(?-, @dir, fn1))
assert_equal(true, test(?-, fn1, fn1))
@@ -1477,11 +1620,7 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_integer_or_nil(fs1.rdev_minor)
assert_integer(fs1.ino)
assert_integer(fs1.mode)
- unless /emx|mswin|mingw/ =~ RUBY_PLATFORM
- # on Windows, nlink is always 1. but this behavior will be changed
- # in the future.
- assert_equal(hardlinkfile ? 2 : 1, fs1.nlink)
- end
+ assert_equal(hardlinkfile ? 2 : 1, fs1.nlink)
assert_integer(fs1.uid)
assert_integer(fs1.gid)
assert_equal(3, fs1.size)
@@ -1551,7 +1690,7 @@ class TestFileExhaustive < Test::Unit::TestCase
def test_stat_chardev_p
assert_not_predicate(File::Stat.new(@dir), :chardev?)
assert_not_predicate(File::Stat.new(regular_file), :chardev?)
- assert_predicate(File::Stat.new(chardev), :chardev?) if chardev
+ assert_predicate(File::Stat.new(chardev), :chardev?)
end
def test_stat_readable_p
@@ -1642,6 +1781,9 @@ class TestFileExhaustive < Test::Unit::TestCase
def test_stat_grpowned_p ## xxx
assert_predicate(File::Stat.new(regular_file), :grpowned?)
+ if file = grpownedfile
+ assert_predicate(File::Stat.new(file), :grpowned?)
+ end
end if POSIX
def test_stat_suid
@@ -1691,4 +1833,8 @@ class TestFileExhaustive < Test::Unit::TestCase
dir = File.expand_path("/bar")
assert_equal(File.join(dir, "~foo"), File.absolute_path("~foo", dir))
end
+
+ def assert_bool_equal(expected, result, *messages)
+ assert_equal(expected, true & result, *messages)
+ end
end
diff --git a/test/ruby/test_fixnum.rb b/test/ruby/test_fixnum.rb
index bd18067dda..2878258920 100644
--- a/test/ruby/test_fixnum.rb
+++ b/test/ruby/test_fixnum.rb
@@ -4,7 +4,6 @@ require 'test/unit'
class TestFixnum < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
end
def teardown
diff --git a/test/ruby/test_flip.rb b/test/ruby/test_flip.rb
index b8b05aed6d..9c58f5f497 100644
--- a/test/ruby/test_flip.rb
+++ b/test/ruby/test_flip.rb
@@ -19,7 +19,7 @@ class TestFlip < Test::Unit::TestCase
assert_nothing_raised(NotImplementedError, bug6899) do
2000.times {eval %[(foo..bar) ? 1 : 2]}
end
- foo = bar
+ [foo, bar]
end
def test_shared_eval
@@ -28,6 +28,7 @@ class TestFlip < Test::Unit::TestCase
vs.select {|n| if n==2..n==16 then 1 end}
v = eval("vs.select {|n| if n==3..n==6 then 1 end}")
assert_equal([*3..6], v, bug7671)
+ vs
end
def test_shared_thread
diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb
index 7fabfd351d..d0d180593a 100644
--- a/test/ruby/test_float.rb
+++ b/test/ruby/test_float.rb
@@ -129,9 +129,9 @@ class TestFloat < Test::Unit::TestCase
assert_in_delta(a, 0, Float::EPSILON)
a = Float("-.0")
assert_in_delta(a, 0, Float::EPSILON)
- assert_raise(ArgumentError){Float("0.")}
- assert_raise(ArgumentError){Float("+0.")}
- assert_raise(ArgumentError){Float("-0.")}
+ assert_equal(0.0, Float("0."))
+ assert_equal(0.0, Float("+0."))
+ assert_equal(0.0, Float("-0."))
assert_raise(ArgumentError){Float(".")}
assert_raise(ArgumentError){Float("+")}
assert_raise(ArgumentError){Float("+.")}
@@ -139,9 +139,12 @@ class TestFloat < Test::Unit::TestCase
assert_raise(ArgumentError){Float("-.")}
assert_raise(ArgumentError){Float("1e")}
assert_raise(ArgumentError){Float("1__1")}
- assert_raise(ArgumentError){Float("1.")}
- assert_raise(ArgumentError){Float("1.e+00")}
- assert_raise(ArgumentError){Float("0x1.p+0")}
+ assert_equal(1.0, Float("1."))
+ assert_equal(1.0, Float("1.e+00"))
+ assert_equal(0.0625, Float("0x.1"))
+ assert_equal(1.0, Float("0x1."))
+ assert_equal(1.0, Float("0x1.0"))
+ assert_equal(1.0, Float("0x1.p+0"))
# add expected behaviour here.
assert_equal(10, Float("1_0"))
@@ -171,6 +174,24 @@ class TestFloat < Test::Unit::TestCase
assert_raise(ArgumentError, n += z + "A") {Float(n)}
assert_raise(ArgumentError, n += z + ".0") {Float(n)}
end
+
+ x = nil
+ 2000.times do
+ x = Float("0x"+"0"*30)
+ break unless x == 0.0
+ end
+ assert_equal(0.0, x, ->{"%a" % x})
+ x = nil
+ 2000.times do
+ begin
+ x = Float("0x1."+"0"*270)
+ rescue ArgumentError => e
+ raise unless /"0x1\.0{270}"/ =~ e.message
+ else
+ break
+ end
+ end
+ assert_equal(1.0, x, ->{"%a" % x})
end
def test_divmod
@@ -206,6 +227,12 @@ class TestFloat < Test::Unit::TestCase
assert_equal(-3.5, (-11.5).remainder(-4))
assert_predicate(Float::NAN.remainder(4), :nan?)
assert_predicate(4.remainder(Float::NAN), :nan?)
+
+ ten = Object.new
+ def ten.coerce(other)
+ [other, 10]
+ end
+ assert_equal(4, 14.0.remainder(ten))
end
def test_to_s
@@ -305,6 +332,7 @@ class TestFloat < Test::Unit::TestCase
assert_equal(1.0, 1.0 ** (2**32))
assert_equal(1.0, 1.0 ** 1.0)
assert_raise(TypeError) { 1.0 ** nil }
+ assert_equal(9.0, 3.0 ** 2)
end
def test_eql
@@ -464,6 +492,17 @@ class TestFloat < Test::Unit::TestCase
assert_equal(-1.26, -1.255.round(2))
end
+ def test_round_half_even_with_precision
+ assert_equal(767573.18759, 767573.1875850001.round(5, half: :even))
+ assert_equal(767573.18758, 767573.187585.round(5, half: :even))
+ assert_equal(767573.18758, 767573.1875849998.round(5, half: :even))
+ assert_equal(767573.18758, 767573.187575.round(5, half: :even))
+ assert_equal(-767573.18759, -767573.1875850001.round(5, half: :even))
+ assert_equal(-767573.18758, -767573.187585.round(5, half: :even))
+ assert_equal(-767573.18758, -767573.1875849998.round(5, half: :even))
+ assert_equal(-767573.18758, -767573.187575.round(5, half: :even))
+ end
+
def test_floor_with_precision
assert_equal(+0.0, +0.001.floor(1))
assert_equal(-0.1, -0.001.floor(1))
@@ -491,6 +530,10 @@ class TestFloat < Test::Unit::TestCase
assert_raise(TypeError) {1.0.floor(nil)}
def (prec = Object.new).to_int; 2; end
assert_equal(0.99, 0.998.floor(prec))
+
+ assert_equal(-10000000000, -1.0.floor(-10), "[Bug #20654]")
+ assert_equal(-100000000000000000000, -1.0.floor(-20), "[Bug #20654]")
+ assert_equal(-100000000000000000000000000000000000000000000000000, -1.0.floor(-50), "[Bug #20654]")
end
def test_ceil_with_precision
@@ -518,6 +561,10 @@ class TestFloat < Test::Unit::TestCase
assert_raise(TypeError) {1.0.ceil(nil)}
def (prec = Object.new).to_int; 2; end
assert_equal(0.99, 0.981.ceil(prec))
+
+ assert_equal(10000000000, 1.0.ceil(-10), "[Bug #20654]")
+ assert_equal(100000000000000000000, 1.0.ceil(-20), "[Bug #20654]")
+ assert_equal(100000000000000000000000000000000000000000000000000, 1.0.ceil(-50), "[Bug #20654]")
end
def test_truncate_with_precision
@@ -764,6 +811,9 @@ class TestFloat < Test::Unit::TestCase
assert_raise_with_message(ArgumentError, /xxx/) {
1.0.round(half: "\0xxx")
}
+ assert_raise_with_message(Encoding::CompatibilityError, /ASCII incompatible/) {
+ 1.0.round(half: "up".force_encoding("utf-16be"))
+ }
end
def test_Float
@@ -783,17 +833,37 @@ class TestFloat < Test::Unit::TestCase
assert_equal(15, Float('0xf'))
assert_equal(15, Float('0xfp0'))
assert_raise(ArgumentError) { Float('0xfp') }
- assert_raise(ArgumentError) { Float('0xf.') }
+ assert_equal(15, Float('0xf.'))
assert_raise(ArgumentError) { Float('0xf.p') }
- assert_raise(ArgumentError) { Float('0xf.p0') }
- assert_raise(ArgumentError) { Float('0xf.f') }
+ assert_equal(15, Float('0xf.p0'))
+ assert_equal(15.9375, Float('0xf.f'))
assert_raise(ArgumentError) { Float('0xf.fp') }
- assert_equal(Float::INFINITY, Float('0xf.fp1000000000000000'))
+ assert_equal(0x10a, Float("0x1_0a"))
+ assert_equal(1.625, Float("0x1.a_0"))
+ assert_equal(3.25, Float("0x1.ap0_1"))
+ assert_raise(ArgumentError) { Float("0x1.ap0a") }
+ begin
+ verbose_bak, $VERBOSE = $VERBOSE, nil
+ assert_equal(Float::INFINITY, Float('0xf.fp1000000000000000'))
+ ensure
+ $VERBOSE = verbose_bak
+ end
assert_equal(1, suppress_warning {Float("1e10_00")}.infinite?)
assert_raise(TypeError) { Float(nil) }
+ assert_raise(TypeError) { Float(:test) }
o = Object.new
def o.to_f; inf = Float::INFINITY; inf/inf; end
assert_predicate(Float(o), :nan?)
+
+ assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-16be"))}
+ assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-16le"))}
+ assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-32be"))}
+ assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-32le"))}
+ assert_raise(Encoding::CompatibilityError) {Float("0".encode("iso-2022-jp"))}
+
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(ArgumentError, /\u{1f4a1}/) {Float("\u{1f4a1}")}
+ end
end
def test_invalid_str
@@ -801,6 +871,49 @@ class TestFloat < Test::Unit::TestCase
assert_raise(ArgumentError, bug4310) {under_gc_stress {Float('a'*10000)}}
end
+ def test_Float_with_invalid_exception
+ assert_raise(ArgumentError) {
+ Float("0", exception: 1)
+ }
+ end
+
+ def test_Float_with_exception_keyword
+ assert_raise(ArgumentError) {
+ Float(".", exception: true)
+ }
+ assert_nothing_raised(ArgumentError) {
+ assert_equal(nil, Float(".", exception: false))
+ }
+ assert_raise(RangeError) {
+ Float(1i, exception: true)
+ }
+ assert_nothing_raised(RangeError) {
+ assert_equal(nil, Float(1i, exception: false))
+ }
+ assert_raise(TypeError) {
+ Float(nil, exception: true)
+ }
+ assert_nothing_raised(TypeError) {
+ assert_equal(nil, Float(nil, exception: false))
+ }
+ assert_nothing_raised(TypeError) {
+ assert_equal(nil, Float(:test, exception: false))
+ }
+ assert_nothing_raised(TypeError) {
+ assert_equal(nil, Float(Object.new, exception: false))
+ }
+ assert_nothing_raised(TypeError) {
+ o = Object.new
+ def o.to_f; 3.14; end
+ assert_equal(3.14, Float(o, exception: false))
+ }
+ assert_nothing_raised(RuntimeError) {
+ o = Object.new
+ def o.to_f; raise; end
+ assert_equal(nil, Float(o, exception: false))
+ }
+ end
+
def test_num2dbl
assert_raise(ArgumentError, "comparison of String with 0 failed") do
1.0.step(2.0, "0.5") {}
@@ -829,6 +942,11 @@ class TestFloat < Test::Unit::TestCase
end
assert_equal([5.0, 4.0, 3.0, 2.0], 5.0.step(1.5, -1).to_a)
+
+ assert_equal(11, ((0.24901079128550474)..(340.2500808898068)).step(34.00010700985213).to_a.size)
+ assert_equal(11, ((0.24901079128550474)..(340.25008088980684)).step(34.00010700985213).to_a.size)
+ assert_equal(11, ((-0.24901079128550474)..(-340.2500808898068)).step(-34.00010700985213).to_a.size)
+ assert_equal(11, ((-0.24901079128550474)..(-340.25008088980684)).step(-34.00010700985213).to_a.size)
end
def test_step2
@@ -840,7 +958,9 @@ class TestFloat < Test::Unit::TestCase
a = rand
b = a+rand*1000
s = (b - a) / 10
- assert_equal(10, (a...b).step(s).to_a.length)
+ b = a + s*9.999999
+ seq = (a...b).step(s)
+ assert_equal(10, seq.to_a.length, seq.inspect)
end
assert_equal([1.0, 2.9, 4.8, 6.699999999999999], (1.0...6.8).step(1.9).to_a)
@@ -849,6 +969,11 @@ class TestFloat < Test::Unit::TestCase
(1.0 ... e).step(1E-16) do |n|
assert_operator(n, :<=, e)
end
+
+ assert_equal(10, ((0.24901079128550474)...(340.2500808898068)).step(34.00010700985213).to_a.size)
+ assert_equal(11, ((0.24901079128550474)...(340.25008088980684)).step(34.00010700985213).to_a.size)
+ assert_equal(10, ((-0.24901079128550474)...(-340.2500808898068)).step(-34.00010700985213).to_a.size)
+ assert_equal(11, ((-0.24901079128550474)...(-340.25008088980684)).step(-34.00010700985213).to_a.size)
end
def test_singleton_method
diff --git a/test/ruby/test_fnmatch.rb b/test/ruby/test_fnmatch.rb
index 30250b5a19..16f1076e48 100644
--- a/test/ruby/test_fnmatch.rb
+++ b/test/ruby/test_fnmatch.rb
@@ -10,6 +10,7 @@ class TestFnmatch < Test::Unit::TestCase
assert_equal(t.include?(i.chr), !File.fnmatch("[!#{s}]", i.chr, File::FNM_DOTMATCH))
end
end
+
def test_fnmatch
assert_file.for("[ruby-dev:22819]").fnmatch('\[1\]' , '[1]')
assert_file.for("[ruby-dev:22815]").fnmatch('*?', 'a')
@@ -17,10 +18,16 @@ class TestFnmatch < Test::Unit::TestCase
assert_file.fnmatch('\[1\]' , '[1]', File::FNM_PATHNAME)
assert_file.fnmatch('*?', 'a', File::FNM_PATHNAME)
assert_file.fnmatch('*/', 'a/', File::FNM_PATHNAME)
+ end
+
+ def test_text
# text
assert_file.fnmatch('cat', 'cat')
assert_file.not_fnmatch('cat', 'category')
assert_file.not_fnmatch('cat', 'wildcat')
+ end
+
+ def test_any_one
# '?' matches any one character
assert_file.fnmatch('?at', 'cat')
assert_file.fnmatch('c?t', 'cat')
@@ -29,6 +36,9 @@ class TestFnmatch < Test::Unit::TestCase
assert_file.not_fnmatch('c??t', 'cat')
assert_file.not_fnmatch('??at', 'cat')
assert_file.not_fnmatch('ca??', 'cat')
+ end
+
+ def test_any_chars
# '*' matches any number (including 0) of any characters
assert_file.fnmatch('c*', 'cats')
assert_file.fnmatch('c*ts', 'cats')
@@ -40,9 +50,15 @@ class TestFnmatch < Test::Unit::TestCase
assert_file.not_fnmatch('a*abc', 'abc')
assert_file.fnmatch('a*bc', 'abc')
assert_file.not_fnmatch('a*bc', 'abcd')
+ end
+
+ def test_char_class
# [seq] : matches any character listed between bracket
# [!seq] or [^seq] : matches any character except those listed between bracket
bracket_test("bd-gikl-mosv-x", "bdefgiklmosvwx")
+ end
+
+ def test_escape
# escaping character
assert_file.fnmatch('\?', '?')
assert_file.not_fnmatch('\?', '\?')
@@ -59,6 +75,9 @@ class TestFnmatch < Test::Unit::TestCase
assert_file.fnmatch('[a\-c]', 'c')
assert_file.not_fnmatch('[a\-c]', 'b')
assert_file.not_fnmatch('[a\-c]', '\\')
+ end
+
+ def test_fnm_escape
# escaping character loses its meaning if FNM_NOESCAPE is set
assert_file.not_fnmatch('\?', '?', File::FNM_NOESCAPE)
assert_file.fnmatch('\?', '\?', File::FNM_NOESCAPE)
@@ -75,6 +94,9 @@ class TestFnmatch < Test::Unit::TestCase
assert_file.fnmatch('[a\-c]', 'c', File::FNM_NOESCAPE)
assert_file.fnmatch('[a\-c]', 'b', File::FNM_NOESCAPE) # '\\' < 'b' < 'c'
assert_file.fnmatch('[a\-c]', '\\', File::FNM_NOESCAPE)
+ end
+
+ def test_fnm_casefold
# case is ignored if FNM_CASEFOLD is set
assert_file.not_fnmatch('cat', 'CAT')
assert_file.fnmatch('cat', 'CAT', File::FNM_CASEFOLD)
@@ -82,11 +104,17 @@ class TestFnmatch < Test::Unit::TestCase
assert_file.fnmatch('[a-z]', 'D', File::FNM_CASEFOLD)
assert_file.not_fnmatch('[abc]', 'B')
assert_file.fnmatch('[abc]', 'B', File::FNM_CASEFOLD)
+ end
+
+ def test_fnm_pathname
# wildcard doesn't match '/' if FNM_PATHNAME is set
assert_file.fnmatch('foo?boo', 'foo/boo')
assert_file.fnmatch('foo*', 'foo/boo')
assert_file.not_fnmatch('foo?boo', 'foo/boo', File::FNM_PATHNAME)
assert_file.not_fnmatch('foo*', 'foo/boo', File::FNM_PATHNAME)
+ end
+
+ def test_fnm_dotmatch
# wildcard matches leading period if FNM_DOTMATCH is set
assert_file.not_fnmatch('*', '.profile')
assert_file.fnmatch('*', '.profile', File::FNM_DOTMATCH)
@@ -95,6 +123,9 @@ class TestFnmatch < Test::Unit::TestCase
assert_file.fnmatch('*/*', 'dave/.profile')
assert_file.not_fnmatch('*/*', 'dave/.profile', File::FNM_PATHNAME)
assert_file.fnmatch('*/*', 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH)
+ end
+
+ def test_recursive
# recursive matching
assert_file.fnmatch('**/foo', 'a/b/c/foo', File::FNM_PATHNAME)
assert_file.fnmatch('**/foo', '/foo', File::FNM_PATHNAME)
diff --git a/test/ruby/test_frozen.rb b/test/ruby/test_frozen.rb
new file mode 100644
index 0000000000..6721cb1128
--- /dev/null
+++ b/test/ruby/test_frozen.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestFrozen < Test::Unit::TestCase
+ def test_setting_ivar_on_frozen_obj
+ obj = Object.new
+ obj.freeze
+ assert_raise(FrozenError) { obj.instance_variable_set(:@a, 1) }
+ end
+
+ def test_setting_ivar_on_frozen_obj_with_ivars
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ obj.freeze
+ assert_raise(FrozenError) { obj.instance_variable_set(:@b, 1) }
+ end
+
+ def test_setting_ivar_on_frozen_string
+ str = "str"
+ str.freeze
+ assert_raise(FrozenError) { str.instance_variable_set(:@a, 1) }
+ end
+
+ def test_setting_ivar_on_frozen_string_with_ivars
+ str = "str"
+ str.instance_variable_set(:@a, 1)
+ 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_frozen_error.rb b/test/ruby/test_frozen_error.rb
new file mode 100644
index 0000000000..ace1e4c775
--- /dev/null
+++ b/test/ruby/test_frozen_error.rb
@@ -0,0 +1,57 @@
+require 'test/unit'
+
+class TestFrozenError < Test::Unit::TestCase
+ def test_new_default
+ exc = FrozenError.new
+ assert_equal("FrozenError", exc.message)
+ assert_raise_with_message(ArgumentError, "no receiver is available") {
+ exc.receiver
+ }
+ end
+
+ def test_new_message
+ exc = FrozenError.new("bar")
+ assert_equal("bar", exc.message)
+ assert_raise_with_message(ArgumentError, "no receiver is available") {
+ exc.receiver
+ }
+ end
+
+ def test_new_receiver
+ obj = Object.new
+ exc = FrozenError.new("bar", receiver: obj)
+ assert_equal("bar", exc.message)
+ assert_same(obj, exc.receiver)
+ end
+
+ def test_message
+ obj = Object.new.freeze
+ e = assert_raise_with_message(FrozenError, /can't modify frozen #{obj.class}/) {
+ obj.instance_variable_set(:@test, true)
+ }
+ assert_include(e.message, obj.inspect)
+
+ klass = Class.new do
+ def init
+ @x = true
+ end
+ def inspect
+ init
+ super
+ end
+ end
+ obj = klass.new.freeze
+ e = assert_raise_with_message(FrozenError, /can't modify frozen #{obj.class}/) {
+ obj.init
+ }
+ assert_include(e.message, klass.inspect)
+ end
+
+ def test_receiver
+ obj = Object.new.freeze
+ e = assert_raise(FrozenError) {def obj.foo; end}
+ assert_same(obj, e.receiver)
+ e = assert_raise(FrozenError) {obj.singleton_class.const_set(:A, 2)}
+ assert_same(obj.singleton_class, e.receiver)
+ end
+end
diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb
index b3528ddacd..09199c34b1 100644
--- a/test/ruby/test_gc.rb
+++ b/test/ruby/test_gc.rb
@@ -17,6 +17,7 @@ class TestGc < Test::Unit::TestCase
1.upto(10000) {
tmp = [0,1,2,3,4,5,6,7,8,9]
}
+ tmp
end
l=nil
100000.times {
@@ -39,21 +40,98 @@ class TestGc < Test::Unit::TestCase
end
def test_enable_disable
+ EnvUtil.without_gc do
+ GC.enable
+ assert_equal(false, GC.enable)
+ assert_equal(false, GC.disable)
+ assert_equal(true, GC.disable)
+ assert_equal(true, GC.disable)
+ assert_nil(GC.start)
+ assert_equal(true, GC.enable)
+ assert_equal(false, GC.enable)
+ end
+ end
+
+ def test_gc_config_full_mark_by_default
+ config = GC.config
+ assert_not_empty(config)
+ assert_true(config[:rgengc_allow_full_mark])
+ end
+
+ def test_gc_config_invalid_args
+ assert_raise(ArgumentError) { GC.config(0) }
+ end
+
+ def test_gc_config_setting_returns_updated_config_hash
+ old_value = GC.config[:rgengc_allow_full_mark]
+ assert_true(old_value)
+
+ new_value = GC.config(rgengc_allow_full_mark: false)[:rgengc_allow_full_mark]
+ assert_false(new_value)
+ new_value = GC.config(rgengc_allow_full_mark: nil)[:rgengc_allow_full_mark]
+ assert_false(new_value)
+ ensure
+ GC.config(rgengc_allow_full_mark: old_value)
+ GC.start
+ end
+
+ 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
GC.enable
- assert_equal(false, GC.enable)
- assert_equal(false, GC.disable)
- assert_equal(true, GC.disable)
- assert_equal(true, GC.disable)
+ GC.start
+
+ GC.config(rgengc_allow_full_mark: false)
+ major_count = GC.stat[:major_gc_count]
+ minor_count = GC.stat[:minor_gc_count]
+
+ arr = []
+ (GC.stat_heap[0][:heap_eden_slots] * 2).times do
+ arr << Object.new
+ Object.new
+ end
+
+ assert_equal(major_count, GC.stat[:major_gc_count])
+ assert_operator(minor_count, :<=, GC.stat[:minor_gc_count])
assert_nil(GC.start)
- assert_equal(true, GC.enable)
- assert_equal(false, GC.enable)
ensure
- GC.enable
+ GC.config(rgengc_allow_full_mark: true)
+ GC.start
+ end
+
+ def test_gc_config_disable_major_gc_start_always_works
+ GC.config(full_mark: false)
+
+ major_count = GC.stat[:major_gc_count]
+ GC.start
+
+ assert_operator(major_count, :<, GC.stat[:major_gc_count])
+ ensure
+ GC.config(full_mark: true)
+ GC.start
+ end
+
+ def test_gc_config_implementation
+ omit unless /darwin|linux/.match(RUBY_PLATFORM)
+
+ gc_name = (ENV['RUBY_GC_LIBRARY'] || "default")
+ assert_equal gc_name, GC.config[:implementation]
+ end
+
+ def test_gc_config_implementation_is_readonly
+ omit unless /darwin|linux/.match(RUBY_PLATFORM)
+
+ assert_raise(ArgumentError) { GC.config(implementation: "somethingelse") }
end
def test_start_full_mark
return unless use_rgengc?
+ omit 'stress' if GC.stress
+ 3.times { GC.start } # full mark and next time it should be minor mark
GC.start(full_mark: false)
assert_nil GC.latest_gc_info(:major_by)
@@ -62,6 +140,8 @@ class TestGc < Test::Unit::TestCase
end
def test_start_immediate_sweep
+ omit 'stress' if GC.stress
+
GC.start(immediate_sweep: false)
assert_equal false, GC.latest_gc_info(:immediate_sweep)
@@ -87,16 +167,23 @@ class TestGc < Test::Unit::TestCase
assert_kind_of(Integer, res[:count])
stat, count = {}, {}
- GC.start
- GC.stat(stat)
- ObjectSpace.count_objects(count)
+ 2.times{ # to ignore const cache imemo creation
+ GC.start
+ GC.stat(stat)
+ ObjectSpace.count_objects(count)
+ # repeat same methods invocation for cache object creation.
+ GC.stat(stat)
+ ObjectSpace.count_objects(count)
+ }
assert_equal(count[:TOTAL]-count[:FREE], stat[:heap_live_slots])
assert_equal(count[:FREE], stat[:heap_free_slots])
# measure again without GC.start
- 1000.times{ "a" + "b" }
- GC.stat(stat)
- ObjectSpace.count_objects(count)
+ 2.times{ # to ignore const cache imemo creation
+ 1000.times{ "a" + "b" }
+ GC.stat(stat)
+ ObjectSpace.count_objects(count)
+ }
assert_equal(count[:FREE], stat[:heap_free_slots])
end
@@ -105,36 +192,149 @@ class TestGc < Test::Unit::TestCase
end
def test_stat_single
+ omit 'stress' if GC.stress
+
stat = GC.stat
assert_equal stat[:count], GC.stat(:count)
assert_raise(ArgumentError){ GC.stat(:invalid) }
end
def test_stat_constraints
+ omit 'stress' if GC.stress
+
stat = GC.stat
+ # marking_time + sweeping_time could differ from time by 1 because they're stored in nanoseconds
+ assert_in_delta stat[:time], stat[:marking_time] + stat[:sweeping_time], 1
assert_equal stat[:total_allocated_pages], stat[:heap_allocated_pages] + stat[:total_freed_pages]
- assert_operator stat[:heap_sorted_length], :>=, stat[:heap_eden_pages] + stat[:heap_allocatable_pages], "stat is: " + stat.inspect
assert_equal stat[:heap_available_slots], stat[:heap_live_slots] + stat[:heap_free_slots] + stat[:heap_final_slots]
assert_equal stat[:heap_live_slots], stat[:total_allocated_objects] - stat[:total_freed_objects] - stat[:heap_final_slots]
- assert_equal stat[:heap_allocated_pages], stat[:heap_eden_pages] + stat[:heap_tomb_pages]
+ assert_equal stat[:heap_allocated_pages], stat[:heap_eden_pages] + stat[:heap_empty_pages]
if use_rgengc?
assert_equal stat[:count], stat[:major_gc_count] + stat[:minor_gc_count]
end
end
+ def test_stat_heap
+ omit 'stress' if GC.stress
+
+ stat_heap = {}
+ stat = {}
+ # Initialize to prevent GC in future calls
+ GC.stat_heap(0, stat_heap)
+ GC.stat(stat)
+
+ GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times do |i|
+ EnvUtil.without_gc do
+ GC.stat_heap(i, stat_heap)
+ GC.stat(stat)
+ 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
+ assert_operator stat_heap[:force_major_gc_count], :>=, 0
+ assert_operator stat_heap[:force_incremental_marking_finish_count], :>=, 0
+ assert_operator stat_heap[:total_allocated_objects], :>=, 0
+ assert_operator stat_heap[:total_freed_objects], :>=, 0
+ assert_operator stat_heap[:total_freed_objects], :<=, stat_heap[:total_allocated_objects]
+ end
+
+ GC.stat_heap(0, stat_heap)
+ assert_equal stat_heap[:slot_size], GC.stat_heap(0, :slot_size)
+ assert_equal stat_heap[:slot_size], GC.stat_heap(0)[:slot_size]
+
+ assert_raise(ArgumentError) { GC.stat_heap(-1) }
+ assert_raise(ArgumentError) { GC.stat_heap(GC::INTERNAL_CONSTANTS[:HEAP_COUNT]) }
+ end
+
+ def test_stat_heap_all
+ stat_heap_all = {}
+ stat_heap = {}
+ # Initialize to prevent GC in future calls
+ GC.stat_heap(0, stat_heap)
+ GC.stat_heap(nil, stat_heap_all)
+
+ GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times do |i|
+ GC.stat_heap(nil, stat_heap_all)
+ GC.stat_heap(i, stat_heap)
+
+ # Remove keys that can vary between invocations
+ %i(total_allocated_objects heap_live_slots heap_free_slots).each do |sym|
+ stat_heap[sym] = stat_heap_all[i][sym] = 0
+ end
+
+ assert_equal stat_heap, stat_heap_all[i]
+ end
+
+ assert_raise(TypeError) { GC.stat_heap(nil, :slot_size) }
+ end
+
+ def test_stat_heap_constraints
+ omit 'stress' if GC.stress
+
+ stat = GC.stat
+ stat_heap = GC.stat_heap
+ 2.times do
+ GC.stat(stat)
+ GC.stat_heap(nil, stat_heap)
+ end
+
+ stat_heap_sum = Hash.new(0)
+ stat_heap.values.each do |hash|
+ 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]
+ assert_equal stat[:total_freed_objects], stat_heap_sum[:total_freed_objects]
+ end
+
+ def test_measure_total_time
+ assert_separately([], __FILE__, __LINE__, <<~RUBY, timeout: 60)
+ GC.measure_total_time = false
+
+ time_before = GC.stat(:time)
+
+ # Generate some garbage
+ Random.new.bytes(100 * 1024 * 1024)
+ GC.start
+
+ time_after = GC.stat(:time)
+
+ # If time measurement is disabled, the time stat should not change
+ assert_equal time_before, time_after
+ RUBY
+ end
+
def test_latest_gc_info
- assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom'
- GC.start
- count = GC.stat(:heap_free_slots) + GC.stat(:heap_allocatable_pages) * GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]
- count.times{ "a" + "b" }
- assert_equal :newobj, GC.latest_gc_info[:gc_by]
- eom
+ omit 'stress' if GC.stress
+
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ GC.start
+ count = GC.stat(:heap_free_slots) + GC.stat(:heap_allocatable_slots)
+ count.times{ "a" + "b" }
+ assert_equal :newobj, GC.latest_gc_info[:gc_by]
+ RUBY
+ GC.latest_gc_info(h = {}) # allocate hash and rehearsal
+ GC.start
GC.start
- assert_equal :force, GC.latest_gc_info[:major_by] if use_rgengc?
- assert_equal :method, GC.latest_gc_info[:gc_by]
- assert_equal true, GC.latest_gc_info[:immediate_sweep]
+ GC.start
+ GC.latest_gc_info(h)
+
+ assert_equal :force, h[:major_by] if use_rgengc?
+ assert_equal :method, h[:gc_by]
+ assert_equal true, h[:immediate_sweep]
+ assert_equal true, h.key?(:need_major_by)
GC.stress = true
assert_equal :force, GC.latest_gc_info[:major_by]
@@ -152,8 +352,81 @@ class TestGc < Test::Unit::TestCase
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {GC.latest_gc_info(:"\u{30eb 30d3 30fc}")}
end
+ def test_latest_gc_info_need_major_by
+ return unless use_rgengc?
+ omit 'stress' if GC.stress
+
+ 3.times { GC.start }
+ assert_nil GC.latest_gc_info(:need_major_by)
+
+ EnvUtil.without_gc do
+ # allocate objects until need_major_by is set or major GC happens
+ objects = []
+ while GC.latest_gc_info(:need_major_by).nil?
+ objects.append(100.times.map { '*' })
+ GC.start(full_mark: false)
+ end
+
+ # We need to ensure that no GC gets ran before the call to GC.start since
+ # it would trigger a major GC. Assertions could allocate objects and
+ # trigger a GC so we don't run assertions until we perform the major GC.
+ need_major_by = GC.latest_gc_info(:need_major_by)
+ GC.start(full_mark: false) # should be upgraded to major
+ major_by = GC.latest_gc_info(:major_by)
+
+ assert_not_nil(need_major_by)
+ assert_not_nil(major_by)
+ end
+ end
+
+ def test_latest_gc_info_weak_references_count
+ assert_separately([], __FILE__, __LINE__, <<~RUBY)
+ GC.disable
+ COUNT = 10_000
+ # Some weak references may be created, so allow some margin of error
+ error_tolerance = 100
+
+ # Run full GC to collect stats about weak references
+ GC.start
+
+ before_weak_references_count = GC.latest_gc_info(:weak_references_count)
+
+ # 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)
+
+ before_weak_references_count = GC.latest_gc_info(: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 GC all the WeakMaps
+ GC.start
+
+ assert_operator(GC.latest_gc_info(:weak_references_count), :<=, before_weak_references_count - COUNT + error_tolerance)
+ RUBY
+ end
+
+ def test_stress_compile_send
+ assert_in_out_err([], <<-EOS, [], [], "")
+ GC.stress = true
+ begin
+ eval("A::B.c(1, 1, d: 234)")
+ rescue
+ end
+ EOS
+ end
+
def test_singleton_method
- assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "[ruby-dev:42832]")
+ assert_in_out_err([], <<-EOS, [], [], "[ruby-dev:42832]")
GC.stress = true
10.times do
obj = Object.new
@@ -165,7 +438,7 @@ class TestGc < Test::Unit::TestCase
end
def test_singleton_method_added
- assert_in_out_err(%w[--disable-gems], <<-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)
@@ -180,20 +453,17 @@ class TestGc < Test::Unit::TestCase
end
def test_gc_parameter
- env = {
- "RUBY_GC_MALLOC_LIMIT" => "60000000",
- "RUBY_GC_HEAP_INIT_SLOTS" => "100000"
- }
- assert_normal_exit("exit", "[ruby-core:39777]", :child_env => env)
+ env = {}
+ GC.stat_heap.keys.each do |heap|
+ env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = "200000"
+ end
+ assert_normal_exit("exit", "", :child_env => env)
- env = {
- "RUBYOPT" => "",
- "RUBY_GC_HEAP_INIT_SLOTS" => "100000"
- }
- assert_in_out_err([env, "-e", "exit"], "", [], [], "[ruby-core:39795]")
- assert_in_out_err([env, "-W0", "-e", "exit"], "", [], [], "[ruby-core:39795]")
- assert_in_out_err([env, "-W1", "-e", "exit"], "", [], [], "[ruby-core:39795]")
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_INIT_SLOTS=100000/, "[ruby-core:39795]")
+ env = {}
+ GC.stat_heap.keys.each do |heap|
+ env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = "0"
+ end
+ assert_normal_exit("exit", "", :child_env => env)
env = {
"RUBY_GC_HEAP_GROWTH_FACTOR" => "2.0",
@@ -203,22 +473,13 @@ class TestGc < Test::Unit::TestCase
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_FACTOR=2.0/, "")
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_MAX_SLOTS=10000/, "[ruby-core:57928]")
- env = {
- "RUBY_GC_HEAP_INIT_SLOTS" => "100000",
- "RUBY_GC_HEAP_FREE_SLOTS" => "10000",
- "RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR" => "0.9",
- }
- assert_normal_exit("exit", "", :child_env => env)
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR=0\.9/, "")
-
- # always full GC when RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR < 1.0
- assert_in_out_err([env, "-e", "1000_000.times{Object.new}; p(GC.stat[:minor_gc_count] < GC.stat[:major_gc_count])"], "", ['true'], //, "") if use_rgengc?
-
- # check obsolete
- assert_in_out_err([{'RUBY_FREE_MIN' => '100'}, '-w', '-eexit'], '', [],
- /RUBY_FREE_MIN is obsolete. Use RUBY_GC_HEAP_FREE_SLOTS instead/)
- assert_in_out_err([{'RUBY_HEAP_MIN_SLOTS' => '100'}, '-w', '-eexit'], '', [],
- /RUBY_HEAP_MIN_SLOTS is obsolete. Use RUBY_GC_HEAP_INIT_SLOTS instead/)
+ if use_rgengc?
+ env = {
+ "RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR" => "0.4",
+ }
+ # always full GC when RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR < 1.0
+ assert_in_out_err([env, "-e", "GC.start; 1000_000.times{Object.new}; p(GC.stat[:minor_gc_count] < GC.stat[:major_gc_count])"], "", ['true'], //, "")
+ end
env = {
"RUBY_GC_MALLOC_LIMIT" => "60000000",
@@ -241,6 +502,55 @@ class TestGc < Test::Unit::TestCase
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_MAX=16000000/, "")
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR=2.0/, "")
end
+
+ ["0.01", "0.1", "1.0"].each do |i|
+ env = {"RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR" => "0", "RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO" => i}
+ assert_separately([env, "-W0"], __FILE__, __LINE__, <<~RUBY)
+ GC.disable
+ GC.start
+ assert_equal((GC.stat[:old_objects] * #{i}).to_i, GC.stat[:remembered_wb_unprotected_objects_limit])
+ RUBY
+ end
+ end
+
+ def test_gc_parameter_init_slots
+ omit "[Bug #21203] This test is flaky and intermittently failing now"
+
+ assert_separately([], __FILE__, __LINE__, <<~RUBY, timeout: 60)
+ # Constant from gc.c.
+ GC_HEAP_INIT_SLOTS = 10_000
+
+ gc_count = GC.stat(:count)
+ # Fill up all of the size pools to the init slots
+ GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times do |i|
+ capa = (GC.stat_heap(i, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] - (2 * RbConfig::SIZEOF["void*"])) / RbConfig::SIZEOF["void*"]
+ while GC.stat_heap(i, :heap_eden_slots) < GC_HEAP_INIT_SLOTS
+ Array.new(capa)
+ end
+ end
+
+ assert_equal gc_count, GC.stat(:count)
+ RUBY
+
+ env = {}
+ sizes = GC.stat_heap.keys.reverse.map { 20_000 }
+ GC.stat_heap.keys.each do |heap|
+ env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = sizes[heap].to_s
+ end
+ assert_separately([env, "-W0"], __FILE__, __LINE__, <<~RUBY, timeout: 60)
+ SIZES = #{sizes}
+
+ gc_count = GC.stat(:count)
+ # Fill up all of the size pools to the init slots
+ GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times do |i|
+ capa = (GC.stat_heap(i, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] - (2 * RbConfig::SIZEOF["void*"])) / RbConfig::SIZEOF["void*"]
+ while GC.stat_heap(i, :heap_eden_slots) < SIZES[i]
+ Array.new(capa)
+ end
+ end
+
+ assert_equal gc_count, GC.stat(:count)
+ RUBY
end
def test_profiler_enabled
@@ -253,19 +563,28 @@ class TestGc < Test::Unit::TestCase
end
def test_profiler_clear
- assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom', timeout: 30
- GC::Profiler.enable
+ omit "for now"
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY', timeout: 30)
+ GC::Profiler.enable
- GC.start
- assert_equal(1, GC::Profiler.raw_data.size)
- GC::Profiler.clear
- assert_equal(0, GC::Profiler.raw_data.size)
+ GC.start
+ assert_equal(1, GC::Profiler.raw_data.size)
+ GC::Profiler.clear
+ assert_equal(0, GC::Profiler.raw_data.size)
- 200.times{ GC.start }
- assert_equal(200, GC::Profiler.raw_data.size)
- GC::Profiler.clear
- assert_equal(0, GC::Profiler.raw_data.size)
- eom
+ 200.times{ GC.start }
+ assert_equal(200, GC::Profiler.raw_data.size)
+ GC::Profiler.clear
+ assert_equal(0, GC::Profiler.raw_data.size)
+ RUBY
+ end
+
+ def test_profiler_raw_data
+ GC::Profiler.enable
+ GC.start
+ assert GC::Profiler.raw_data
+ ensure
+ GC::Profiler.disable
end
def test_profiler_total_time
@@ -279,39 +598,110 @@ class TestGc < Test::Unit::TestCase
end
def test_finalizing_main_thread
- assert_in_out_err(%w[--disable-gems], <<-EOS, ["\"finalize\""], [], "[ruby-dev:46647]")
+ assert_in_out_err([], <<-EOS, ["\"finalize\""], [], "[ruby-dev:46647]")
ObjectSpace.define_finalizer(Thread.main) { p 'finalize' }
EOS
end
def test_expand_heap
- assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom'
- GC.start
- base_length = GC.stat[:heap_eden_pages]
- (base_length * 500).times{ 'a' }
- GC.start
- base_length = GC.stat[:heap_eden_pages]
- (base_length * 500).times{ 'a' }
- GC.start
- assert_in_epsilon base_length, (v = GC.stat[:heap_eden_pages]), 1/8r,
- "invalid heap expanding (base_length: #{base_length}, GC.stat[:heap_eden_pages]: #{v})"
+ assert_separately([], __FILE__, __LINE__, <<~'RUBY')
+ GC.start
+ base_length = GC.stat[:heap_eden_pages]
+ (base_length * 500).times{ 'a' }
+ GC.start
+ base_length = GC.stat[:heap_eden_pages]
+ (base_length * 500).times{ 'a' }
+ GC.start
+ assert_in_epsilon base_length, (v = GC.stat[:heap_eden_pages]), 1/8r,
+ "invalid heap expanding (base_length: #{base_length}, GC.stat[:heap_eden_pages]: #{v})"
- a = []
- (base_length * 500).times{ a << 'a'; nil }
- GC.start
- assert_operator base_length, :<, GC.stat[:heap_eden_pages] + 1
- eom
+ a = []
+ (base_length * 500).times{ a << 'a'; nil }
+ GC.start
+ assert_operator base_length, :<, GC.stat[:heap_eden_pages] + 1
+ RUBY
+ end
+
+ def test_thrashing_for_young_objects
+ # This test prevents bugs like [Bug #18929]
+
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY', timeout: 60)
+ # Grow the heap
+ @ary = 100_000.times.map { Object.new }
+
+ # Warmup to make sure heap stabilizes
+ 1_000_000.times { Object.new }
+
+ # We need to pre-allocate all the hashes for GC.stat calls, because
+ # otherwise the call to GC.stat/GC.stat_heap itself could cause a new
+ # page to be allocated and the before/after assertions will fail
+ before_stats = {}
+ after_stats = {}
+ # stat_heap needs a hash of hashes for each heap; easiest way to get the
+ # right shape for that is just to call stat_heap with no argument
+ before_stat_heap = GC.stat_heap
+ after_stat_heap = GC.stat_heap
+
+ # Now collect the actual stats
+ GC.stat before_stats
+ GC.stat_heap nil, before_stat_heap
+
+ 1_000_000.times { Object.new }
+
+ # Previous loop may have caused GC to be in an intermediate state,
+ # running a minor GC here will guarantee that GC will be complete
+ GC.start(full_mark: false)
+
+ GC.stat after_stats
+ GC.stat_heap nil, after_stat_heap
+
+ # Debugging output to for failures in trunk-repeat50@phosphorus-docker
+ debug_msg = "before_stats: #{before_stats}\nbefore_stat_heap: #{before_stat_heap}\nafter_stats: #{after_stats}\nafter_stat_heap: #{after_stat_heap}"
+
+ # Should not be thrashing in page creation
+ assert_equal before_stats[:heap_allocated_pages], after_stats[:heap_allocated_pages], debug_msg
+ assert_equal 0, after_stats[:total_freed_pages], debug_msg
+ # Only young objects, so should not trigger major GC
+ assert_equal before_stats[:major_gc_count], after_stats[:major_gc_count], debug_msg
+ RUBY
+ end
+
+ def test_heaps_grow_independently
+ # [Bug #21214]
+
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY', timeout: 60)
+ COUNT = 1_000_000
+
+ def allocate_small_object = []
+ def allocate_large_object = Array.new(10)
+
+ @arys = Array.new(COUNT) do
+ # Allocate 10 small transient objects
+ 10.times { allocate_small_object }
+ # Allocate 1 large object that is persistent
+ allocate_large_object
+ end
+
+ # 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
def test_gc_internals
assert_not_nil GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]
- assert_not_nil GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
+ assert_not_nil GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]
end
def test_sweep_in_finalizer
bug9205 = '[ruby-core:58833] [Bug #9205]'
2.times do
- assert_ruby_status([], <<-'end;', bug9205, timeout: 60)
+ assert_ruby_status([], <<-'end;', bug9205, timeout: 120)
raise_proc = proc do |id|
GC.start
end
@@ -336,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')
@@ -351,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
@@ -361,6 +752,14 @@ class TestGc < Test::Unit::TestCase
assert_empty(out)
end
+ def test_finalizer_passed_object_id
+ assert_in_out_err([], <<~RUBY, ["true"], [])
+ o = Object.new
+ obj_id = o.object_id
+ ObjectSpace.define_finalizer(o, ->(id){ p id == obj_id })
+ RUBY
+ end
+
def test_verify_internal_consistency
assert_nil(GC.verify_internal_consistency)
end
@@ -384,18 +783,21 @@ class TestGc < Test::Unit::TestCase
end
def test_gc_stress_at_startup
- skip # it'll be fixed later
- assert_in_out_err([{"RUBY_DEBUG"=>"gc_stress"}], '', [], [], '[Bug #15784]', success: true)
+ assert_in_out_err([{"RUBY_DEBUG"=>"gc_stress"}], '', [], [], '[Bug #15784]', success: true, timeout: 60)
end
def test_gc_disabled_start
- begin
- disabled = GC.disable
+ EnvUtil.without_gc do
c = GC.count
GC.start
assert_equal 1, GC.count - c
- ensure
- GC.enable unless disabled
+ end
+
+ EnvUtil.without_gc do
+ c = GC.count
+ GC.start(immediate_mark: false, immediate_sweep: false)
+ 10_000.times { Object.new }
+ assert_equal 1, GC.count - c
end
end
@@ -407,48 +809,114 @@ class TestGc < Test::Unit::TestCase
end
def test_exception_in_finalizer_procs
- result = []
+ require '-test-/stack'
+ omit 'failing with ASAN' if Thread.asan?
+ assert_in_out_err(["-W0"], "#{<<~"begin;"}\n#{<<~'end;'}", %w[c1 c2])
c1 = proc do
- result << :c1
+ puts "c1"
raise
end
c2 = proc do
- result << :c2
+ puts "c2"
raise
end
- tap {
- tap {
+ begin;
+ tap do
obj = Object.new
ObjectSpace.define_finalizer(obj, c1)
ObjectSpace.define_finalizer(obj, c2)
obj = nil
- }
- }
- GC.start
- skip "finalizers did not get run" if result.empty?
- assert_equal([:c1, :c2], result)
+ end
+ end;
end
def test_exception_in_finalizer_method
- @result = []
+ 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)
- @result << :c1
+ puts "c1"
raise
end
def self.c2(x)
- @result << :c2
+ puts "c2"
raise
end
- tap {
- tap {
+ begin;
+ tap do
obj = Object.new
ObjectSpace.define_finalizer(obj, method(:c1))
ObjectSpace.define_finalizer(obj, method(:c2))
obj = nil
- }
- }
+ end
+ end;
+
+ assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #20042]'
+ begin;
+ def (f = Object.new).call = nil # missing ID
+ o = Object.new
+ ObjectSpace.define_finalizer(o, f)
+ o = nil
+ GC.start
+ end;
+ end
+
+ def test_object_ids_never_repeat
+ GC.start
+ a = 1000.times.map { Object.new.object_id }
GC.start
- skip "finalizers did not get run" if @result.empty?
- assert_equal([:c1, :c2], @result)
+ b = 1000.times.map { Object.new.object_id }
+ assert_empty(a & b)
+ end
+
+ def test_ast_node_buffer
+ # https://github.com/ruby/ruby/pull/4416
+ Module.new.class_eval( (["# shareable_constant_value: literal"] +
+ (0..100000).map {|i| "M#{ i } = {}" }).join("\n"))
+ end
+
+ def test_old_to_young_reference
+ EnvUtil.without_gc do
+ require "objspace"
+
+ old_obj = Object.new
+ 4.times { GC.start }
+
+ assert_include ObjectSpace.dump(old_obj), '"old":true'
+
+ young_obj = Object.new
+ old_obj.instance_variable_set(:@test, young_obj)
+
+ # Not immediately promoted to old generation
+ 3.times do
+ assert_not_include ObjectSpace.dump(young_obj), '"old":true'
+ GC.start
+ end
+
+ # Takes 4 GC to promote to old generation
+ GC.start
+ 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
new file mode 100644
index 0000000000..7e0c499dd9
--- /dev/null
+++ b/test/ruby/test_gc_compact.rb
@@ -0,0 +1,488 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+if RUBY_PLATFORM =~ /s390x/
+ warn "Currently, it is known that the compaction does not work well on s390x; contribution is welcome https://github.com/ruby/ruby/pull/5077"
+ return
+end
+
+class TestGCCompact < Test::Unit::TestCase
+ module CompactionSupportInspector
+ def supports_compact?
+ GC.respond_to?(:compact)
+ end
+ end
+
+ module OmitUnlessCompactSupported
+ include CompactionSupportInspector
+
+ def setup
+ omit "GC compaction not supported on this platform" unless supports_compact?
+ super
+ end
+ end
+
+ include OmitUnlessCompactSupported
+
+ class AutoCompact < Test::Unit::TestCase
+ include OmitUnlessCompactSupported
+
+ def test_enable_autocompact
+ before = GC.auto_compact
+ GC.auto_compact = true
+ assert_predicate GC, :auto_compact
+ ensure
+ GC.auto_compact = before
+ end
+
+ def test_disable_autocompact
+ before = GC.auto_compact
+ GC.auto_compact = false
+ refute GC.auto_compact
+ ensure
+ GC.auto_compact = before
+ end
+
+ def test_major_compacts
+ before = GC.auto_compact
+ GC.auto_compact = true
+ compact = GC.stat :compact_count
+ GC.start
+ assert_operator GC.stat(:compact_count), :>, compact
+ ensure
+ GC.auto_compact = before
+ end
+
+ def test_implicit_compaction_does_something
+ before = GC.auto_compact
+ list = []
+ list2 = []
+
+ # Try to make some fragmentation
+ 500.times {
+ list << Object.new
+ Object.new
+ Object.new
+ }
+ count = GC.stat :compact_count
+ GC.auto_compact = true
+ n = 1_000_000
+ n.times do
+ break if count < GC.stat(:compact_count)
+ list2 << Object.new
+ end and omit "implicit compaction didn't happen within #{n} objects"
+ compact_stats = GC.latest_compact_info
+ refute_predicate compact_stats[:considered], :empty?
+ refute_predicate compact_stats[:moved], :empty?
+ ensure
+ GC.auto_compact = before
+ end
+ end
+
+ class CompactMethodsNotImplemented < Test::Unit::TestCase
+ include CompactionSupportInspector
+
+ def assert_not_implemented(method, *args)
+ omit "autocompact is supported on this platform" if supports_compact?
+
+ assert_raise(NotImplementedError) { GC.send(method, *args) }
+ refute(GC.respond_to?(method), "GC.#{method} should be defined as rb_f_notimplement")
+ end
+
+ def test_gc_compact_not_implemented
+ assert_not_implemented(:compact)
+ end
+
+ def test_gc_auto_compact_get_not_implemented
+ assert_not_implemented(:auto_compact)
+ end
+
+ def test_gc_auto_compact_set_not_implemented
+ assert_not_implemented(:auto_compact=, true)
+ end
+
+ def test_gc_latest_compact_info_not_implemented
+ assert_not_implemented(:latest_compact_info)
+ end
+
+ def test_gc_verify_compaction_references_not_implemented
+ assert_not_implemented(:verify_compaction_references)
+ end
+ end
+
+ def test_gc_compact_stats
+ list = []
+
+ # Try to make some fragmentation
+ 500.times {
+ list << Object.new
+ Object.new
+ Object.new
+ }
+ compact_stats = GC.compact
+ refute_predicate compact_stats[:considered], :empty?
+ refute_predicate compact_stats[:moved], :empty?
+ end
+
+ def big_list(level = 10)
+ if level > 0
+ big_list(level - 1)
+ else
+ 1000.times.map {
+ # try to make some empty slots by allocating an object and discarding
+ Object.new
+ Object.new
+ } # likely next to each other
+ end
+ end
+
+ def test_complex_hash_keys
+ list_of_objects = big_list
+ hash = list_of_objects.hash
+ GC.verify_compaction_references(toward: :empty)
+ assert_equal hash, list_of_objects.hash
+ GC.verify_compaction_references(expand_heap: false)
+ assert_equal hash, list_of_objects.hash
+ end
+
+ def test_ast_compacts
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
+ begin;
+ def walk_ast ast
+ children = ast.children.grep(RubyVM::AbstractSyntaxTree::Node)
+ children.each do |child|
+ assert_predicate child, :type
+ walk_ast child
+ end
+ end
+ ast = RubyVM::AbstractSyntaxTree.parse_file #{__FILE__.dump}
+ assert_predicate GC, :compact
+ walk_ast ast
+ end;
+ end
+
+ def test_compact_count
+ count = GC.stat(:compact_count)
+ GC.compact
+ assert_equal count + 1, GC.stat(:compact_count)
+ end
+
+ def test_compacting_from_trace_point
+ obj = Object.new
+ def obj.tracee
+ :ret # expected to emit both line and call event from one instruction
+ end
+
+ results = []
+ TracePoint.new(:call, :line) do |tp|
+ results << tp.event
+ GC.verify_compaction_references
+ end.enable(target: obj.method(:tracee)) do
+ obj.tracee
+ end
+
+ assert_equal([:call, :line], results)
+ end
+
+ def test_updating_references_for_heap_allocated_shared_arrays
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
+ begin;
+ ary = []
+ 50.times { |i| ary << i }
+
+ # Pointer in slice should point to buffer of ary
+ slice = ary[10..40]
+
+ # Check that slice is pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+
+ # Run compaction to re-embed ary
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ # Assert that slice is pointer to updated buffer in ary
+ assert_equal(10, slice[0])
+ # Check that slice is still pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+ end;
+ end
+
+ def test_updating_references_for_embed_shared_arrays
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
+ begin;
+ ary = Array.new(50)
+ 50.times { |i| ary[i] = i }
+
+ # Ensure ary is embedded
+ assert_include(ObjectSpace.dump(ary), '"embedded":true')
+
+ slice = ary[10..40]
+
+ # Check that slice is pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+
+ # Run compaction to re-embed ary
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ # Assert that slice is pointer to updated buffer in ary
+ assert_equal(10, slice[0])
+ # Check that slice is still pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+ end;
+ end
+
+ def test_updating_references_for_heap_allocated_frozen_shared_arrays
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
+ begin;
+ ary = []
+ 50.times { |i| ary << i }
+ # Frozen arrays can become shared root without RARRAY_SHARED_ROOT_FLAG
+ ary.freeze
+
+ slice = ary[10..40]
+
+ # Check that slice is pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+
+ # Run compaction to re-embed ary
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ # Assert that slice is pointer to updated buffer in ary
+ assert_equal(10, slice[0])
+ # Check that slice is still pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+ end;
+ end
+
+ def test_updating_references_for_embed_frozen_shared_arrays
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
+ begin;
+ ary = Array.new(50)
+ 50.times { |i| ary[i] = i }
+ # Frozen arrays can become shared root without RARRAY_SHARED_ROOT_FLAG
+ ary.freeze
+
+ # Ensure ary is embedded
+ assert_include(ObjectSpace.dump(ary), '"embedded":true')
+
+ slice = ary[10..40]
+
+ # Check that slice is pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+
+ # Run compaction to re-embed ary
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ # Assert that slice is pointer to updated buffer in ary
+ assert_equal(10, slice[0])
+ # Check that slice is still pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+ end;
+ end
+
+ def test_moving_arrays_down_heaps
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
+ begin;
+ ARY_COUNT = 50000
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ Fiber.new {
+ $arys = ARY_COUNT.times.map do
+ ary = "abbbbbbbbbb".chars
+ ary.uniq!
+ end
+ }.resume
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+ assert_operator(stats.dig(:moved_down, :T_ARRAY) || 0, :>=, ARY_COUNT - 10)
+ refute_empty($arys.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
+ end;
+ end
+
+ def test_moving_arrays_up_heaps
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
+ begin;
+ ARY_COUNT = 50000
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ Fiber.new {
+ ary = "hello".chars
+ $arys = ARY_COUNT.times.map do
+ x = []
+ ary.each { |e| x << e }
+ x
+ end
+ }.resume
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+ 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
+
+ def test_moving_objects_between_heaps
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 60)
+ begin;
+ class Foo
+ def add_ivars
+ 10.times do |i|
+ instance_variable_set("@foo" + i.to_s, 0)
+ end
+ end
+ end
+
+ OBJ_COUNT = 50000
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ Fiber.new {
+ $ary = OBJ_COUNT.times.map { Foo.new }
+ $ary.each(&:add_ivars)
+
+ GC.start
+ Foo.new.add_ivars
+ }.resume
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ 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
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30)
+ begin;
+ STR_COUNT = 50000
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ Fiber.new {
+ str = "a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] * 4
+ $ary = STR_COUNT.times.map { +"" << str }
+ }.resume
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_operator(stats[:moved_up][:T_STRING], :>=, STR_COUNT - 15)
+ refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
+ end;
+ end
+
+ def test_moving_strings_down_heaps
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30)
+ begin;
+ STR_COUNT = 50000
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ Fiber.new {
+ $ary = STR_COUNT.times.map { ("a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] * 4).squeeze! }
+ }.resume
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_operator(stats[:moved_down][:T_STRING], :>=, STR_COUNT - 10)
+ refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
+ end;
+ end
+
+ def test_moving_hashes_down_heaps
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ # AR and ST hashes are in the same size pool on 32 bit
+ omit unless RbConfig::SIZEOF["uint64_t"] <= RbConfig::SIZEOF["void*"]
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30)
+ begin;
+ HASH_COUNT = 50000
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ Fiber.new {
+ base_hash = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8 }
+ $ary = HASH_COUNT.times.map { base_hash.dup }
+ $ary.each_with_index { |h, i| h[:i] = 9 }
+ }.resume
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_operator(stats[:moved_down][:T_HASH], :>=, HASH_COUNT - 10)
+ end;
+ end
+
+ def test_moving_objects_between_heaps_keeps_shape_frozen_status
+ # [Bug #19536]
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}")
+ begin;
+ class A
+ def add_ivars
+ @a = @b = @c = @d = 1
+ end
+
+ def set_a
+ @a = 10
+ end
+ end
+
+ a = A.new
+ a.add_ivars
+ a.freeze
+
+ b = A.new
+ b.add_ivars
+ b.set_a # Set the inline cache in set_a
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_raise(FrozenError) { a.set_a }
+ end;
+ end
+
+ def test_moving_too_complex_generic_ivar
+ omit "not compiled with SHAPE_DEBUG" unless defined?(RubyVM::Shape)
+
+ assert_separately([], <<~RUBY)
+ RubyVM::Shape.exhaust_shapes
+
+ obj = []
+ obj.instance_variable_set(:@fixnum, 123)
+ obj.instance_variable_set(:@str, "hello")
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_equal(123, obj.instance_variable_get(:@fixnum))
+ assert_equal("hello", obj.instance_variable_get(:@str))
+ RUBY
+ end
+end
diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb
index bdcf022668..32384f5a5c 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -4,7 +4,6 @@ require 'test/unit'
EnvUtil.suppress_warning {require 'continuation'}
class TestHash < Test::Unit::TestCase
-
def test_hash
x = @cls[1=>2, 2=>4, 3=>6]
y = @cls[1=>2, 2=>4, 3=>6] # y = {1, 2, 2, 4, 3, 6} # 1.9 doesn't support
@@ -85,21 +84,9 @@ class TestHash < Test::Unit::TestCase
self => 'self', true => 'true', nil => 'nil',
'nil' => nil
]
- @verbose = $VERBOSE
- $VERBOSE = nil
end
def teardown
- $VERBOSE = @verbose
- end
-
- def test_bad_initialize_copy
- h = Class.new(Hash) {
- def initialize_copy(h)
- super(Object.new)
- end
- }.new
- assert_raise(TypeError) { h.dup }
end
def test_clear_initialize_copy
@@ -114,16 +101,7 @@ class TestHash < Test::Unit::TestCase
assert_equal(2, h[1])
end
- def test_dup_will_rehash
- set1 = @cls[]
- set2 = @cls[set1 => true]
-
- set1[set1] = true
-
- assert_equal set2, set2.dup
- end
-
- def test_s_AREF
+ def test_s_AREF_from_hash
h = @cls["a" => 100, "b" => 200]
assert_equal(100, h['a'])
assert_equal(200, h['b'])
@@ -134,11 +112,21 @@ class TestHash < Test::Unit::TestCase
assert_equal(200, h['b'])
assert_nil(h['c'])
+ h = @cls[Hash.new(42)]
+ assert_nil(h['a'])
+
+ h = @cls[Hash.new {42}]
+ assert_nil(h['a'])
+ end
+
+ def test_s_AREF_from_list
h = @cls["a", 100, "b", 200]
assert_equal(100, h['a'])
assert_equal(200, h['b'])
assert_nil(h['c'])
+ end
+ def test_s_AREF_from_pairs
h = @cls[[["a", 100], ["b", 200]]]
assert_equal(100, h['a'])
assert_equal(200, h['b'])
@@ -149,10 +137,9 @@ class TestHash < Test::Unit::TestCase
assert_equal(nil, h['b'])
assert_equal(300, h['c'])
- h = @cls[[["a", 100], "b", ["c", 300]]]
- assert_equal(100, h['a'])
- assert_equal(nil, h['b'])
- assert_equal(300, h['c'])
+ assert_raise(ArgumentError) do
+ @cls[[["a", 100], "b", ["c", 300]]]
+ end
end
def test_s_AREF_duplicated_key
@@ -191,6 +178,16 @@ class TestHash < Test::Unit::TestCase
assert_equal('default', h['spurious'])
end
+ def test_st_literal_memory_leak
+ assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", rss: true)
+ begin;
+ 1_000_000.times do
+ # >8 element hashes are ST allocated rather than AR allocated
+ {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9}
+ end
+ end;
+ end
+
def test_try_convert
assert_equal({1=>2}, Hash.try_convert({1=>2}))
assert_equal(nil, Hash.try_convert("1=>2"))
@@ -266,51 +263,6 @@ class TestHash < Test::Unit::TestCase
assert_equal(256, h[z])
end
- def test_AREF_fstring_key
- h = {"abc" => 1}
- before = GC.stat(:total_allocated_objects)
- 5.times{ h["abc"] }
- assert_equal before, GC.stat(:total_allocated_objects)
- end
-
- def test_ASET_fstring_key
- a, b = {}, {}
- assert_equal 1, a["abc"] = 1
- assert_equal 1, b["abc"] = 1
- assert_same a.keys[0], b.keys[0]
- end
-
- def test_hash_aset_fstring_identity
- h = {}.compare_by_identity
- h['abc'] = 1
- h['abc'] = 2
- assert_equal 2, h.size, '[ruby-core:78783] [Bug #12855]'
- end
-
- def test_hash_aref_fstring_identity
- h = {}.compare_by_identity
- h['abc'] = 1
- assert_nil h['abc'], '[ruby-core:78783] [Bug #12855]'
- end
-
- def test_NEWHASH_fstring_key
- a = {"ABC" => :t}
- b = {"ABC" => :t}
- assert_same a.keys[0], b.keys[0]
- assert_same "ABC".freeze, a.keys[0]
- end
-
- def test_tainted_string_key
- str = 'str'.taint
- h = {}
- h[str] = nil
- key = h.keys.first
- assert_equal true, str.tainted?
- assert_equal false, str.frozen?
- assert_equal true, key.tainted?
- assert_equal true, key.frozen?
- end
-
def test_EQUAL # '=='
h1 = @cls[ "a" => 1, "c" => 2 ]
h2 = @cls[ "a" => 1, "c" => 2, 7 => 35 ]
@@ -333,18 +285,14 @@ class TestHash < Test::Unit::TestCase
end
def test_clone
- for taint in [ false, true ]
- for frozen in [ false, true ]
- a = @h.clone
- a.taint if taint
- a.freeze if frozen
- b = a.clone
-
- assert_equal(a, b)
- assert_not_same(a, b)
- assert_equal(a.frozen?, b.frozen?)
- assert_equal(a.tainted?, b.tainted?)
- end
+ for frozen in [ false, true ]
+ a = @h.clone
+ a.freeze if frozen
+ b = a.clone
+
+ assert_equal(a, b)
+ assert_not_same(a, b)
+ assert_equal(a.frozen?, b.frozen?)
end
end
@@ -412,6 +360,19 @@ class TestHash < Test::Unit::TestCase
true
}
assert_equal(base.size, n)
+
+ h = base.dup
+ assert_raise(FrozenError) do
+ h.delete_if do
+ h.freeze
+ true
+ end
+ end
+ assert_equal(base.dup, h)
+
+ h = base.dup
+ assert_same h, h.delete_if {h.assoc(nil); true}
+ assert_empty h
end
def test_keep_if
@@ -419,6 +380,14 @@ class TestHash < Test::Unit::TestCase
assert_equal({3=>4,5=>6}, h.keep_if {|k, v| k + v >= 7 })
h = @cls[1=>2,3=>4,5=>6]
assert_equal({1=>2,3=>4,5=>6}, h.keep_if{true})
+ h = @cls[1=>2,3=>4,5=>6]
+ assert_raise(FrozenError) do
+ h.keep_if do
+ h.freeze
+ false
+ end
+ end
+ assert_equal(@cls[1=>2,3=>4,5=>6], h)
end
def test_compact
@@ -431,18 +400,14 @@ class TestHash < Test::Unit::TestCase
end
def test_dup
- for taint in [ false, true ]
- for frozen in [ false, true ]
- a = @h.dup
- a.taint if taint
- a.freeze if frozen
- b = a.dup
-
- assert_equal(a, b)
- assert_not_same(a, b)
- assert_equal(false, b.frozen?)
- assert_equal(a.tainted?, b.tainted?)
- end
+ for frozen in [ false, true ]
+ a = @h.dup
+ a.freeze if frozen
+ b = a.dup
+
+ assert_equal(a, b)
+ assert_not_same(a, b)
+ assert_equal(false, b.frozen?)
end
end
@@ -452,6 +417,7 @@ class TestHash < Test::Unit::TestCase
h1 = @cls[h => 1]
assert_equal(h1, h1.dup)
h[1] = 2
+ h1.rehash
assert_equal(h1, h1.dup)
end
@@ -650,12 +616,21 @@ class TestHash < Test::Unit::TestCase
assert_not_send([@h, :member?, 'gumby'])
end
+ def hash_hint hv
+ hv & 0xff
+ end
+
def test_rehash
a = [ "a", "b" ]
c = [ "c", "d" ]
h = @cls[ a => 100, c => 300 ]
assert_equal(100, h[a])
- a[0] = "z"
+
+ hv = a.hash
+ begin
+ a[0] << "z"
+ end while hash_hint(a.hash) == hash_hint(hv)
+
assert_nil(h[a])
h.rehash
assert_equal(100, h[a])
@@ -683,14 +658,39 @@ class TestHash < Test::Unit::TestCase
h.instance_variable_set(:@foo, :foo)
h.default = 42
- h.taint
h = EnvUtil.suppress_warning {h.reject {false}}
assert_instance_of(Hash, h)
- assert_not_predicate(h, :tainted?)
assert_nil(h.default)
assert_not_send([h, :instance_variable_defined?, :@foo])
end
+ def test_reject_on_identhash
+ h = @cls[1=>2,3=>4,5=>6]
+ h.compare_by_identity
+ str1 = +'str'
+ str2 = +'str'
+ h[str1] = 1
+ h[str2] = 2
+ expected = {}.compare_by_identity
+ expected[str1] = 1
+ expected[str2] = 2
+ h2 = h.reject{|k,| k != 'str'}
+ assert_equal(expected, h2)
+ assert_equal(true, h2.compare_by_identity?)
+ h2 = h.reject{true}
+ assert_equal({}.compare_by_identity, h2)
+ assert_equal(true, h2.compare_by_identity?)
+
+ h = @cls[]
+ h.compare_by_identity
+ h2 = h.reject{true}
+ assert_equal({}.compare_by_identity, h2)
+ assert_equal(true, h2.compare_by_identity?)
+ h2 = h.reject{|k,| k != 'str'}
+ assert_equal({}.compare_by_identity, h2)
+ assert_equal(true, h2.compare_by_identity?)
+ end
+
def test_reject!
base = @cls[ 1 => 'one', 2 => false, true => 'true', 'cat' => 99 ]
h1 = @cls[ 1 => 'one', 2 => false, true => 'true' ]
@@ -712,6 +712,15 @@ class TestHash < Test::Unit::TestCase
h = base.dup
assert_equal(h3, h.reject! {|k,v| v })
assert_equal(h3, h)
+
+ h = base.dup
+ assert_raise(FrozenError) do
+ h.reject! do
+ h.freeze
+ true
+ end
+ end
+ assert_equal(base.dup, h)
end
def test_replace
@@ -803,11 +812,6 @@ class TestHash < Test::Unit::TestCase
assert_equal([3,4], a.delete([3,4]))
assert_equal([5,6], a.delete([5,6]))
assert_equal(0, a.length)
-
- h = @cls[ 1=>2, 3=>4, 5=>6 ]
- h.taint
- a = h.to_a
- assert_equal(true, a.tainted?)
end
def test_to_hash
@@ -844,17 +848,20 @@ class TestHash < Test::Unit::TestCase
assert_equal("nope42", h[42])
end
- def test_nil_to_h
- h = nil.to_h
- assert_equal({}, h)
- assert_nil(h.default)
- assert_nil(h.default_proc)
+ def test_to_h_block
+ h = @h.to_h {|k, v| [k.to_s, v.to_s]}
+ assert_equal({
+ "1"=>"one", "2"=>"two", "3"=>"three", to_s=>"self",
+ "true"=>"true", ""=>"nil", "nil"=>""
+ },
+ h)
+ assert_instance_of(Hash, h)
end
def test_to_s
h = @cls[ 1 => 2, "cat" => "dog", 1.5 => :fred ]
assert_equal(h.inspect, h.to_s)
- $, = ":"
+ assert_deprecated_warning { $, = ":" }
assert_equal(h.inspect, h.to_s)
h = @cls[]
assert_equal(h.inspect, h.to_s)
@@ -862,6 +869,34 @@ class TestHash < Test::Unit::TestCase
$, = nil
end
+ def test_inspect
+ no_quote = '{a: 1, a!: 1, a?: 1}'
+ quote0 = '{"": 1}'
+ quote1 = '{"0": 1, "!": 1, "%": 1, "&": 1, "*": 1, "+": 1, "-": 1, "/": 1, "<": 1, ">": 1, "^": 1, "`": 1, "|": 1, "~": 1}'
+ quote2 = '{"@a": 1, "$a": 1, "+@": 1, "a=": 1, "[]": 1}'
+ quote3 = '{"a\"b": 1, "@@a": 1, "<=>": 1, "===": 1, "[]=": 1}'
+ assert_equal(no_quote, eval(no_quote).inspect)
+ assert_equal(quote0, eval(quote0).inspect)
+ assert_equal(quote1, eval(quote1).inspect)
+ assert_equal(quote2, eval(quote2).inspect)
+ assert_equal(quote3, eval(quote3).inspect)
+
+ EnvUtil.with_default_external(Encoding::ASCII) do
+ utf8_ascii_hash = '{"\\u3042": 1}'
+ assert_equal(eval(utf8_ascii_hash).inspect, utf8_ascii_hash)
+ end
+
+ EnvUtil.with_default_external(Encoding::UTF_8) do
+ utf8_hash = "{\u3042: 1}"
+ assert_equal(eval(utf8_hash).inspect, utf8_hash)
+ end
+
+ EnvUtil.with_default_external(Encoding::Windows_31J) do
+ sjis_hash = "{\x87]: 1}".force_encoding('sjis')
+ assert_equal(eval(sjis_hash).inspect, sjis_hash)
+ end
+ end
+
def test_update
h1 = @cls[ 1 => 2, 2 => 3, 3 => 4 ]
h2 = @cls[ 2 => 'two', 4 => 'four' ]
@@ -897,16 +932,10 @@ class TestHash < Test::Unit::TestCase
assert_equal([], expected - vals)
end
- def test_initialize_wrong_arguments
- assert_raise(ArgumentError) do
- Hash.new(0) { }
- end
- end
-
def test_create
assert_equal({1=>2, 3=>4}, @cls[[[1,2],[3,4]]])
- assert_raise(ArgumentError) { Hash[0, 1, 2] }
- assert_warning(/wrong element type Integer at 1 /) {@cls[[[1, 2], 3]]}
+ assert_raise(ArgumentError) { @cls[0, 1, 2] }
+ assert_raise(ArgumentError) { @cls[[[0, 1], 2]] }
bug5406 = '[ruby-core:39945]'
assert_raise(ArgumentError, bug5406) { @cls[[[1, 2], [3, 4, 5]]] }
assert_equal({1=>2, 3=>4}, @cls[1,2,3,4])
@@ -923,7 +952,7 @@ class TestHash < Test::Unit::TestCase
end
def test_fetch2
- assert_equal(:bar, @h.fetch(0, :foo) { :bar })
+ assert_equal(:bar, assert_warning(/block supersedes default value argument/) {@h.fetch(0, :foo) { :bar }})
end
def test_default_proc
@@ -941,14 +970,14 @@ class TestHash < Test::Unit::TestCase
h = @cls.new {|hh, k| :foo }
h[1] = 2
assert_equal([1, 2], h.shift)
- assert_equal(:foo, h.shift)
- assert_equal(:foo, h.shift)
+ assert_nil(h.shift)
+ assert_nil(h.shift)
h = @cls.new(:foo)
h[1] = 2
assert_equal([1, 2], h.shift)
- assert_equal(:foo, h.shift)
- assert_equal(:foo, h.shift)
+ assert_nil(h.shift)
+ assert_nil(h.shift)
h =@cls[1=>2]
h.each { assert_equal([1, 2], h.shift) }
@@ -959,7 +988,20 @@ class TestHash < Test::Unit::TestCase
def h.default(k = nil)
super.upcase
end
- assert_equal("FOO", h.shift)
+ assert_nil(h.shift)
+ end
+
+ def test_shift_for_empty_hash
+ # [ruby-dev:51159]
+ h = @cls[]
+ 100.times{|n|
+ while h.size < n
+ k = Random.rand 0..1<<30
+ h[k] = 1
+ end
+ 0 while h.shift
+ assert_equal({}, h)
+ }
end
def test_reject_bang2
@@ -990,20 +1032,53 @@ class TestHash < Test::Unit::TestCase
h.instance_variable_set(:@foo, :foo)
h.default = 42
- h.taint
h = h.select {true}
assert_instance_of(Hash, h)
- assert_not_predicate(h, :tainted?)
assert_nil(h.default)
assert_not_send([h, :instance_variable_defined?, :@foo])
end
+ def test_select_on_identhash
+ h = @cls[1=>2,3=>4,5=>6]
+ h.compare_by_identity
+ str1 = +'str'
+ str2 = +'str'
+ h[str1] = 1
+ h[str2] = 2
+ expected = {}.compare_by_identity
+ expected[str1] = 1
+ expected[str2] = 2
+ h2 = h.select{|k,| k == 'str'}
+ assert_equal(expected, h2)
+ assert_equal(true, h2.compare_by_identity?)
+ h2 = h.select{false}
+ assert_equal({}.compare_by_identity, h2)
+ assert_equal(true, h2.compare_by_identity?)
+
+ h = @cls[]
+ h.compare_by_identity
+ h2 = h.select{false}
+ assert_equal({}.compare_by_identity, h2)
+ assert_equal(true, h2.compare_by_identity?)
+ h2 = h.select{|k,| k == 'str'}
+ assert_equal({}.compare_by_identity, h2)
+ assert_equal(true, h2.compare_by_identity?)
+ end
+
def test_select!
h = @cls[1=>2,3=>4,5=>6]
assert_equal(h, h.select! {|k, v| k + v >= 7 })
assert_equal({3=>4,5=>6}, h)
h = @cls[1=>2,3=>4,5=>6]
assert_equal(nil, h.select!{true})
+ h = @cls[1=>2,3=>4,5=>6]
+ assert_raise(FrozenError) do
+ h.select! do
+ h.freeze
+ false
+ end
+ end
+ assert_equal(@cls[1=>2,3=>4,5=>6], h)
end
def test_slice
@@ -1014,6 +1089,109 @@ class TestHash < Test::Unit::TestCase
assert_equal({}, {}.slice)
end
+ def test_slice_on_identhash
+ h = @cls[1=>2,3=>4,5=>6]
+ h.compare_by_identity
+ str1 = +'str'
+ str2 = +'str'
+ h[str1] = 1
+ h[str2] = 2
+ sliced = h.slice(str1, str2)
+ expected = {}.compare_by_identity
+ expected[str1] = 1
+ expected[str2] = 2
+ assert_equal(expected, sliced)
+ assert_equal(true, sliced.compare_by_identity?)
+ sliced = h.slice
+ assert_equal({}.compare_by_identity, sliced)
+ assert_equal(true, sliced.compare_by_identity?)
+
+ h = @cls[]
+ h.compare_by_identity
+ sliced= h.slice
+ assert_equal({}.compare_by_identity, sliced)
+ assert_equal(true, sliced.compare_by_identity?)
+ sliced = h.slice(str1, str2)
+ assert_equal({}.compare_by_identity, sliced)
+ assert_equal(true, sliced.compare_by_identity?)
+ end
+
+ def test_except
+ h = @cls[1=>2,3=>4,5=>6]
+ assert_equal({5=>6}, h.except(1, 3))
+ assert_equal({1=>2,3=>4,5=>6}, h.except(7))
+ assert_equal({1=>2,3=>4,5=>6}, h.except)
+ assert_equal({}, {}.except)
+ end
+
+ def test_except_on_identhash
+ h = @cls[1=>2,3=>4,5=>6]
+ h.compare_by_identity
+ str1 = +'str'
+ str2 = +'str'
+ h[str1] = 1
+ h[str2] = 2
+ excepted = h.except(str1, str2)
+ assert_equal({1=>2,3=>4,5=>6}.compare_by_identity, excepted)
+ assert_equal(true, excepted.compare_by_identity?)
+ excepted = h.except
+ assert_equal(h, excepted)
+ assert_equal(true, excepted.compare_by_identity?)
+
+ h = @cls[]
+ h.compare_by_identity
+ excepted = h.except
+ assert_equal({}.compare_by_identity, excepted)
+ assert_equal(true, excepted.compare_by_identity?)
+ excepted = h.except(str1, str2)
+ assert_equal({}.compare_by_identity, excepted)
+ assert_equal(true, excepted.compare_by_identity?)
+ end
+
+ def test_filter
+ assert_equal({3=>4,5=>6}, @cls[1=>2,3=>4,5=>6].filter {|k, v| k + v >= 7 })
+
+ base = @cls[ 1 => 'one', '2' => false, true => 'true', 'cat' => 99 ]
+ h1 = @cls[ '2' => false, 'cat' => 99 ]
+ h2 = @cls[ 1 => 'one', true => 'true' ]
+ h3 = @cls[ 1 => 'one', true => 'true', 'cat' => 99 ]
+
+ h = base.dup
+ assert_equal(h, h.filter { true })
+ assert_equal(@cls[], h.filter { false })
+
+ h = base.dup
+ assert_equal(h1, h.filter {|k,v| k.instance_of?(String) })
+
+ assert_equal(h2, h.filter {|k,v| v.instance_of?(String) })
+
+ assert_equal(h3, h.filter {|k,v| v })
+ assert_equal(base, h)
+
+ h.instance_variable_set(:@foo, :foo)
+ h.default = 42
+ h = h.filter {true}
+ assert_instance_of(Hash, h)
+ assert_nil(h.default)
+ assert_not_send([h, :instance_variable_defined?, :@foo])
+ end
+
+ def test_filter!
+ h = @cls[1=>2,3=>4,5=>6]
+ assert_equal(h, h.filter! {|k, v| k + v >= 7 })
+ assert_equal({3=>4,5=>6}, h)
+ h = @cls[1=>2,3=>4,5=>6]
+ assert_equal(nil, h.filter!{true})
+ h = @cls[1=>2,3=>4,5=>6]
+ assert_raise(FrozenError) do
+ h.filter! do
+ h.freeze
+ false
+ end
+ end
+ assert_equal(@cls[1=>2,3=>4,5=>6], h)
+ end
+
def test_clear2
assert_equal({}, @cls[1=>2,3=>4,5=>6].clear)
h = @cls[1=>2,3=>4,5=>6]
@@ -1046,6 +1224,7 @@ class TestHash < Test::Unit::TestCase
def o.to_hash; @cls[]; end
def o.==(x); true; end
assert_equal({}, o)
+ o.singleton_class.remove_method(:==)
def o.==(x); false; end
assert_not_equal({}, o)
@@ -1062,6 +1241,7 @@ class TestHash < Test::Unit::TestCase
def o.to_hash; @cls[]; end
def o.eql?(x); true; end
assert_send([@cls[], :eql?, o])
+ o.singleton_class.remove_method(:eql?)
def o.eql?(x); false; end
assert_not_send([@cls[], :eql?, o])
end
@@ -1082,11 +1262,111 @@ class TestHash < Test::Unit::TestCase
assert_equal({1=>6, 3=>4, 5=>7}, h1)
end
+ def test_update3
+ h1 = @cls[1=>2, 3=>4]
+ h1.update()
+ assert_equal({1=>2, 3=>4}, h1)
+ h2 = {1=>3, 5=>7}
+ h3 = {1=>1, 2=>4}
+ h1.update(h2, h3)
+ assert_equal({1=>1, 2=>4, 3=>4, 5=>7}, h1)
+ end
+
+ def test_update4
+ h1 = @cls[1=>2, 3=>4]
+ h1.update(){|k, v1, v2| k + v1 + v2 }
+ assert_equal({1=>2, 3=>4}, h1)
+ h2 = {1=>3, 5=>7}
+ h3 = {1=>1, 2=>4}
+ h1.update(h2, h3){|k, v1, v2| k + v1 + v2 }
+ assert_equal({1=>8, 2=>4, 3=>4, 5=>7}, h1)
+ end
+
+ def test_update5
+ h = @cls[a: 1, b: 2, c: 3]
+ assert_raise(FrozenError) do
+ h.update({a: 10, b: 20}){ |key, v1, v2| key == :b && h.freeze; v2 }
+ end
+ assert_equal(@cls[a: 10, b: 2, c: 3], h)
+
+ h = @cls[a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10]
+ assert_raise(FrozenError) do
+ h.update({a: 10, b: 20}){ |key, v1, v2| key == :b && h.freeze; v2 }
+ end
+ 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
+ i[key] = 0
+ h = @cls[].update(i)
+ key.upcase!
+ assert_equal(0, h.fetch('a'))
+ end
+
def test_merge
h1 = @cls[1=>2, 3=>4]
h2 = {1=>3, 5=>7}
+ h3 = {1=>1, 2=>4}
+ assert_equal({1=>2, 3=>4}, h1.merge())
assert_equal({1=>3, 3=>4, 5=>7}, h1.merge(h2))
assert_equal({1=>6, 3=>4, 5=>7}, h1.merge(h2) {|k, v1, v2| k + v1 + v2 })
+ assert_equal({1=>1, 2=>4, 3=>4, 5=>7}, h1.merge(h2, h3))
+ assert_equal({1=>8, 2=>4, 3=>4, 5=>7}, h1.merge(h2, h3) {|k, v1, v2| k + v1 + v2 })
+ end
+
+ def test_merge_on_identhash
+ h = @cls[1=>2,3=>4,5=>6]
+ h.compare_by_identity
+ str1 = +'str'
+ str2 = +'str'
+ h[str1] = 1
+ h[str2] = 2
+ expected = h.dup
+ expected[7] = 8
+ h2 = h.merge(7=>8)
+ assert_equal(expected, h2)
+ assert_predicate(h2, :compare_by_identity?)
+ h2 = h.merge({})
+ assert_equal(h, h2)
+ assert_predicate(h2, :compare_by_identity?)
+
+ h = @cls[]
+ h.compare_by_identity
+ h1 = @cls[7=>8]
+ h1.compare_by_identity
+ h2 = h.merge(7=>8)
+ assert_equal(h1, h2)
+ assert_predicate(h2, :compare_by_identity?)
+ h2 = h.merge({})
+ assert_equal(h, h2)
+ assert_predicate(h2, :compare_by_identity?)
+ end
+
+ def test_merge!
+ h = @cls[a: 1, b: 2, c: 3]
+ assert_raise(FrozenError) do
+ h.merge!({a: 10, b: 20}){ |key, v1, v2| key == :b && h.freeze; v2 }
+ end
+ assert_equal(@cls[a: 10, b: 2, c: 3], h)
+
+ h = @cls[a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10]
+ assert_raise(FrozenError) do
+ h.merge!({a: 10, b: 20}){ |key, v1, v2| key == :b && h.freeze; v2 }
+ end
+ 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_assoc
@@ -1127,6 +1407,8 @@ class TestHash < Test::Unit::TestCase
end
def test_callcc
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
h = @cls[1=>2]
c = nil
f = false
@@ -1147,6 +1429,8 @@ class TestHash < Test::Unit::TestCase
end
def test_callcc_iter_level
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
bug9105 = '[ruby-dev:47803] [Bug #9105]'
h = @cls[1=>2, 3=>4]
c = nil
@@ -1165,6 +1449,8 @@ class TestHash < Test::Unit::TestCase
end
def test_callcc_escape
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
bug9105 = '[ruby-dev:47803] [Bug #9105]'
assert_nothing_raised(RuntimeError, bug9105) do
h=@cls[]
@@ -1179,6 +1465,8 @@ class TestHash < Test::Unit::TestCase
end
def test_callcc_reenter
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
bug9105 = '[ruby-dev:47803] [Bug #9105]'
assert_nothing_raised(RuntimeError, bug9105) do
h = @cls[1=>2,3=>4]
@@ -1226,6 +1514,16 @@ class TestHash < Test::Unit::TestCase
assert_predicate(h.dup, :compare_by_identity?, bug8703)
end
+ def test_compare_by_identy_memory_leak
+ assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #20145]", rss: true)
+ begin;
+ h = { 1 => 2 }.compare_by_identity
+ 1_000_000.times do
+ h.select { false }
+ end
+ end;
+ end
+
def test_same_key
bug9646 = '[ruby-dev:48047] [Bug #9646] Infinite loop at Hash#each'
h = @cls[a=[], 1]
@@ -1356,115 +1654,6 @@ class TestHash < Test::Unit::TestCase
end
end
- def test_exception_in_rehash_memory_leak
- return unless @cls == Hash
-
- bug9187 = '[ruby-core:58728] [Bug #9187]'
-
- prepare = <<-EOS
- class Foo
- def initialize
- @raise = false
- end
-
- def hash
- raise if @raise
- @raise = true
- return 0
- end
- end
- h = {Foo.new => true}
- EOS
-
- code = <<-EOS
- 10_0000.times do
- h.rehash rescue nil
- end
- GC.start
- EOS
-
- assert_no_memory_leak([], prepare, code, bug9187)
- end
-
- def test_wrapper
- bug9381 = '[ruby-core:59638] [Bug #9381]'
-
- wrapper = Class.new do
- def initialize(obj)
- @obj = obj
- end
-
- def hash
- @obj.hash
- end
-
- def eql?(other)
- @obj.eql?(other)
- end
- end
-
- bad = [
- 5, true, false, nil,
- 0.0, 1.72723e-77,
- :foo, "dsym_#{self.object_id.to_s(16)}_#{Time.now.to_i.to_s(16)}".to_sym,
- "str",
- ].select do |x|
- hash = {x => bug9381}
- hash[wrapper.new(x)] != bug9381
- end
- assert_empty(bad, bug9381)
- end
-
- def assert_hash_random(obj, dump = obj.inspect)
- a = [obj.hash.to_s]
- 3.times {
- assert_in_out_err(["-e", "print (#{dump}).hash"], "") do |r, e|
- a += r
- assert_equal([], e)
- end
- }
- assert_not_equal([obj.hash.to_s], a.uniq)
- assert_operator(a.uniq.size, :>, 2, proc {a.inspect})
- end
-
- def test_string_hash_random
- assert_hash_random('abc')
- end
-
- def test_symbol_hash_random
- assert_hash_random(:-)
- assert_hash_random(:foo)
- assert_hash_random("dsym_#{self.object_id.to_s(16)}_#{Time.now.to_i.to_s(16)}".to_sym)
- end
-
- def test_integer_hash_random
- assert_hash_random(0)
- assert_hash_random(+1)
- assert_hash_random(-1)
- assert_hash_random(+(1<<100))
- assert_hash_random(-(1<<100))
- end
-
- def test_float_hash_random
- assert_hash_random(0.0)
- assert_hash_random(+1.0)
- assert_hash_random(-1.0)
- assert_hash_random(1.72723e-77)
- assert_hash_random(Float::INFINITY, "Float::INFINITY")
- end
-
- def test_label_syntax
- return unless @cls == Hash
-
- feature4935 = '[ruby-core:37553] [Feature #4935]'
- x = 'world'
- hash = assert_nothing_raised(SyntaxError, feature4935) do
- break eval(%q({foo: 1, "foo-bar": 2, "hello-#{x}": 3, 'hello-#{x}': 4, 'bar': {}}))
- end
- assert_equal({:foo => 1, :'foo-bar' => 2, :'hello-world' => 3, :'hello-#{x}' => 4, :bar => {}}, hash, feature4935)
- x = x
- end
-
def test_dig
h = @cls[a: @cls[b: [1, 2, 3]], c: 4]
assert_equal(1, h.dig(:a, :b, 0))
@@ -1484,12 +1673,12 @@ class TestHash < Test::Unit::TestCase
def o.respond_to?(*args)
super
end
- assert_raise(TypeError, bug12030) {{foo: o}.dig(:foo, :foo)}
+ assert_raise(TypeError, bug12030) {@cls[foo: o].dig(:foo, :foo)}
end
def test_cmp
- h1 = {a:1, b:2}
- h2 = {a:1, b:2, c:3}
+ h1 = @cls[a:1, b:2]
+ h2 = @cls[a:1, b:2, c:3]
assert_operator(h1, :<=, h1)
assert_operator(h1, :<=, h2)
@@ -1513,8 +1702,8 @@ class TestHash < Test::Unit::TestCase
end
def test_cmp_samekeys
- h1 = {a:1}
- h2 = {a:2}
+ h1 = @cls[a:1]
+ h2 = @cls[a:2]
assert_operator(h1, :<=, h1)
assert_not_operator(h1, :<=, h2)
@@ -1538,13 +1727,15 @@ class TestHash < Test::Unit::TestCase
end
def test_to_proc
- h = {
+ h = @cls[
1 => 10,
2 => 20,
3 => 30,
- }
+ ]
assert_equal([10, 20, 30], [1, 2, 3].map(&h))
+
+ assert_predicate(h.to_proc, :lambda?)
end
def test_transform_keys
@@ -1559,6 +1750,27 @@ class TestHash < Test::Unit::TestCase
y = x.transform_keys.with_index {|k, i| "#{k}.#{i}" }
assert_equal(%w(a.0 b.1 c.2), y.keys)
+
+ assert_equal({A: 1, B: 2, c: 3}, x.transform_keys({a: :A, b: :B, d: :D}))
+ assert_equal({A: 1, B: 2, "c" => 3}, x.transform_keys({a: :A, b: :B, d: :D}, &:to_s))
+ end
+
+ def test_transform_keys_on_identhash
+ h = @cls[1=>2,3=>4,5=>6]
+ h.compare_by_identity
+ str1 = +'str'
+ str2 = +'str'
+ h[str1] = 1
+ h[str2] = 2
+ h2 = h.transform_keys(&:itself)
+ assert_equal(Hash[h.to_a], h2)
+ assert_equal(false, h2.compare_by_identity?)
+
+ h = @cls[]
+ h.compare_by_identity
+ h2 = h.transform_keys(&:itself)
+ assert_equal({}, h2)
+ assert_equal(false, h2.compare_by_identity?)
end
def test_transform_keys_bang
@@ -1578,21 +1790,57 @@ class TestHash < Test::Unit::TestCase
x.transform_keys! {|k| -k }
assert_equal([-1, :a, 1, :b], x.flatten)
+ x = @cls[a: 1, b: 2, c: 3]
+ x.transform_keys! { |k| k == :b && break }
+ assert_equal({false => 1, b: 2, c: 3}, x)
+
x = @cls[true => :a, false => :b]
x.transform_keys! {|k| !k }
assert_equal([false, :a, true, :b], x.flatten)
+
+ x = @cls[a: 1, b: 2, c: 3]
+ x.transform_keys!({a: :A, b: :B, d: :D})
+ assert_equal({A: 1, B: 2, c: 3}, x)
+ x = @cls[a: 1, b: 2, c: 3]
+ x.transform_keys!({a: :A, b: :B, d: :D}, &:to_s)
+ assert_equal({A: 1, B: 2, "c" => 3}, x)
end
def test_transform_values
x = @cls[a: 1, b: 2, c: 3]
+ x.default = 42
y = x.transform_values {|v| v ** 2 }
assert_equal([1, 4, 9], y.values_at(:a, :b, :c))
assert_not_same(x, y)
+ assert_nil(y.default)
+
+ x.default_proc = proc {|h, k| k}
+ y = x.transform_values {|v| v ** 2 }
+ assert_nil(y.default_proc)
+ assert_nil(y.default)
y = x.transform_values.with_index {|v, i| "#{v}.#{i}" }
assert_equal(%w(1.0 2.1 3.2), y.values_at(:a, :b, :c))
end
+ def test_transform_values_on_identhash
+ h = @cls[1=>2,3=>4,5=>6]
+ h.compare_by_identity
+ str1 = +'str'
+ str2 = +'str'
+ h[str1] = 1
+ h[str2] = 2
+ h2 = h.transform_values(&:itself)
+ assert_equal(h, h2)
+ assert_equal(true, h2.compare_by_identity?)
+
+ h = @cls[]
+ h.compare_by_identity
+ h2 = h.transform_values(&:itself)
+ assert_equal({}.compare_by_identity, h2)
+ assert_equal(true, h2.compare_by_identity?)
+ end
+
def test_transform_values_bang
x = @cls[a: 1, b: 2, c: 3]
y = x.transform_values! {|v| v ** 2 }
@@ -1600,22 +1848,83 @@ class TestHash < Test::Unit::TestCase
assert_same(x, y)
x = @cls[a: 1, b: 2, c: 3]
+ x.transform_values! { |v| v == 2 && break }
+ assert_equal({a: false, b: 2, c: 3}, x)
+
+ x = @cls[a: 1, b: 2, c: 3]
y = x.transform_values!.with_index {|v, i| "#{v}.#{i}" }
assert_equal(%w(1.0 2.1 3.2), y.values_at(:a, :b, :c))
+
+ x = @cls[a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10]
+ assert_raise(FrozenError) do
+ x.transform_values!() do |v|
+ x.freeze if v == 2
+ v.succ
+ 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 test_broken_hash_value
- bug14218 = '[ruby-core:84395] [Bug #14218]'
+ def hrec h, n, &b
+ if n > 0
+ h.each{hrec(h, n-1, &b)}
+ else
+ yield
+ end
+ end
- assert_equal(0, 1_000_000.times.count{a=Object.new.hash; b=Object.new.hash; a < 0 && b < 0 && a + b > 0}, bug14218)
- assert_equal(0, 1_000_000.times.count{a=Object.new.hash; b=Object.new.hash; 0 + a + b != 0 + b + a}, bug14218)
+ def test_huge_iter_level
+ nrec = 200
+
+ h = @cls[a: 1]
+ hrec(h, nrec){}
+ h[:c] = 3
+ assert_equal(3, h[:c])
+
+ h = @cls[a: 1]
+ h.freeze # set hidden attribute for a frozen object
+ hrec(h, nrec){}
+ assert_equal(1, h.size)
+
+ h = @cls[a: 1]
+ assert_raise(RuntimeError){
+ hrec(h, nrec){ h[:c] = 3 }
+ }
+ rescue SystemStackError
+ # ignore
+ end
+
+ # Previously this test would fail because rb_hash inside opt_aref would look
+ # at the current method name
+ def test_hash_recursion_independent_of_mid
+ o = Class.new do
+ def hash(h, k)
+ h[k]
+ end
+
+ def any_other_name(h, k)
+ h[k]
+ end
+ end.new
+
+ rec = []; rec << rec
+
+ h = @cls[]
+ h[rec] = 1
+ assert o.hash(h, rec)
+ assert o.any_other_name(h, rec)
end
class TestSubHash < TestHash
class SubHash < Hash
- def reject(*)
- super
- end
end
def setup
@@ -1624,3 +1933,502 @@ class TestHash < Test::Unit::TestCase
end
end
end
+
+class TestHashOnly < Test::Unit::TestCase
+ def test_bad_initialize_copy
+ h = Class.new(Hash) {
+ def initialize_copy(h)
+ super(Object.new)
+ end
+ }.new
+ assert_raise(TypeError) { h.dup }
+ end
+
+ def test_dup_will_not_rehash
+ assert_hash_does_not_rehash(&:dup)
+ end
+
+ def assert_hash_does_not_rehash
+ obj = Object.new
+ class << obj
+ attr_accessor :hash_calls
+ def hash
+ @hash_calls += 1
+ super
+ end
+ end
+ obj.hash_calls = 0
+ hash = {obj => 42}
+ assert_equal(1, obj.hash_calls)
+ yield hash
+ assert_equal(1, obj.hash_calls)
+ end
+
+ def test_select_reject_will_not_rehash
+ assert_hash_does_not_rehash do |hash|
+ hash.select { true }
+ end
+ assert_hash_does_not_rehash do |hash|
+ hash.reject { false }
+ end
+ end
+
+ def test_st_literal_memory_leak
+ assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", rss: true)
+ begin;
+ 1_000_000.times do
+ # >8 element hashes are ST allocated rather than AR allocated
+ {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9}
+ end
+ end;
+ end
+
+ def test_compare_by_id_memory_leak
+ assert_no_memory_leak([], "", <<~RUBY, rss: true)
+ 1_000_000.times do
+ {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8}.compare_by_identity
+ end
+ RUBY
+ end
+
+ def test_try_convert
+ assert_equal({1=>2}, Hash.try_convert({1=>2}))
+ assert_equal(nil, Hash.try_convert("1=>2"))
+ o = Object.new
+ def o.to_hash; {3=>4} end
+ assert_equal({3=>4}, Hash.try_convert(o))
+ end
+
+ def test_AREF_fstring_key
+ # warmup ObjectSpace.count_objects
+ ObjectSpace.count_objects
+
+ h = {"abc" => 1}
+
+ EnvUtil.without_gc do
+ before = ObjectSpace.count_objects[:T_STRING]
+ 5.times{ h["abc".freeze] }
+ assert_equal before, ObjectSpace.count_objects[:T_STRING]
+ end
+ end
+
+ def test_AREF_fstring_key_default_proc
+ assert_separately(['--disable-frozen-string-literal'], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ h = Hash.new do |h, k|
+ k.frozen?
+ end
+
+ str = "foo"
+ refute str.frozen?
+ refute h[str]
+ refute h["foo"]
+ end;
+ end
+
+ def test_ASET_fstring_key
+ a, b = {}, {}
+ assert_equal 1, a["abc"] = 1
+ assert_equal 1, b["abc"] = 1
+ assert_same a.keys[0], b.keys[0]
+ end
+
+ def test_ASET_fstring_non_literal_key
+ underscore = "_"
+ non_literal_strings = Proc.new{ ["abc#{underscore}def", "abc" * 5, "abc" + "def", "" << "ghi" << "jkl"] }
+
+ a, b = {}, {}
+ non_literal_strings.call.each do |string|
+ assert_equal 1, a[string] = 1
+ end
+
+ non_literal_strings.call.each do |string|
+ assert_equal 1, b[string] = 1
+ end
+
+ [a.keys, b.keys].transpose.each do |key_a, key_b|
+ assert_same key_a, key_b
+ end
+ end
+
+ def test_hash_aset_fstring_identity
+ h = {}.compare_by_identity
+ h['abc'] = 1
+ h['abc'] = 2
+ assert_equal 2, h.size, '[ruby-core:78783] [Bug #12855]'
+ end
+
+ def test_hash_aref_fstring_identity
+ h = {}.compare_by_identity
+ h['abc'] = 1
+ assert_nil h['abc'], '[ruby-core:78783] [Bug #12855]'
+ end
+
+ def test_NEWHASH_fstring_key
+ a = {"ABC" => :t}
+ b = {"ABC" => :t}
+ assert_same a.keys[0], b.keys[0]
+ assert_same "ABC".freeze, a.keys[0]
+ var = +'ABC'
+ c = { var => :t }
+ assert_same "ABC".freeze, c.keys[0]
+ end
+
+ def test_rehash_memory_leak
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ ar_hash = 1.times.map { |i| [i, i] }.to_h
+ st_hash = 10.times.map { |i| [i, i] }.to_h
+
+ code = proc do
+ ar_hash.rehash
+ st_hash.rehash
+ end
+ 1_000.times(&code)
+ PREP
+ 1_000_000.times(&code)
+ CODE
+ end
+
+ def test_replace_bug15358
+ h1 = {}
+ h2 = {a:1,b:2,c:3,d:4,e:5}
+ h2.replace(h1)
+ GC.start
+ assert(true)
+ end
+
+ def test_replace_st_with_ar
+ # ST hash
+ h1 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9 }
+ # AR hash
+ h2 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 }
+ # Replace ST hash with AR hash
+ h1.replace(h2)
+ assert_equal(h2, h1)
+ end
+
+ def test_nil_to_h
+ h = nil.to_h
+ assert_equal({}, h)
+ assert_nil(h.default)
+ assert_nil(h.default_proc)
+ end
+
+ def test_initialize_wrong_arguments
+ assert_raise(ArgumentError) do
+ Hash.new(0) { }
+ end
+ end
+
+ def test_replace_memory_leak
+ assert_no_memory_leak([], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true)
+ h = ("aa".."zz").each_with_index.to_h
+ 10_000.times {h.dup}
+ begin;
+ 500_000.times {h.dup.replace(h)}
+ end;
+ end
+
+ def hash_iter_recursion(h, level)
+ return if level == 0
+ h.each_key {}
+ h.each_value { hash_iter_recursion(h, level - 1) }
+ end
+
+ def test_iterlevel_in_ivar_bug19589
+ h = { a: nil }
+ # 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
+
+ def test_exception_in_rehash_memory_leak
+ bug9187 = '[ruby-core:58728] [Bug #9187]'
+
+ prepare = <<-EOS
+ class Foo
+ def initialize
+ @raise = false
+ end
+
+ def hash
+ raise if @raise
+ @raise = true
+ return 0
+ end
+ end
+ h = {Foo.new => true}
+ EOS
+
+ code = <<-EOS
+ 10_0000.times do
+ h.rehash rescue nil
+ end
+ GC.start
+ EOS
+
+ assert_no_memory_leak([], prepare, code, bug9187)
+ end
+
+ def test_memory_size_after_delete
+ require 'objspace'
+ h = {}
+ 1000.times {|i| h[i] = true}
+ big = ObjectSpace.memsize_of(h)
+ 1000.times {|i| h.delete(i)}
+ assert_operator ObjectSpace.memsize_of(h), :<, big/10
+ end
+
+ def test_wrapper
+ bug9381 = '[ruby-core:59638] [Bug #9381]'
+
+ wrapper = Class.new do
+ def initialize(obj)
+ @obj = obj
+ end
+
+ def hash
+ @obj.hash
+ end
+
+ def eql?(other)
+ @obj.eql?(other)
+ end
+ end
+
+ bad = [
+ 5, true, false, nil,
+ 0.0, 1.72723e-77,
+ :foo, "dsym_#{self.object_id.to_s(16)}_#{Time.now.to_i.to_s(16)}".to_sym,
+ "str",
+ ].select do |x|
+ hash = {x => bug9381}
+ hash[wrapper.new(x)] != bug9381
+ end
+ assert_empty(bad, bug9381)
+ end
+
+ def assert_hash_random(obj, dump = obj.inspect)
+ a = [obj.hash.to_s]
+ 3.times {
+ assert_in_out_err(["-e", "print (#{dump}).hash"], "") do |r, e|
+ a += r
+ assert_equal([], e)
+ end
+ }
+ assert_not_equal([obj.hash.to_s], a.uniq)
+ assert_operator(a.uniq.size, :>, 2, proc {a.inspect})
+ end
+
+ def test_string_hash_random
+ assert_hash_random('abc')
+ end
+
+ def test_symbol_hash_random
+ assert_hash_random(:-)
+ assert_hash_random(:foo)
+ assert_hash_random("dsym_#{self.object_id.to_s(16)}_#{Time.now.to_i.to_s(16)}".to_sym)
+ end
+
+ def test_integer_hash_random
+ assert_hash_random(0)
+ assert_hash_random(+1)
+ assert_hash_random(-1)
+ assert_hash_random(+(1<<100))
+ assert_hash_random(-(1<<100))
+ end
+
+ def test_float_hash_random
+ assert_hash_random(0.0)
+ assert_hash_random(+1.0)
+ assert_hash_random(-1.0)
+ assert_hash_random(1.72723e-77)
+ assert_hash_random(Float::INFINITY, "Float::INFINITY")
+ end
+
+ def test_label_syntax
+ feature4935 = '[ruby-core:37553] [Feature #4935]'
+ x = 'world'
+ hash = assert_nothing_raised(SyntaxError, feature4935) do
+ break eval(%q({foo: 1, "foo-bar": 2, "hello-#{x}": 3, 'hello-#{x}': 4, 'bar': {}}))
+ end
+ assert_equal({:foo => 1, :'foo-bar' => 2, :'hello-world' => 3, :'hello-#{x}' => 4, :bar => {}}, hash, feature4935)
+ x = x
+ end
+
+ def test_broken_hash_value
+ bug14218 = '[ruby-core:84395] [Bug #14218]'
+
+ assert_equal(0, 1_000_000.times.count{a=Object.new.hash; b=Object.new.hash; a < 0 && b < 0 && a + b > 0}, bug14218)
+ assert_equal(0, 1_000_000.times.count{a=Object.new.hash; b=Object.new.hash; 0 + a + b != 0 + b + a}, bug14218)
+ end
+
+ def test_reserved_hash_val
+ s = Struct.new(:hash)
+ h = {}
+ keys = [*0..8]
+ keys.each {|i| h[s.new(i)]=true}
+ msg = proc {h.inspect}
+ assert_equal(keys, h.keys.map(&:hash), msg)
+ end
+
+ ruby2_keywords def get_flagged_hash(*args)
+ args.last
+ end
+
+ def check_flagged_hash(k: :NG)
+ k
+ end
+
+ def test_ruby2_keywords_hash?
+ flagged_hash = get_flagged_hash(k: 1)
+ assert_equal(true, Hash.ruby2_keywords_hash?(flagged_hash))
+ assert_equal(false, Hash.ruby2_keywords_hash?({}))
+ assert_raise(TypeError) { Hash.ruby2_keywords_hash?(1) }
+ end
+
+ def test_ruby2_keywords_hash
+ hash = {k: 1}
+ assert_equal(false, Hash.ruby2_keywords_hash?(hash))
+ hash = Hash.ruby2_keywords_hash(hash)
+ assert_equal(true, Hash.ruby2_keywords_hash?(hash))
+ assert_equal(1, check_flagged_hash(*[hash]))
+ assert_raise(TypeError) { Hash.ruby2_keywords_hash(1) }
+ end
+
+ def ar2st_object
+ class << (obj = Object.new)
+ attr_reader :h
+ end
+ obj.instance_variable_set(:@h, {})
+ def obj.hash
+ 10.times{|i| @h[i] = i}
+ 0
+ end
+ def obj.inspect
+ 'test'
+ end
+ def obj.eql? other
+ other.class == Object
+ end
+ obj
+ end
+
+ def test_ar2st_insert
+ obj = ar2st_object
+ h = obj.h
+
+ h[obj] = true
+ assert_equal '{0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, 8 => 8, 9 => 9, test => true}', h.inspect
+ end
+
+ def test_ar2st_delete
+ obj = ar2st_object
+ h = obj.h
+
+ obj2 = Object.new
+ def obj2.hash
+ 0
+ end
+
+ h[obj2] = true
+ h.delete obj
+ assert_equal '{0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, 8 => 8, 9 => 9}', h.inspect
+ end
+
+ def test_ar2st_lookup
+ obj = ar2st_object
+ h = obj.h
+
+ obj2 = Object.new
+ def obj2.hash
+ 0
+ end
+
+ h[obj2] = true
+ assert_equal true, h[obj]
+ end
+
+ def test_bug_12706
+ assert_raise(ArgumentError) do
+ {a: 1}.each(&->(k, v) {})
+ 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;'}")
+ begin;
+ require "delegate"
+ typename = DelegateClass(String)
+
+ hash = {
+ "Int" => true,
+ "Float" => true,
+ "String" => true,
+ "Boolean" => true,
+ "WidgetFilter" => true,
+ "WidgetAggregation" => true,
+ "WidgetEdge" => true,
+ "WidgetSortOrder" => true,
+ "WidgetGrouping" => true,
+ }
+
+ hash.each_key do |key|
+ assert_send([hash, :key?, typename.new(key)])
+ end
+ end;
+ end
+ end
+
+ def test_compare_by_identity_during_iteration
+ h = { 1 => 1 }
+ h.each do
+ assert_raise(RuntimeError, "compare_by_identity during iteration") do
+ h.compare_by_identity
+ end
+ end
+ end
+
+ def test_ar_hash_to_st_hash
+ assert_normal_exit("#{<<~"begin;"}\n#{<<~'end;'}", 'https://bugs.ruby-lang.org/issues/20050#note-5')
+ begin;
+ srand(0)
+ class Foo
+ def to_a
+ []
+ end
+
+ def hash
+ $h.delete($h.keys.sample) if rand < 0.1
+ to_a.hash
+ end
+ end
+
+ 1000.times do
+ $h = {}
+ (0..10).each {|i| $h[Foo.new] ||= {} }
+ end
+ end;
+ end
+
+ def test_ar_to_st_reserved_value
+ klass = Class.new do
+ attr_reader :hash
+ def initialize(val) = @hash = val
+ end
+
+ values = 0.downto(-16).to_a
+ hash = {}
+ values.each do |val|
+ hash[klass.new(val)] = val
+ end
+ assert_equal values, hash.values, "[ruby-core:121239] [Bug #21170]"
+ end
+end
diff --git a/test/ruby/test_ifunless.rb b/test/ruby/test_ifunless.rb
index d533e155bc..f68e5154a2 100644
--- a/test/ruby/test_ifunless.rb
+++ b/test/ruby/test_ifunless.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: false
require 'test/unit'
-class TestIfunless < Test::Unit::TestCase
+class TestIfUnless < Test::Unit::TestCase
def test_if_unless
x = 'test';
assert(if x == x then true else false end)
diff --git a/test/ruby/test_inlinecache.rb b/test/ruby/test_inlinecache.rb
new file mode 100644
index 0000000000..d48d95d74e
--- /dev/null
+++ b/test/ruby/test_inlinecache.rb
@@ -0,0 +1,110 @@
+# -*- coding: us-ascii -*-
+# frozen_string_literal: true
+
+require 'test/unit'
+
+class TestMethodInlineCache < Test::Unit::TestCase
+ def test_alias
+ m0 = Module.new do
+ def foo; :M0 end
+ end
+ m1 = Module.new do
+ include m0
+ end
+ c = Class.new do
+ include m1
+ alias bar foo
+ end
+ d = Class.new(c) do
+ end
+
+ test = -> do
+ d.new.bar
+ end
+
+ assert_equal :M0, test[]
+
+ c.class_eval do
+ def bar
+ :C
+ end
+ end
+
+ assert_equal :C, test[]
+ end
+
+ def test_zsuper
+ assert_separately [], <<-EOS
+ class C
+ private def foo
+ :C
+ end
+ end
+
+ class D < C
+ public :foo
+ end
+
+ class E < D; end
+ class F < E; end
+
+ test = -> do
+ F.new().foo
+ end
+
+ assert_equal :C, test[]
+
+ class E
+ def foo; :E; end
+ end
+
+ assert_equal :E, test[]
+ EOS
+ end
+
+ def test_module_methods_redefiniton
+ m0 = Module.new do
+ def foo
+ super
+ end
+ end
+
+ c1 = Class.new do
+ def foo
+ :C1
+ end
+ end
+
+ c2 = Class.new do
+ def foo
+ :C2
+ end
+ end
+
+ d1 = Class.new(c1) do
+ include m0
+ end
+
+ d2 = Class.new(c2) do
+ include m0
+ end
+
+ assert_equal :C1, d1.new.foo
+
+ m = Module.new do
+ def foo
+ super
+ end
+ end
+
+ d1.class_eval do
+ include m
+ end
+
+ d2.class_eval do
+ include m
+ end
+
+ assert_equal :C2, d2.new.foo
+ end
+end
diff --git a/test/ruby/test_insns_leaf.rb b/test/ruby/test_insns_leaf.rb
new file mode 100644
index 0000000000..9c9a4324cb
--- /dev/null
+++ b/test/ruby/test_insns_leaf.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestInsnsLeaf < Test::Unit::TestCase
+ require "set"
+
+ class Id
+ attr_reader :db_id
+ def initialize(db_id)
+ @db_id = db_id
+ end
+
+ def ==(other)
+ other.class == self.class && other.db_id == db_id
+ end
+ alias_method :eql?, :==
+
+ def hash
+ 10
+ end
+
+ def <=>(other)
+ db_id <=> other.db_id if other.is_a?(self.class)
+ end
+ end
+
+ class Namespace
+ IDS = Set[
+ Id.new(1).freeze,
+ Id.new(2).freeze,
+ Id.new(3).freeze,
+ Id.new(4).freeze,
+ ].freeze
+
+ class << self
+ def test?(id)
+ IDS.include?(id)
+ end
+ end
+ end
+
+ def test_insns_leaf
+ assert Namespace.test?(Id.new(1)), "IDS should include 1"
+ assert !Namespace.test?(Id.new(5)), "IDS should not include 5"
+ end
+end
diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb
index 1b485760e3..f9bf4fa20c 100644
--- a/test/ruby/test_integer.rb
+++ b/test/ruby/test_integer.rb
@@ -2,15 +2,47 @@
require 'test/unit'
class TestInteger < Test::Unit::TestCase
- BDSIZE = 0x4000000000000000.coerce(0)[0].size
- def self.bdsize(x)
- ((x + 1) / 8 + BDSIZE) / BDSIZE * BDSIZE
- end
- def bdsize(x)
- self.class.bdsize(x)
- end
+ FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN']
+ FIXNUM_MAX = RbConfig::LIMITS['FIXNUM_MAX']
+ LONG_MAX = RbConfig::LIMITS['LONG_MAX']
def test_aref
+
+ [
+ *-16..16,
+ *(FIXNUM_MIN-2)..(FIXNUM_MIN+2),
+ *(FIXNUM_MAX-2)..(FIXNUM_MAX+2),
+ ].each do |n|
+ (-64..64).each do |idx|
+ assert_equal((n >> idx) & 1, n[idx])
+ end
+ [*-66..-62, *-34..-30, *-5..5, *30..34, *62..66].each do |idx|
+ (0..100).each do |len|
+ assert_equal((n >> idx) & ((1 << len) - 1), n[idx, len], "#{ n }[#{ idx }, #{ len }]")
+ end
+ (0..100).each do |len|
+ assert_equal((n >> idx) & ((1 << (len + 1)) - 1), n[idx..idx+len], "#{ n }[#{ idx }..#{ idx+len }]")
+ assert_equal((n >> idx) & ((1 << len) - 1), n[idx...idx+len], "#{ n }[#{ idx }...#{ idx+len }]")
+ end
+
+ # endless
+ assert_equal((n >> idx), n[idx..], "#{ n }[#{ idx }..]")
+ assert_equal((n >> idx), n[idx...], "#{ n }[#{ idx }...#]")
+
+ # beginless
+ if idx >= 0 && n & ((1 << (idx + 1)) - 1) != 0
+ assert_raise(ArgumentError, "#{ n }[..#{ idx }]") { n[..idx] }
+ else
+ assert_equal(0, n[..idx], "#{ n }[..#{ idx }]")
+ end
+ if idx >= 0 && n & ((1 << idx) - 1) != 0
+ assert_raise(ArgumentError, "#{ n }[...#{ idx }]") { n[...idx] }
+ else
+ assert_equal(0, n[...idx], "#{ n }[...#{ idx }]")
+ end
+ end
+ end
+
# assert_equal(1, (1 << 0x40000000)[0x40000000], "[ruby-dev:31271]")
# assert_equal(0, (-1 << 0x40000001)[0x40000000], "[ruby-dev:31271]")
big_zero = 0x40000000.coerce(0)[0]
@@ -24,6 +56,31 @@ class TestInteger < Test::Unit::TestCase
rescue
nil
end, "[ruby-dev:32084] [ruby-dev:34547]")
+
+ assert_raise(ArgumentError) {2 ** -0x4000000000000000}
+
+ <<~EXPRS.each_line.with_index(__LINE__+1) do |expr, line|
+ crash01: 111r+11**-11111161111111
+ crash02: 1118111111111**-1111111111111111**1+1==11111
+ crash03: -1111111**-1111*11 - -11** -1111111
+ crash04: 1118111111111** -1111111111111111**1+11111111111**1 ===111
+ crash05: 11** -111155555555555555 -55 !=5-555
+ crash07: 1 + 111111111**-1111811111
+ crash08: 18111111111**-1111111111111111**1 + 1111111111**-1111**1
+ crash10: -7 - -1111111** -1111**11
+ crash12: 1118111111111** -1111111111111111**1 + 1111 - -1111111** -1111*111111111119
+ crash13: 1.0i - -11** -1111111
+ crash14: 11111**111111111**111111 * -11111111111111111111**-111111111111
+ crash15: ~1**1111 + -~1**~1**111
+ crash17: 11** -1111111**1111 /11i
+ crash18: 5555i**-5155 - -9111111**-1111**11
+ crash19: 111111*-11111111111111111111**-1111111111111111
+ crash20: 1111**111-11**-11111**11
+ crash21: 11**-10111111119-1i -1r
+ EXPRS
+ name, expr = expr.split(':', 2)
+ assert_ruby_status(%w"-W0", "begin; #{ expr }; rescue ArgumentError; end", name)
+ end
end
def test_lshift
@@ -31,11 +88,16 @@ class TestInteger < Test::Unit::TestCase
assert_equal(0, 1 << -0x40000001)
assert_equal(0, 1 << -0x80000000)
assert_equal(0, 1 << -0x80000001)
- # assert_equal(bdsize(0x80000000), (1 << 0x80000000).size)
+
+ char_bit = RbConfig::LIMITS["UCHAR_MAX"].bit_length
+ size_max = RbConfig::LIMITS["SIZE_MAX"]
+ size_bit_max = size_max * char_bit
+ assert_raise_with_message(RangeError, /shift width/) {
+ 1 << size_bit_max
+ }
end
def test_rshift
- # assert_equal(bdsize(0x40000001), (1 >> -0x40000001).size)
assert_predicate((1 >> 0x80000000), :zero?)
assert_predicate((1 >> 0xffffffff), :zero?)
assert_predicate((1 >> 0x100000000), :zero?)
@@ -75,23 +137,20 @@ class TestInteger < Test::Unit::TestCase
assert_equal(1234, Integer(1234))
assert_equal(1, Integer(1.234))
- # base argument
- assert_equal(1234, Integer("1234", 10))
- assert_equal(668, Integer("1234", 8))
- assert_equal(4660, Integer("1234", 16))
- assert_equal(49360, Integer("1234", 36))
- # decimal, not octal
- assert_equal(1234, Integer("01234", 10))
- assert_raise(ArgumentError) { Integer("0x123", 10) }
- assert_raise(ArgumentError) { Integer(1234, 10) }
- assert_raise(ArgumentError) { Integer(12.34, 10) }
- assert_raise(ArgumentError) { Integer(Object.new, 1) }
-
- assert_raise(ArgumentError) { Integer(1, 1, 1) }
-
assert_equal(2 ** 50, Integer(2.0 ** 50))
assert_raise(TypeError) { Integer(nil) }
+ bug14552 = '[ruby-core:85813]'
+ obj = Object.new
+ def obj.to_int; "str"; end
+ assert_raise(TypeError, bug14552) { Integer(obj) }
+ def obj.to_i; 42; end
+ assert_equal(42, Integer(obj), bug14552)
+
+ obj = Object.new
+ def obj.to_i; "str"; end
+ assert_raise(TypeError) { Integer(obj) }
+
bug6192 = '[ruby-core:43566]'
assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("utf-16be"))}
assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("utf-16le"))}
@@ -99,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
@@ -116,6 +177,94 @@ class TestInteger < Test::Unit::TestCase
end;
end
+ def test_Integer_with_invalid_exception
+ assert_raise(ArgumentError) {
+ Integer("0", exception: 1)
+ }
+ end
+
+ def test_Integer_with_exception_keyword
+ assert_nothing_raised(ArgumentError) {
+ assert_equal(nil, Integer("1z", exception: false))
+ }
+ assert_nothing_raised(ArgumentError) {
+ assert_equal(nil, Integer(Object.new, exception: false))
+ }
+ assert_nothing_raised(ArgumentError) {
+ o = Object.new
+ def o.to_i; 42.5; end
+ assert_equal(nil, Integer(o, exception: false))
+ }
+ assert_nothing_raised(ArgumentError) {
+ o = Object.new
+ def o.to_i; raise; end
+ assert_equal(nil, Integer(o, exception: false))
+ }
+ assert_nothing_raised(ArgumentError) {
+ o = Object.new
+ def o.to_int; raise; end
+ assert_equal(nil, Integer(o, exception: false))
+ }
+ assert_nothing_raised(FloatDomainError) {
+ assert_equal(nil, Integer(Float::INFINITY, exception: false))
+ }
+ assert_nothing_raised(FloatDomainError) {
+ assert_equal(nil, Integer(-Float::INFINITY, exception: false))
+ }
+ assert_nothing_raised(FloatDomainError) {
+ assert_equal(nil, Integer(Float::NAN, exception: false))
+ }
+
+ assert_raise(ArgumentError) {
+ Integer("1z", exception: true)
+ }
+ assert_raise(TypeError) {
+ Integer(nil, exception: true)
+ }
+ assert_nothing_raised(TypeError) {
+ assert_equal(nil, Integer(nil, exception: false))
+ }
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Integer;def method_missing(*);"";end;end
+ assert_equal(0, Integer("0", 2))
+ end;
+ end
+
+ def test_Integer_when_to_str
+ def (obj = Object.new).to_str
+ "0x10"
+ end
+ assert_equal(16, Integer(obj))
+ end
+
+ def test_Integer_with_base
+ assert_equal(1234, Integer("1234", 10))
+ assert_equal(668, Integer("1234", 8))
+ assert_equal(4660, Integer("1234", 16))
+ assert_equal(49360, Integer("1234", 36))
+ # decimal, not octal
+ assert_equal(1234, Integer("01234", 10))
+ assert_raise(ArgumentError) { Integer("0x123", 10) }
+ assert_raise(ArgumentError) { Integer(1234, 10) }
+ assert_raise(ArgumentError) { Integer(12.34, 10) }
+ assert_raise(ArgumentError) { Integer(Object.new, 1) }
+
+ assert_raise(ArgumentError) { Integer(1, 1, 1) }
+
+ def (base = Object.new).to_int
+ 8
+ end
+ assert_equal(8, Integer("10", base))
+
+ assert_raise(TypeError) { Integer("10", "8") }
+ def (base = Object.new).to_int
+ "8"
+ end
+ assert_raise(TypeError) { Integer("10", base) }
+ end
+
def test_int_p
assert_not_predicate(1.0, :integer?)
assert_predicate(1, :integer?)
@@ -129,35 +278,36 @@ class TestInteger < Test::Unit::TestCase
assert_equal("a", "a".ord.chr)
assert_raise(RangeError) { (-1).chr }
assert_raise(RangeError) { 0x100.chr }
+ assert_raise_with_message(RangeError, "3000000000 out of char range") { 3_000_000_000.chr }
end
def test_upto
a = []
- 1.upto(3) {|x| a << x }
+ assert_equal(1, 1.upto(3) {|x| a << x })
assert_equal([1, 2, 3], a)
a = []
- 1.upto(0) {|x| a << x }
+ assert_equal(1, 1.upto(0) {|x| a << x })
assert_equal([], a)
y = 2**30 - 1
a = []
- y.upto(y+2) {|x| a << x }
+ assert_equal(y, y.upto(y+2) {|x| a << x })
assert_equal([y, y+1, y+2], a)
end
def test_downto
a = []
- -1.downto(-3) {|x| a << x }
+ assert_equal(-1, -1.downto(-3) {|x| a << x })
assert_equal([-1, -2, -3], a)
a = []
- 1.downto(2) {|x| a << x }
+ assert_equal(1, 1.downto(2) {|x| a << x })
assert_equal([], a)
y = -(2**30)
a = []
- y.downto(y-2) {|x| a << x }
+ assert_equal(y, y.downto(y-2) {|x| a << x })
assert_equal([y, y-1, y-2], a)
end
@@ -167,6 +317,42 @@ class TestInteger < Test::Unit::TestCase
end
end
+ def test_times_bignum_redefine_plus_lt
+ assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
+ called = false
+ Integer.class_eval do
+ alias old_succ succ
+ undef succ
+ define_method(:succ){|x| called = true; x+1}
+ alias old_lt <
+ undef <
+ define_method(:<){|x| called = true}
+ end
+
+ fix = 1
+ fix.times{break 0}
+ fix_called = called
+
+ called = false
+
+ big = 2**65
+ big.times{break 0}
+ big_called = called
+
+ Integer.class_eval do
+ undef succ
+ alias succ old_succ
+ undef <
+ alias < old_lt
+ end
+
+ # Asssert that Fixnum and Bignum behave consistently
+ bug18377 = "[ruby-core:106361]"
+ assert_equal(fix_called, big_called, bug18377)
+ end;
+ end
+
def assert_int_equal(expected, result, mesg = nil)
assert_kind_of(Integer, result, mesg)
assert_equal(expected, result, mesg)
@@ -280,6 +466,10 @@ class TestInteger < Test::Unit::TestCase
assert_int_equal(1111_1111_1111_1111_1111_1111_1111_1111, 1111_1111_1111_1111_1111_1111_1111_1111.floor(1))
assert_int_equal(10**400, (10**400).floor(1))
+
+ assert_int_equal(-10000000000, -1.floor(-10), "[Bug #20654]")
+ assert_int_equal(-100000000000000000000, -1.floor(-20), "[Bug #20654]")
+ assert_int_equal(-100000000000000000000000000000000000000000000000000, -1.floor(-50), "[Bug #20654]")
end
def test_ceil
@@ -308,6 +498,10 @@ class TestInteger < Test::Unit::TestCase
assert_int_equal(1111_1111_1111_1111_1111_1111_1111_1111, 1111_1111_1111_1111_1111_1111_1111_1111.ceil(1))
assert_int_equal(10**400, (10**400).ceil(1))
+
+ assert_int_equal(10000000000, 1.ceil(-10), "[Bug #20654]")
+ assert_int_equal(100000000000000000000, 1.ceil(-20), "[Bug #20654]")
+ assert_int_equal(100000000000000000000000000000000000000000000000000, 1.ceil(-50), "[Bug #20654]")
end
def test_truncate
@@ -444,6 +638,8 @@ class TestInteger < Test::Unit::TestCase
assert_equal([0, 9, 8, 7, 6, 5, 4, 3, 2, 1], 1234567890.digits)
assert_equal([90, 78, 56, 34, 12], 1234567890.digits(100))
assert_equal([10, 5, 6, 8, 0, 10, 8, 6, 1], 1234567890.digits(13))
+ assert_equal((2 ** 1024).to_s(7).chars.map(&:to_i).reverse, (2 ** 1024).digits(7))
+ assert_equal([0] * 100 + [1], (2 ** (128 * 100)).digits(2 ** 128))
end
def test_digits_for_negative_numbers
@@ -509,11 +705,26 @@ class TestInteger < Test::Unit::TestCase
failures << n unless root*root <= n && (root+1)*(root+1) > n
end
assert_empty(failures, bug13440)
+
+ x = 0xffff_ffff_ffff_ffff
+ assert_equal(x, Integer.sqrt(x ** 2), "[ruby-core:95453]")
+ end
+
+ def test_bug_21217
+ assert_equal(0x10000 * 2**10, Integer.sqrt(0x100000008 * 2**20))
end
def test_fdiv
assert_equal(1.0, 1.fdiv(1))
assert_equal(0.5, 1.fdiv(2))
+
+ m = 50 << Float::MANT_DIG
+ prev = 1.0
+ (1..100).each do |i|
+ val = (m + i).fdiv(m)
+ assert_operator val, :>=, prev, "1+epsilon*(#{i}/100)"
+ prev = val
+ end
end
def test_obj_fdiv
@@ -525,4 +736,42 @@ class TestInteger < Test::Unit::TestCase
def o.fdiv(x); 1; end
assert_equal(1.0, 1.fdiv(o))
end
+
+ def test_try_convert
+ assert_equal(1, Integer.try_convert(1))
+ assert_equal(1, Integer.try_convert(1.0))
+ assert_nil Integer.try_convert("1")
+ o = Object.new
+ assert_nil Integer.try_convert(o)
+ def o.to_i; 1; end
+ assert_nil Integer.try_convert(o)
+ o = Object.new
+ def o.to_int; 1; end
+ assert_equal(1, Integer.try_convert(o))
+
+ o = Object.new
+ def o.to_int; Object.new; end
+ assert_raise_with_message(TypeError, /can't convert Object to Integer/) {Integer.try_convert(o)}
+ end
+
+ def test_ceildiv
+ assert_equal(0, 0.ceildiv(3))
+ assert_equal(1, 1.ceildiv(3))
+ assert_equal(1, 3.ceildiv(3))
+ assert_equal(2, 4.ceildiv(3))
+
+ assert_equal(-1, 4.ceildiv(-3))
+ assert_equal(-1, -4.ceildiv(3))
+ assert_equal(2, -4.ceildiv(-3))
+
+ assert_equal(3, 3.ceildiv(1.2))
+ assert_equal(3, 3.ceildiv(6/5r))
+
+ assert_equal(10, (10**100-11).ceildiv(10**99-1))
+ assert_equal(11, (10**100-9).ceildiv(10**99-1))
+
+ o = Object.new
+ def o.coerce(other); [other, 10]; end
+ assert_equal(124, 1234.ceildiv(o))
+ end
end
diff --git a/test/ruby/test_integer_comb.rb b/test/ruby/test_integer_comb.rb
index 1ad13dd31b..150f45cfd7 100644
--- a/test/ruby/test_integer_comb.rb
+++ b/test/ruby/test_integer_comb.rb
@@ -408,19 +408,32 @@ class TestIntegerComb < Test::Unit::TestCase
end
def test_remainder
+ coerce = EnvUtil.labeled_class("CoerceNum") do
+ def initialize(num)
+ @num = num
+ end
+ def coerce(other)
+ [other, @num]
+ end
+ def inspect
+ "#{self.class.name}(#@num)"
+ end
+ alias to_s inspect
+ end
+
VS.each {|a|
- VS.each {|b|
- if b == 0
+ (VS + VS.map {|b| [coerce.new(b), b]}).each {|b, i = b|
+ if i == 0
assert_raise(ZeroDivisionError) { a.divmod(b) }
else
- r = a.remainder(b)
+ r = assert_nothing_raised(ArgumentError, "#{a}.remainder(#{b})") {a.remainder(b)}
assert_kind_of(Integer, r)
if a < 0
- assert_operator(-b.abs, :<, r, "#{a}.remainder(#{b})")
+ assert_operator(-i.abs, :<, r, "#{a}.remainder(#{b})")
assert_operator(0, :>=, r, "#{a}.remainder(#{b})")
elsif 0 < a
assert_operator(0, :<=, r, "#{a}.remainder(#{b})")
- assert_operator(b.abs, :>, r, "#{a}.remainder(#{b})")
+ assert_operator(i.abs, :>, r, "#{a}.remainder(#{b})")
else
assert_equal(0, r, "#{a}.remainder(#{b})")
end
diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb
index 178e3c3d7e..1adf47ac51 100644
--- a/test/ruby/test_io.rb
+++ b/test/ruby/test_io.rb
@@ -49,8 +49,8 @@ class TestIO < Test::Unit::TestCase
end
flunk("timeout") unless wt.join(10) && rt.join(10)
ensure
- w.close unless !w || w.closed?
- r.close unless !r || r.closed?
+ w&.close
+ r&.close
(wt.kill; wt.join) if wt
(rt.kill; rt.join) if rt
raise we if we
@@ -62,8 +62,8 @@ class TestIO < Test::Unit::TestCase
begin
yield r, w
ensure
- r.close unless r.closed?
- w.close unless w.closed?
+ r.close
+ w.close
end
end
@@ -84,12 +84,17 @@ class TestIO < Test::Unit::TestCase
}
end
- def trapping_usr1
- @usr1_rcvd = 0
- trap(:USR1) { @usr1_rcvd += 1 }
- yield
+ def trapping_usr2
+ @usr2_rcvd = 0
+ r, w = IO.pipe
+ trap(:USR2) do
+ w.write([@usr2_rcvd += 1].pack('L'))
+ end
+ yield r
ensure
- trap(:USR1, "DEFAULT")
+ trap(:USR2, "DEFAULT")
+ w&.close
+ r&.close
end
def test_pipe
@@ -108,6 +113,65 @@ class TestIO < Test::Unit::TestCase
].each{|thr| thr.join}
end
+ def test_binmode_pipe
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ EnvUtil.with_default_external(Encoding::UTF_8) do
+ begin
+ reader0, writer0 = IO.pipe
+ reader0.binmode
+ writer0.binmode
+
+ reader1, writer1 = IO.pipe
+
+ reader2, writer2 = IO.pipe(binmode: true)
+ assert_predicate writer0, :binmode?
+ assert_predicate writer2, :binmode?
+ assert_equal writer0.binmode?, writer2.binmode?
+ assert_equal writer0.external_encoding, writer2.external_encoding
+ assert_equal writer0.internal_encoding, writer2.internal_encoding
+ assert_predicate reader0, :binmode?
+ assert_predicate reader2, :binmode?
+ assert_equal reader0.binmode?, reader2.binmode?
+ assert_equal reader0.external_encoding, reader2.external_encoding
+ assert_equal reader0.internal_encoding, reader2.internal_encoding
+
+ reader3, writer3 = IO.pipe("UTF-8:UTF-8", binmode: true)
+ assert_predicate writer3, :binmode?
+ assert_equal writer1.external_encoding, writer3.external_encoding
+ assert_equal writer1.internal_encoding, writer3.internal_encoding
+ assert_predicate reader3, :binmode?
+ assert_equal reader1.external_encoding, reader3.external_encoding
+ assert_equal reader1.internal_encoding, reader3.internal_encoding
+
+ reader4, writer4 = IO.pipe("UTF-8:UTF-8", binmode: true)
+ assert_predicate writer4, :binmode?
+ assert_equal writer1.external_encoding, writer4.external_encoding
+ assert_equal writer1.internal_encoding, writer4.internal_encoding
+ assert_predicate reader4, :binmode?
+ assert_equal reader1.external_encoding, reader4.external_encoding
+ assert_equal reader1.internal_encoding, reader4.internal_encoding
+
+ reader5, writer5 = IO.pipe("UTF-8", "UTF-8", binmode: true)
+ assert_predicate writer5, :binmode?
+ assert_equal writer1.external_encoding, writer5.external_encoding
+ assert_equal writer1.internal_encoding, writer5.internal_encoding
+ assert_predicate reader5, :binmode?
+ assert_equal reader1.external_encoding, reader5.external_encoding
+ assert_equal reader1.internal_encoding, reader5.internal_encoding
+ ensure
+ [
+ reader0, writer0,
+ reader1, writer1,
+ reader2, writer2,
+ reader3, writer3,
+ reader4, writer4,
+ reader5, writer5,
+ ].compact.map(&:close)
+ end
+ end
+ end
+ end
+
def test_pipe_block
x = nil
ret = IO.pipe {|r, w|
@@ -248,7 +312,7 @@ class TestIO < Test::Unit::TestCase
w.print "a\n\nb\n\n"
w.close
end, proc do |r|
- assert_equal "a\n\nb\n", r.gets(nil, chomp: true)
+ assert_equal("a\n\nb\n\n", r.gets(nil, chomp: true), "[Bug #18770]")
assert_nil r.gets("")
r.close
end)
@@ -286,6 +350,19 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_ungetc_with_seek
+ make_tempfile {|t|
+ t.open
+ t.write('0123456789')
+ t.rewind
+
+ t.ungetc('a')
+ t.seek(2, :SET)
+
+ assert_equal('2', t.getc)
+ }
+ end
+
def test_ungetbyte
make_tempfile {|t|
t.open
@@ -309,6 +386,19 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_ungetbyte_with_seek
+ make_tempfile {|t|
+ t.open
+ t.write('0123456789')
+ t.rewind
+
+ t.ungetbyte('a'.ord)
+ t.seek(2, :SET)
+
+ assert_equal('2'.ord, t.getbyte)
+ }
+ end
+
def test_each_byte
pipe(proc do |w|
w << "abc def"
@@ -330,6 +420,24 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_each_byte_closed
+ pipe(proc do |w|
+ w << "abc def"
+ w.close
+ end, proc do |r|
+ assert_raise(IOError) do
+ r.each_byte {|byte| r.close if byte == 32 }
+ end
+ end)
+ make_tempfile {|t|
+ File.open(t, 'rt') {|f|
+ assert_raise(IOError) do
+ f.each_byte {|c| f.close if c == 10}
+ end
+ }
+ }
+ end
+
def test_each_codepoint
make_tempfile {|t|
bug2959 = '[ruby-core:28650]'
@@ -341,16 +449,39 @@ class TestIO < Test::Unit::TestCase
}
end
- def test_codepoints
+ def test_each_codepoint_closed
+ pipe(proc do |w|
+ w.print("abc def")
+ w.close
+ end, proc do |r|
+ assert_raise(IOError) do
+ r.each_codepoint {|c| r.close if c == 32}
+ end
+ end)
make_tempfile {|t|
- bug2959 = '[ruby-core:28650]'
- a = ""
File.open(t, 'rt') {|f|
- assert_warn(/deprecated/) {
- f.codepoints {|c| a << c}
- }
+ assert_raise(IOError) do
+ f.each_codepoint {|c| f.close if c == 10}
+ end
}
- assert_equal("foo\nbar\nbaz\n", a, bug2959)
+ }
+ 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
@@ -359,7 +490,7 @@ class TestIO < Test::Unit::TestCase
path = t.path
t.close!
assert_raise(Errno::ENOENT, "[ruby-dev:33072]") do
- File.read(path, nil, nil, {})
+ File.read(path, nil, nil, **{})
end
end
@@ -389,6 +520,18 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_copy_stream_append_to_nonempty
+ with_srccontent("foobar") {|src, content|
+ preface = 'preface'
+ File.write('dst', preface)
+ File.open('dst', 'ab') do |dst|
+ ret = IO.copy_stream(src, dst)
+ assert_equal(content.bytesize, ret)
+ assert_equal(preface + content, File.read("dst"))
+ end
+ }
+ end
+
def test_copy_stream_smaller
with_srccontent {|src, content|
@@ -556,14 +699,13 @@ class TestIO < Test::Unit::TestCase
if have_nonblock?
def test_copy_stream_no_busy_wait
+ omit "multiple threads already active" if Thread.list.size > 1
+
msg = 'r58534 [ruby-core:80969] [Backport #13533]'
IO.pipe do |r,w|
r.nonblock = true
- assert_cpu_usage_low(msg) do
- th = Thread.new { IO.copy_stream(r, IO::NULL) }
- sleep 0.1
- w.close
- th.join
+ assert_cpu_usage_low(msg, stop: ->{w.close}) do
+ IO.copy_stream(r, IO::NULL)
end
end
end
@@ -576,8 +718,7 @@ class TestIO < Test::Unit::TestCase
begin
w2.nonblock = true
rescue Errno::EBADF
- skip "nonblocking IO for pipe is not implemented"
- break
+ omit "nonblocking IO for pipe is not implemented"
end
s = w2.syswrite("a" * 100000)
t = Thread.new { sleep 0.1; r2.read }
@@ -643,7 +784,7 @@ class TestIO < Test::Unit::TestCase
assert_equal(bigcontent[30, 40], File.read("bigdst"))
assert_equal(0, f.pos)
rescue NotImplementedError
- #skip "pread(2) is not implemtented."
+ #skip "pread(2) is not implemented."
end
}
}
@@ -681,7 +822,7 @@ class TestIO < Test::Unit::TestCase
r1.nonblock = true
w2.nonblock = true
rescue Errno::EBADF
- skip "nonblocking IO for pipe is not implemented"
+ omit "nonblocking IO for pipe is not implemented"
end
t1 = Thread.new { w1 << megacontent; w1.close }
t2 = Thread.new { r2.read }
@@ -745,7 +886,7 @@ class TestIO < Test::Unit::TestCase
assert_equal("bcd", r.read)
end)
rescue NotImplementedError
- skip "pread(2) is not implemtented."
+ omit "pread(2) is not implemtented."
end
}
end
@@ -802,6 +943,10 @@ class TestIO < Test::Unit::TestCase
end if defined? UNIXSocket
def test_copy_stream_socket4
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
with_bigsrc {|bigsrc, bigcontent|
File.open(bigsrc) {|f|
assert_equal(0, f.pos)
@@ -818,9 +963,13 @@ class TestIO < Test::Unit::TestCase
}
}
}
- end if defined? UNIXSocket
+ end
def test_copy_stream_socket5
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
with_bigsrc {|bigsrc, bigcontent|
File.open(bigsrc) {|f|
assert_equal(bigcontent[0,100], f.read(100))
@@ -838,9 +987,13 @@ class TestIO < Test::Unit::TestCase
}
}
}
- end if defined? UNIXSocket
+ end
def test_copy_stream_socket6
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
mkcdtmpdir {
megacontent = "abc" * 1234567
File.open("megasrc", "w") {|f| f << megacontent }
@@ -849,7 +1002,7 @@ class TestIO < Test::Unit::TestCase
begin
s1.nonblock = true
rescue Errno::EBADF
- skip "nonblocking IO for pipe is not implemented"
+ omit "nonblocking IO for pipe is not implemented"
end
t1 = Thread.new { s2.read }
t2 = Thread.new {
@@ -861,9 +1014,13 @@ class TestIO < Test::Unit::TestCase
assert_equal(megacontent, result)
}
}
- end if defined? UNIXSocket
+ end
def test_copy_stream_socket7
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
GC.start
mkcdtmpdir {
megacontent = "abc" * 1234567
@@ -873,22 +1030,23 @@ class TestIO < Test::Unit::TestCase
begin
s1.nonblock = true
rescue Errno::EBADF
- skip "nonblocking IO for pipe is not implemented"
+ omit "nonblocking IO for pipe is not implemented"
end
- trapping_usr1 do
+ trapping_usr2 do |rd|
nr = 30
begin
pid = fork do
s1.close
IO.select([s2])
- Process.kill(:USR1, Process.ppid)
- s2.read
+ Process.kill(:USR2, Process.ppid)
+ buf = String.new(capacity: 16384)
+ nil while s2.read(16384, buf)
end
s2.close
nr.times do
assert_equal megacontent.bytesize, IO.copy_stream("megasrc", s1)
end
- assert_equal(1, @usr1_rcvd)
+ assert_equal(1, rd.read(4).unpack1('L'))
ensure
s1.close
_, status = Process.waitpid2(pid) if pid
@@ -897,7 +1055,7 @@ class TestIO < Test::Unit::TestCase
end
}
}
- end if defined? UNIXSocket and IO.method_defined?("nonblock=")
+ end
def test_copy_stream_strio
src = StringIO.new("abcd")
@@ -1001,6 +1159,34 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_copy_stream_dup_buffer
+ bug21131 = '[ruby-core:120961] [Bug #21131]'
+ mkcdtmpdir do
+ dst_class = Class.new do
+ def initialize(&block)
+ @block = block
+ end
+
+ def write(data)
+ @block.call(data.dup)
+ data.bytesize
+ end
+ end
+
+ rng = Random.new(42)
+ body = Tempfile.new("ruby-bug", binmode: true)
+ body.write(rng.bytes(16_385))
+ body.rewind
+
+ payload = []
+ IO.copy_stream(body, dst_class.new{payload << it})
+ body.rewind
+ assert_equal(body.read, payload.join, bug21131)
+ ensure
+ body&.close
+ end
+ end
+
def test_copy_stream_write_in_binmode
bug8767 = '[ruby-core:56518] [Bug #8767]'
mkcdtmpdir {
@@ -1192,10 +1378,11 @@ class TestIO < Test::Unit::TestCase
def test_copy_stream_to_duplex_io
result = IO.pipe {|a,w|
- Thread.start {w.puts "yes"; w.close}
+ th = Thread.start {w.puts "yes"; w.close}
IO.popen([EnvUtil.rubybin, '-pe$_="#$.:#$_"'], "r+") {|b|
IO.copy_stream(a, b)
b.close_write
+ assert_join_threads([th])
b.read
}
}
@@ -1208,7 +1395,7 @@ class TestIO < Test::Unit::TestCase
opts = {}
if defined?(Process::RLIMIT_NPROC)
lim = Process.getrlimit(Process::RLIMIT_NPROC)[1]
- opts[:rlimit_nproc] = [lim, 1024].min
+ opts[:rlimit_nproc] = [lim, 2048].min
end
f = IO.popen([ruby] + args, 'r+', opts)
pid = f.pid
@@ -1296,6 +1483,13 @@ class TestIO < Test::Unit::TestCase
assert_empty(err)
end
+ def test_write_no_args
+ IO.pipe do |r, w|
+ assert_equal 0, w.write, '[ruby-core:86285] [Bug #14338]'
+ assert_equal :wait_readable, r.read_nonblock(1, exception: false)
+ end
+ end
+
def test_write_non_writable
with_pipe do |r, w|
assert_raise(IOError) do
@@ -1323,7 +1517,7 @@ class TestIO < Test::Unit::TestCase
def test_dup_many
opts = {}
opts[:rlimit_nofile] = 1024 if defined?(Process::RLIMIT_NOFILE)
- assert_separately([], <<-'End', opts)
+ assert_separately([], <<-'End', **opts)
a = []
assert_raise(Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM) do
loop {a << IO.pipe}
@@ -1334,6 +1528,16 @@ class TestIO < Test::Unit::TestCase
End
end
+ def test_dup_timeout
+ with_pipe do |r, w|
+ r.timeout = 0.1
+ r2 = r.dup
+ assert_equal(0.1, r2.timeout)
+ ensure
+ r2&.close
+ end
+ end
+
def test_inspect
with_pipe do |r, w|
assert_match(/^#<IO:fd \d+>$/, r.inspect)
@@ -1387,6 +1591,13 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_readpartial_zero_size
+ File.open(IO::NULL) do |r|
+ assert_empty(r.readpartial(0, s = "01234567"))
+ assert_empty(s)
+ end
+ end
+
def test_readpartial_buffer_error
with_pipe do |r, w|
s = ""
@@ -1432,6 +1643,13 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_read_zero_size
+ File.open(IO::NULL) do |r|
+ assert_empty(r.read(0, s = "01234567"))
+ assert_empty(s)
+ end
+ end
+
def test_read_buffer_error
with_pipe do |r, w|
s = ""
@@ -1469,6 +1687,35 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_read_nonblock_zero_size
+ File.open(IO::NULL) do |r|
+ assert_empty(r.read_nonblock(0, s = "01234567"))
+ assert_empty(s)
+ end
+ end
+
+ def test_read_nonblock_file
+ make_tempfile do |path|
+ File.open(path, 'r') do |file|
+ file.read_nonblock(4)
+ end
+ end
+ end
+
+ def test_write_nonblock_file
+ make_tempfile do |path|
+ File.open(path, 'w') do |file|
+ file.write_nonblock("Ruby")
+ end
+ end
+ end
+
+ def test_explicit_path
+ io = IO.for_fd(0, path: "Fake Path", autoclose: false)
+ assert_match %r"Fake Path", io.inspect
+ assert_equal "Fake Path", io.path
+ end
+
def test_write_nonblock_simple_no_exceptions
pipe(proc do |w|
w.write_nonblock('1', exception: false)
@@ -1496,6 +1743,12 @@ class TestIO < Test::Unit::TestCase
}
end if have_nonblock?
+ def test_read_nonblock_invalid_exception
+ with_pipe {|r, w|
+ assert_raise(ArgumentError) {r.read_nonblock(4096, exception: 1)}
+ }
+ end if have_nonblock?
+
def test_read_nonblock_no_exceptions
with_pipe {|r, w|
assert_equal :wait_readable, r.read_nonblock(4096, exception: false)
@@ -1531,6 +1784,12 @@ class TestIO < Test::Unit::TestCase
}
end if have_nonblock?
+ def test_write_nonblock_invalid_exception
+ with_pipe {|r, w|
+ assert_raise(ArgumentError) {w.write_nonblock(4096, exception: 1)}
+ }
+ end if have_nonblock?
+
def test_write_nonblock_no_exceptions
with_pipe {|r, w|
loop {
@@ -1691,23 +1950,167 @@ class TestIO < Test::Unit::TestCase
f.gets; assert_equal(3, $.)
end
SRC
+ }
+ end
- pipe(proc do |w|
- w.puts "foo"
- w.puts "bar"
- w.puts "baz"
- w.close
- end, proc do |r|
- r.gets; assert_equal(1, $.)
- r.gets; assert_equal(2, $.)
- r.lineno = 1000; assert_equal(2, $.)
- r.gets; assert_equal(1001, $.)
- r.gets; assert_equal(1001, $.)
- end)
+ def test_set_lineno_gets
+ pipe(proc do |w|
+ w.puts "foo"
+ w.puts "bar"
+ w.puts "baz"
+ w.close
+ end, proc do |r|
+ r.gets; assert_equal(1, $.)
+ r.gets; assert_equal(2, $.)
+ r.lineno = 1000; assert_equal(2, $.)
+ r.gets; assert_equal(1001, $.)
+ r.gets; assert_equal(1001, $.)
+ end)
+ end
+
+ def test_readline_bad_param_raises
+ File.open(__FILE__) do |f|
+ assert_raise(TypeError) do
+ f.readline Object.new
+ end
+ end
+
+ File.open(__FILE__) do |f|
+ assert_raise(TypeError) do
+ f.readline 1, 2
+ end
+ end
+ end
+
+ def test_readline_raises
+ File.open(__FILE__) do |f|
+ assert_equal File.read(__FILE__), f.readline(nil)
+ assert_raise(EOFError) do
+ f.readline
+ end
+ end
+ end
+
+ def test_readline_separators
+ File.open(__FILE__) do |f|
+ line = f.readline("def")
+ assert_equal File.read(__FILE__)[/\A.*?def/m], line
+ end
+
+ File.open(__FILE__) do |f|
+ line = f.readline("def", chomp: true)
+ assert_equal File.read(__FILE__)[/\A.*?(?=def)/m], line
+ end
+ end
+
+ def test_readline_separators_limits
+ t = Tempfile.open("readline_limit")
+ str = "#" * 50
+ sep = "def"
+
+ t.write str
+ t.write sep
+ t.write str
+ t.flush
+
+ # over limit
+ File.open(t.path) do |f|
+ line = f.readline sep, str.bytesize
+ assert_equal(str, line)
+ end
+
+ # under limit
+ File.open(t.path) do |f|
+ line = f.readline(sep, str.bytesize + 5)
+ assert_equal(str + sep, line)
+ end
+
+ # under limit + chomp
+ File.open(t.path) do |f|
+ line = f.readline(sep, str.bytesize + 5, chomp: true)
+ assert_equal(str, line)
+ end
+ ensure
+ t&.close!
+ end
+
+ def test_readline_limit_without_separator
+ t = Tempfile.open("readline_limit")
+ str = "#" * 50
+ sep = "\n"
+
+ t.write str
+ t.write sep
+ t.write str
+ t.flush
+
+ # over limit
+ File.open(t.path) do |f|
+ line = f.readline str.bytesize
+ assert_equal(str, line)
+ end
+
+ # under limit
+ File.open(t.path) do |f|
+ line = f.readline(str.bytesize + 5)
+ assert_equal(str + sep, line)
+ end
+
+ # under limit + chomp
+ File.open(t.path) do |f|
+ line = f.readline(str.bytesize + 5, chomp: true)
+ assert_equal(str, line)
+ end
+ ensure
+ t&.close!
+ end
+
+ def test_readline_chomp_true
+ File.open(__FILE__) do |f|
+ line = f.readline(chomp: true)
+ assert_equal File.readlines(__FILE__).first.chomp, line
+ end
+ end
+
+ def test_readline_incompatible_rs
+ first_line = File.open(__FILE__, &:gets).encode("utf-32le")
+ File.open(__FILE__, encoding: "utf-8:utf-32le") {|f|
+ assert_equal first_line, f.readline
+ assert_raise(ArgumentError) {f.readline("\0")}
}
end
- def test_readline
+ def test_readline_limit_nonascii
+ mkcdtmpdir do
+ i = 0
+
+ File.open("text#{i+=1}", "w+:utf-8") do |f|
+ f.write("Test\nok\u{bf}ok\n")
+ f.rewind
+
+ assert_equal("Test\nok\u{bf}", f.readline("\u{bf}"))
+ assert_equal("ok\n", f.readline("\u{bf}"))
+ end
+
+ File.open("text#{i+=1}", "w+b:utf-32le") do |f|
+ f.write("0123456789")
+ f.rewind
+
+ assert_equal(4, f.readline(4).bytesize)
+ assert_equal(4, f.readline(3).bytesize)
+ end
+
+ File.open("text#{i+=1}", "w+:utf-8:utf-32le") do |f|
+ f.write("0123456789")
+ f.rewind
+
+ assert_equal(4, f.readline(4).bytesize)
+ assert_equal(4, f.readline(3).bytesize)
+ end
+ end
+ end
+
+ def test_set_lineno_readline
pipe(proc do |w|
w.puts "foo"
w.puts "bar"
@@ -1735,8 +2138,7 @@ class TestIO < Test::Unit::TestCase
end)
end
- def test_lines
- verbose, $VERBOSE = $VERBOSE, nil
+ def test_each_line
pipe(proc do |w|
w.puts "foo"
w.puts "bar"
@@ -1744,20 +2146,31 @@ class TestIO < Test::Unit::TestCase
w.close
end, proc do |r|
e = nil
- assert_warn(/deprecated/) {
- e = r.lines
+ assert_warn('') {
+ e = r.each_line
}
assert_equal("foo\n", e.next)
assert_equal("bar\n", e.next)
assert_equal("baz\n", e.next)
assert_raise(StopIteration) { e.next }
end)
- ensure
- $VERBOSE = verbose
+
+ pipe(proc do |w|
+ w.write "foo\n"
+ w.close
+ end, proc do |r|
+ assert_equal(["foo\n"], r.each_line(nil, chomp: true).to_a, "[Bug #18770]")
+ end)
+
+ pipe(proc do |w|
+ w.write "foo\n"
+ w.close
+ end, proc do |r|
+ assert_equal(["fo", "o\n"], r.each_line(nil, 2, chomp: true).to_a, "[Bug #18770]")
+ end)
end
- def test_bytes
- verbose, $VERBOSE = $VERBOSE, nil
+ def test_each_byte2
pipe(proc do |w|
w.binmode
w.puts "foo"
@@ -1766,20 +2179,17 @@ class TestIO < Test::Unit::TestCase
w.close
end, proc do |r|
e = nil
- assert_warn(/deprecated/) {
- e = r.bytes
+ assert_warn('') {
+ e = r.each_byte
}
(%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c|
assert_equal(c.ord, e.next)
end
assert_raise(StopIteration) { e.next }
end)
- ensure
- $VERBOSE = verbose
end
- def test_chars
- verbose, $VERBOSE = $VERBOSE, nil
+ def test_each_char2
pipe(proc do |w|
w.puts "foo"
w.puts "bar"
@@ -1787,16 +2197,14 @@ class TestIO < Test::Unit::TestCase
w.close
end, proc do |r|
e = nil
- assert_warn(/deprecated/) {
- e = r.chars
+ assert_warn('') {
+ e = r.each_char
}
(%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c|
assert_equal(c, e.next)
end
assert_raise(StopIteration) { e.next }
end)
- ensure
- $VERBOSE = verbose
end
def test_readbyte
@@ -1861,7 +2269,6 @@ class TestIO < Test::Unit::TestCase
def test_pos
make_tempfile {|t|
-
open(t.path, IO::RDWR|IO::CREAT|IO::TRUNC, 0600) do |f|
f.write "Hello"
assert_equal(5, f.pos)
@@ -2066,6 +2473,14 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_sysread_with_negative_length
+ make_tempfile {|t|
+ open(t.path) do |f|
+ assert_raise(ArgumentError) { f.sysread(-1) }
+ end
+ }
+ end
+
def test_flag
make_tempfile {|t|
assert_raise(ArgumentError) do
@@ -2154,12 +2569,12 @@ class TestIO < Test::Unit::TestCase
t.close
rescue Errno::EBADF
end
- skip "expect IO object was GC'ed but not recycled yet"
+ omit "expect IO object was GC'ed but not recycled yet"
rescue WeakRef::RefError
assert_raise(Errno::EBADF, feature2250) {t.close}
end
ensure
- t.close!
+ t&.close!
end
def test_autoclose_false_closed_by_finalizer
@@ -2170,7 +2585,7 @@ class TestIO < Test::Unit::TestCase
begin
w.close
t.close
- skip "expect IO object was GC'ed but not recycled yet"
+ omit "expect IO object was GC'ed but not recycled yet"
rescue WeakRef::RefError
assert_nothing_raised(Errno::EBADF, feature2250) {t.close}
end
@@ -2189,27 +2604,30 @@ class TestIO < Test::Unit::TestCase
assert_equal(o, o2)
end
- def test_open_pipe
- open("|" + EnvUtil.rubybin, "r+") do |f|
- f.puts "puts 'foo'"
- f.close_write
- assert_equal("foo\n", f.read)
+ def test_open_redirect_keyword
+ o = Object.new
+ def o.to_open(**kw); kw; end
+ assert_equal({:a=>1}, open(o, a: 1))
+
+ assert_raise(ArgumentError) { open(o, {a: 1}) }
+
+ class << o
+ remove_method(:to_open)
end
+ def o.to_open(kw); kw; end
+ assert_equal({:a=>1}, open(o, a: 1))
+ assert_equal({:a=>1}, open(o, {a: 1}))
end
- def test_read_command
- assert_equal("foo\n", IO.read("|echo foo"))
- assert_warn(/invoke external command/) do
- File.read("|#{EnvUtil.rubybin} -e puts")
- end
- assert_warn(/invoke external command/) 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")
+ def test_path_with_pipe
+ mkcdtmpdir do
+ cmd = "|echo foo"
+ assert_file.not_exist?(cmd)
+
+ 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
@@ -2254,13 +2672,13 @@ class TestIO < Test::Unit::TestCase
def test_reopen_inherit
mkcdtmpdir {
- system(EnvUtil.rubybin, '-e', <<"End")
+ system(EnvUtil.rubybin, '-e', <<-"End")
f = open("out", "w")
STDOUT.reopen(f)
STDERR.reopen(f)
system(#{EnvUtil.rubybin.dump}, '-e', 'STDOUT.print "out"')
system(#{EnvUtil.rubybin.dump}, '-e', 'STDERR.print "err"')
-End
+ End
assert_equal("outerr", File.read("out"))
}
end
@@ -2312,6 +2730,17 @@ End
}
end
+ def test_reopen_binmode
+ f1 = File.open(__FILE__)
+ f2 = File.open(__FILE__)
+ f1.binmode
+ f1.reopen(f2)
+ assert_not_operator(f1, :binmode?)
+ ensure
+ f2.close
+ f1.close
+ end
+
def make_tempfile_for_encoding
t = make_tempfile
open(t.path, "rb+:utf-8") {|f| f.puts "\u7d05\u7389bar\n"}
@@ -2321,7 +2750,7 @@ End
t
end
ensure
- t.close(true) if t and block_given?
+ t&.close(true) if block_given?
end
def test_reopen_encoding
@@ -2342,6 +2771,16 @@ End
}
end
+ def test_reopen_encoding_from_io
+ f1 = File.open(__FILE__, "rb:UTF-16LE")
+ f2 = File.open(__FILE__, "r:UTF-8")
+ f1.reopen(f2)
+ assert_equal(Encoding::UTF_8, f1.external_encoding)
+ ensure
+ f2.close
+ f1.close
+ end
+
def test_reopen_opt_encoding
feature7103 = '[ruby-core:47806]'
make_tempfile_for_encoding {|t|
@@ -2381,30 +2820,33 @@ End
end
end
- def test_foreach
- a = []
- IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :foo; puts :bar; puts :baz'") {|x| a << x }
- assert_equal(["foo\n", "bar\n", "baz\n"], a)
-
- a = []
- IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :zot'", :open_args => ["r"]) {|x| a << x }
- assert_equal(["zot\n"], a)
+ def test_reopen_ivar
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ f = File.open(IO::NULL)
+ f.instance_variable_set(:@foo, 42)
+ f.reopen(STDIN)
+ f.instance_variable_defined?(:@foo)
+ f.instance_variable_get(:@foo)
+ end;
+ end
+ def test_foreach
make_tempfile {|t|
a = []
IO.foreach(t.path) {|x| a << x }
assert_equal(["foo\n", "bar\n", "baz\n"], a)
a = []
- IO.foreach(t.path, {:mode => "r" }) {|x| a << x }
+ IO.foreach(t.path, :mode => "r") {|x| a << x }
assert_equal(["foo\n", "bar\n", "baz\n"], a)
a = []
- IO.foreach(t.path, {:open_args => [] }) {|x| a << x }
+ IO.foreach(t.path, :open_args => []) {|x| a << x }
assert_equal(["foo\n", "bar\n", "baz\n"], a)
a = []
- IO.foreach(t.path, {:open_args => ["r"] }) {|x| a << x }
+ IO.foreach(t.path, :open_args => ["r"]) {|x| a << x }
assert_equal(["foo\n", "bar\n", "baz\n"], a)
a = []
@@ -2422,6 +2864,8 @@ End
bug = '[ruby-dev:31525]'
assert_raise(ArgumentError, bug) {IO.foreach}
+ assert_raise(ArgumentError, "[Bug #18767] [ruby-core:108499]") {IO.foreach(__FILE__, 0){}}
+
a = nil
assert_nothing_raised(ArgumentError, bug) {a = IO.foreach(t.path).to_a}
assert_equal(["foo\n", "bar\n", "baz\n"], a, bug)
@@ -2430,6 +2874,8 @@ End
assert_raise_with_message(IOError, /not opened for reading/, bug6054) do
IO.foreach(t.path, mode:"w").next
end
+
+ assert_raise(ArgumentError, "[Bug #18771] [ruby-core:108503]") {IO.foreach(t, "\n", 10, true){}}
}
end
@@ -2439,6 +2885,7 @@ End
assert_equal(["foo\nb", "ar\nb", "az\n"], IO.readlines(t.path, "b"))
assert_equal(["fo", "o\n", "ba", "r\n", "ba", "z\n"], IO.readlines(t.path, 2))
assert_equal(["fo", "o\n", "b", "ar", "\nb", "az", "\n"], IO.readlines(t.path, "b", 2))
+ assert_raise(ArgumentError, "[Bug #18771] [ruby-core:108503]") {IO.readlines(t, "\n", 10, true){}}
}
end
@@ -2460,11 +2907,13 @@ End
end
def test_print_separators
- $, = ':'
- $\ = "\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')
- w.print('a','b','c')
+ EnvUtil.suppress_warning {w.print('a','b','c')}
w.close
end, proc do |r|
assert_equal("a\n", r.gets)
@@ -2502,7 +2951,7 @@ End
end
def test_puts_parallel
- skip "not portable"
+ omit "not portable"
pipe(proc do |w|
threads = []
100.times do
@@ -2522,7 +2971,7 @@ End
end
capture.clear
- assert_warning(/[.#]write is outdated/) do
+ assert_deprecated_warning(/[.#]write is outdated/) do
stdout, $stdout = $stdout, capture
puts "hey"
ensure
@@ -2547,7 +2996,8 @@ End
assert_in_out_err([], "$> = $stderr\nputs 'foo'", [], %w(foo))
- assert_separately(%w[-Eutf-8], <<-"end;") # do
+ assert_separately(%w[-Eutf-8], "#{<<~"begin;"}\n#{<<~"end;"}")
+ begin;
alias $\u{6a19 6e96 51fa 529b} $stdout
x = eval("class X\u{307b 3052}; self; end".encode("euc-jp"))
assert_raise_with_message(TypeError, /\\$\u{6a19 6e96 51fa 529b} must.*, X\u{307b 3052} given/) do
@@ -2560,7 +3010,6 @@ End
return unless defined?(Fcntl::F_GETFL)
make_tempfile {|t|
-
fd = IO.sysopen(t.path, "w")
assert_kind_of(Integer, fd)
%w[r r+ w+ a+].each do |mode|
@@ -2571,6 +3020,15 @@ End
f.close
assert_equal("FOO\n", File.read(t.path))
+
+ fd = IO.sysopen(t.path)
+ %w[w r+ w+ a+].each do |mode|
+ assert_raise(Errno::EINVAL, "#{mode} [ruby-dev:38571]") {IO.new(fd, mode)}
+ end
+ f = IO.new(fd, "r")
+ data = f.read
+ f.close
+ assert_equal("FOO\n", data)
}
end
@@ -2623,6 +3081,9 @@ End
assert_equal("foo\nbar\nbaz\n", File.read(t.path))
assert_equal("foo\nba", File.read(t.path, 6))
assert_equal("bar\n", File.read(t.path, 4, 4))
+
+ assert_raise(ArgumentError) { File.read(t.path, -1) }
+ assert_raise(ArgumentError) { File.read(t.path, 1, -1) }
}
end
@@ -2648,13 +3109,6 @@ End
}
end if /freebsd|linux/ =~ RUBY_PLATFORM and defined? File::NOFOLLOW
- def test_tainted
- make_tempfile {|t|
- assert_predicate(File.read(t.path, 4), :tainted?, '[ruby-dev:38826]')
- assert_predicate(File.open(t.path) {|f| f.read(4)}, :tainted?, '[ruby-dev:38826]')
- }
- end
-
def test_binmode_after_closed
make_tempfile {|t|
assert_raise(IOError) {t.binmode}
@@ -2678,7 +3132,7 @@ __END__
end;
10.times.map do
Thread.start do
- assert_in_out_err([], src) {|stdout, stderr|
+ assert_in_out_err([], src, timeout: 20) {|stdout, stderr|
assert_no_match(/hi.*hi/, stderr.join, bug3585)
}
end
@@ -2686,7 +3140,6 @@ __END__
end
def test_flush_in_finalizer1
- require 'tempfile'
bug3910 = '[ruby-dev:42341]'
tmp = Tempfile.open("bug3910") {|t|
path = t.path
@@ -2712,9 +3165,8 @@ __END__
end
def test_flush_in_finalizer2
- require 'tempfile'
bug3910 = '[ruby-dev:42341]'
- Tempfile.open("bug3910") {|t|
+ Tempfile.create("bug3910") {|t|
path = t.path
t.close
begin
@@ -2733,7 +3185,6 @@ __END__
end
}
end
- t.close!
}
end
@@ -2846,7 +3297,7 @@ __END__
end
def test_fcntl_lock_linux
- pad=0
+ pad = 0
Tempfile.create(self.class.name) do |f|
r, w = IO.pipe
pid = fork do
@@ -2939,6 +3390,8 @@ __END__
end
def test_cross_thread_close_stdio
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
assert_separately([], <<-'end;')
IO.pipe do |r,w|
$stdin.reopen(r)
@@ -3027,9 +3480,8 @@ __END__
assert_equal("\00f", File.read(path))
assert_equal(1, File.write(path, "f", 0, encoding: "UTF-8"))
assert_equal("ff", File.read(path))
- assert_raise(TypeError) {
- File.write(path, "foo", Object.new => Object.new)
- }
+ File.write(path, "foo", Object.new => Object.new)
+ assert_equal("foo", File.read(path))
end
end
@@ -3147,7 +3599,7 @@ __END__
begin
f = File.open('/dev/tty')
rescue Errno::ENOENT, Errno::ENXIO => e
- skip e.message
+ omit e.message
else
tiocgwinsz=0x5413
winsize=""
@@ -3155,7 +3607,7 @@ __END__
f.ioctl(tiocgwinsz, winsize)
}
ensure
- f.close if f
+ f&.close
end
end if /^(?:i.?86|x86_64)-linux/ =~ RUBY_PLATFORM
@@ -3228,11 +3680,17 @@ __END__
data = "a" * 100
with_pipe do |r,w|
th = Thread.new {r.sysread(100, buf)}
+
Thread.pass until th.stop?
- buf.replace("")
- assert_empty(buf, bug6099)
+
+ assert_equal 100, buf.bytesize
+
+ msg = /can't modify string; temporarily locked/
+ assert_raise_with_message(RuntimeError, msg) do
+ buf.replace("")
+ end
+ assert_predicate(th, :alive?)
w.write(data)
- Thread.pass while th.alive?
th.join
end
assert_equal(data, buf, bug6099)
@@ -3251,17 +3709,12 @@ __END__
assert_equal 100, buf.bytesize
- begin
+ msg = /can't modify string; temporarily locked/
+ assert_raise_with_message(RuntimeError, msg) do
buf.replace("")
- rescue RuntimeError => e
- assert_match(/can't modify string; temporarily locked/, e.message)
- Thread.pass
- end until buf.empty?
-
- assert_empty(buf, bug6099)
+ end
assert_predicate(th, :alive?)
w.write(data)
- Thread.pass while th.alive?
th.join
end
assert_equal(data, buf, bug6099)
@@ -3272,10 +3725,10 @@ __END__
with_pipe do |r,w|
# Linux 2.6.15 and earlier returned EINVAL instead of ESPIPE
assert_raise(Errno::ESPIPE, Errno::EINVAL) {
- r.advise(:willneed) or skip "fadvise(2) is not implemented"
+ r.advise(:willneed) or omit "fadvise(2) is not implemented"
}
assert_raise(Errno::ESPIPE, Errno::EINVAL) {
- w.advise(:willneed) or skip "fadvise(2) is not implemented"
+ w.advise(:willneed) or omit "fadvise(2) is not implemented"
}
end
end if /linux/ =~ RUBY_PLATFORM
@@ -3357,11 +3810,18 @@ __END__
end
tempfiles = []
- (0..fd_setsize+1).map {|i|
- tempfiles << Tempfile.open("test_io_select_with_many_files")
+ (0...fd_setsize).map {|i|
+ tempfiles << Tempfile.create("test_io_select_with_many_files")
}
- IO.select(tempfiles)
+ begin
+ IO.select(tempfiles)
+ ensure
+ tempfiles.each { |t|
+ t.close
+ File.unlink(t.path)
+ }
+ end
}, bug8080, timeout: 100
end if defined?(Process::RLIMIT_NOFILE)
@@ -3408,14 +3868,14 @@ __END__
f.write('1')
pos = f.tell
rescue Errno::ENOSPC
- skip "non-sparse file system"
+ omit "non-sparse file system"
rescue SystemCallError
else
assert_equal(0x1_0000_0000, pos, msg)
end
end;
rescue Timeout::Error
- skip "Timeout because of slow file writing"
+ omit "Timeout because of slow file writing"
end
}
end if /mswin|mingw/ =~ RUBY_PLATFORM
@@ -3507,8 +3967,10 @@ __END__
end
def test_open_fifo_does_not_block_other_threads
- mkcdtmpdir {
+ mkcdtmpdir do
File.mkfifo("fifo")
+ rescue NotImplementedError
+ else
assert_separately([], <<-'EOS')
t1 = Thread.new {
open("fifo", "r") {|r|
@@ -3523,8 +3985,32 @@ __END__
t1_value, _ = assert_join_threads([t1, t2])
assert_equal("foo", t1_value)
EOS
- }
- end if /mswin|mingw|bccwin|cygwin/ !~ RUBY_PLATFORM
+ end
+ end
+
+ def test_open_fifo_restart_at_signal_intterupt
+ mkcdtmpdir do
+ File.mkfifo("fifo")
+ rescue NotImplementedError
+ else
+ wait = EnvUtil.apply_timeout_scale(0.1)
+ data = "writing to fifo"
+
+ # Do not use assert_separately, because reading from stdin
+ # prevents to reproduce [Bug #20708]
+ assert_in_out_err(["-e", "#{<<~"begin;"}\n#{<<~'end;'}"], [], [data])
+ wait, data = #{wait}, #{data.dump}
+ ;
+ begin;
+ trap(:USR1) {}
+ Thread.new do
+ sleep wait; Process.kill(:USR1, $$)
+ sleep wait; File.write("fifo", data)
+ end
+ puts File.read("fifo")
+ end;
+ end
+ end if Signal.list[:USR1] # Pointless on platforms without signal
def test_open_flag
make_tempfile do |t|
@@ -3535,21 +4021,42 @@ __END__
end
def test_open_flag_binary
+ binary_enc = Encoding.find("BINARY")
make_tempfile do |t|
open(t.path, File::RDONLY, flags: File::BINARY) do |f|
assert_equal true, f.binmode?
+ assert_equal binary_enc, f.external_encoding
end
open(t.path, 'r', flags: File::BINARY) do |f|
assert_equal true, f.binmode?
+ assert_equal binary_enc, f.external_encoding
end
open(t.path, mode: 'r', flags: File::BINARY) do |f|
assert_equal true, f.binmode?
+ assert_equal binary_enc, f.external_encoding
+ end
+ open(t.path, File::RDONLY|File::BINARY) do |f|
+ assert_equal true, f.binmode?
+ assert_equal binary_enc, f.external_encoding
+ end
+ open(t.path, File::RDONLY|File::BINARY, autoclose: true) do |f|
+ assert_equal true, f.binmode?
+ assert_equal binary_enc, f.external_encoding
end
end
end if File::BINARY != 0
+ def test_exclusive_mode
+ make_tempfile do |t|
+ assert_raise(Errno::EEXIST){ open(t.path, 'wx'){} }
+ assert_raise(ArgumentError){ open(t.path, 'rx'){} }
+ assert_raise(ArgumentError){ open(t.path, 'ax'){} }
+ end
+ end
+
def test_race_gets_and_close
- assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
+ opt = { signal: :ABRT, timeout: 10 }
+ assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}", **opt)
bug13076 = '[ruby-core:78845] [Bug #13076]'
begin;
10.times do |i|
@@ -3571,19 +4078,21 @@ __END__
w.close
r.close
end
- assert_nothing_raised(IOError, bug13076) {
- t.each(&:join)
- }
+ t.each do |th|
+ assert_same(th, th.join(2), bug13076)
+ end
end
end;
end
def test_race_closed_stream
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
bug13158 = '[ruby-core:79262] [Bug #13158]'
closed = nil
- q = Queue.new
+ q = Thread::Queue.new
IO.pipe do |r, w|
thread = Thread.new do
begin
@@ -3673,6 +4182,8 @@ __END__
end
def test_closed_stream_in_rescue
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}")
begin;
10.times do
@@ -3694,61 +4205,40 @@ __END__
end
end;
end
+ end
- def test_write_no_garbage
- res = {}
- ObjectSpace.count_objects(res) # creates strings on first call
- [ 'foo'.b, '*' * 24 ].each do |buf|
- with_pipe do |r, w|
- GC.disable
- begin
- before = ObjectSpace.count_objects(res)[:T_STRING]
- n = w.write(buf)
- s = w.syswrite(buf)
- after = ObjectSpace.count_objects(res)[:T_STRING]
- ensure
- GC.enable
- end
- assert_equal before, after,
- "no strings left over after write [ruby-core:78898] [Bug #13085]: #{ before } strings before write -> #{ after } strings after write"
- assert_not_predicate buf, :frozen?, 'no inadvertent freeze'
- assert_equal buf.bytesize, n, 'IO#write wrote expected size'
- assert_equal s, n, 'IO#syswrite wrote expected size'
- end
+ def test_pread
+ make_tempfile { |t|
+ open(t.path) do |f|
+ assert_equal("bar", f.pread(3, 4))
+ buf = "asdf"
+ assert_equal("bar", f.pread(3, 4, buf))
+ assert_equal("bar", buf)
+ assert_raise(EOFError) { f.pread(1, f.size) }
end
- end
-
- def test_pread
- make_tempfile { |t|
- open(t.path) do |f|
- assert_equal("bar", f.pread(3, 4))
- buf = "asdf"
- assert_equal("bar", f.pread(3, 4, buf))
- assert_equal("bar", buf)
- assert_raise(EOFError) { f.pread(1, f.size) }
- end
- }
- end if IO.method_defined?(:pread)
+ }
+ end
- def test_pwrite
- make_tempfile { |t|
- open(t.path, IO::RDWR) do |f|
- assert_equal(3, f.pwrite("ooo", 4))
- assert_equal("ooo", f.pread(3, 4))
- end
- }
- end if IO.method_defined?(:pread) and IO.method_defined?(:pwrite)
+ def test_pwrite
+ make_tempfile { |t|
+ open(t.path, IO::RDWR) do |f|
+ assert_equal(3, f.pwrite("ooo", 4))
+ assert_equal("ooo", f.pread(3, 4))
+ end
+ }
end
def test_select_exceptfds
- if Etc.uname[:sysname] == 'SunOS' && Etc.uname[:release] == '5.11'
- skip "Solaris 11 fails this"
+ if Etc.uname[:sysname] == 'SunOS'
+ str = 'h'.freeze #(???) Only 1 byte with MSG_OOB on Solaris
+ else
+ str = 'hello'.freeze
end
TCPServer.open('localhost', 0) do |svr|
con = TCPSocket.new('localhost', svr.addr[1])
acc = svr.accept
- assert_equal 5, con.send('hello', Socket::MSG_OOB)
+ assert_equal str.length, con.send(str, Socket::MSG_OOB)
set = IO.select(nil, nil, [acc], 30)
assert_equal([[], [], [acc]], set, 'IO#select exceptions array OK')
acc.close
@@ -3756,20 +4246,183 @@ __END__
end
end if Socket.const_defined?(:MSG_OOB)
- def test_select_leak
- assert_no_memory_leak([], <<-"end;", <<-"end;", rss: true, timeout: 240)
+ def test_select_timeout
+ assert_equal(nil, IO.select(nil,nil,nil,0))
+ assert_equal(nil, IO.select(nil,nil,nil,0.0))
+ assert_raise(TypeError) { IO.select(nil,nil,nil,"invalid-timeout") }
+ assert_raise(ArgumentError) { IO.select(nil,nil,nil,-1) }
+ assert_raise(ArgumentError) { IO.select(nil,nil,nil,-0.1) }
+ assert_raise(ArgumentError) { IO.select(nil,nil,nil,-Float::INFINITY) }
+ assert_raise(RangeError) { IO.select(nil,nil,nil,Float::NAN) }
+ IO.pipe {|r, w|
+ w << "x"
+ ret = [[r], [], []]
+ assert_equal(ret, IO.select([r],nil,nil,0.1))
+ assert_equal(ret, IO.select([r],nil,nil,1))
+ assert_equal(ret, IO.select([r],nil,nil,Float::INFINITY))
+ }
+ end
+
+ def test_recycled_fd_close
+ dot = -'.'
+ IO.pipe do |sig_rd, sig_wr|
+ noex = Thread.new do # everything right and never see exceptions :)
+ until sig_rd.wait_readable(0)
+ IO.pipe do |r, w|
+ assert_nil r.timeout
+ assert_nil w.timeout
+
+ th = Thread.new { r.read(1) }
+ w.write(dot)
+
+ assert_same th, th.join(15), '"good" reader timeout'
+ assert_equal(dot, th.value)
+ end
+ end
+ sig_rd.read(4)
+ end
+ 1000.times do |i| # stupid things and make exceptions:
+ IO.pipe do |r,w|
+ th = Thread.new do
+ begin
+ while r.gets
+ end
+ rescue IOError => e
+ e
+ end
+ end
+ Thread.pass until th.stop?
+
+ r.close
+ assert_same th, th.join(30), '"bad" reader timeout'
+ assert_match(/stream closed/, th.value.message)
+ end
+ end
+ sig_wr.write 'done'
+ assert_same noex, noex.join(20), '"good" writer timeout'
+ assert_equal 'done', noex.value ,'r63216'
+ end
+ end
+
+ def test_select_memory_leak
+ # avoid malloc arena explosion from glibc and jemalloc:
+ env = {
+ 'MALLOC_ARENA_MAX' => '1',
+ 'MALLOC_ARENA_TEST' => '1',
+ 'MALLOC_CONF' => 'narenas:1',
+ }
+ assert_no_memory_leak([env], "#{<<~"begin;"}\n#{<<~'else;'}", "#{<<~'end;'}", rss: true, timeout: 60)
+ begin;
r, w = IO.pipe
rset = [r]
wset = [w]
+ exc = StandardError.new(-"select used to leak on exception")
+ exc.set_backtrace([])
Thread.new { IO.select(rset, wset, nil, 0) }.join
- end;
- 20_000.times do
- th = Thread.new { IO.select(rset, wset) }
+ else;
+ th = Thread.new do
+ Thread.handle_interrupt(StandardError => :on_blocking) do
+ begin
+ IO.select(rset, wset)
+ rescue
+ retry
+ end while true
+ end
+ end
+ 50_000.times do
Thread.pass until th.stop?
- th.kill
- th.join
- GC.start
+ th.raise(exc)
end
+ th.kill
+ th.join
end;
end
+
+ def test_external_encoding_index
+ IO.pipe {|r, w|
+ assert_raise(TypeError) {Marshal.dump(r)}
+ assert_raise(TypeError) {Marshal.dump(w)}
+ }
+ end
+
+ def test_marshal_closed_io
+ bug18077 = '[ruby-core:104927] [Bug #18077]'
+ r, w = IO.pipe
+ r.close; w.close
+ assert_raise(TypeError, bug18077) {Marshal.dump(r)}
+
+ class << r
+ undef_method :closed?
+ end
+ assert_raise(TypeError, bug18077) {Marshal.dump(r)}
+ end
+
+ def test_stdout_to_closed_pipe
+ EnvUtil.invoke_ruby(["-e", "loop {puts :ok}"], "", true, true) do
+ |in_p, out_p, err_p, pid|
+ out = out_p.gets
+ out_p.close
+ err = err_p.read
+ ensure
+ status = Process.wait2(pid)[1]
+ assert_equal("ok\n", out)
+ assert_empty(err)
+ assert_not_predicate(status, :success?)
+ if Signal.list["PIPE"]
+ assert_predicate(status, :signaled?)
+ assert_equal("PIPE", Signal.signame(status.termsig) || status.termsig)
+ end
+ end
+ end
+
+ def test_blocking_timeout
+ assert_separately([], <<~'RUBY')
+ IO.pipe do |r, w|
+ trap(:INT) do
+ w.puts "INT"
+ end
+
+ main = Thread.current
+ thread = Thread.new do
+ # Wait until the main thread has entered `$stdin.gets`:
+ Thread.pass until main.status == 'sleep'
+
+ # Cause an interrupt while handling `$stdin.gets`:
+ Process.kill :INT, $$
+ end
+
+ r.timeout = 1
+ assert_equal("INT", r.gets.chomp)
+ rescue IO::TimeoutError
+ # Ignore - some platforms don't support interrupting `gets`.
+ ensure
+ thread&.join
+ 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
new file mode 100644
index 0000000000..706ce16c42
--- /dev/null
+++ b/test/ruby/test_io_buffer.rb
@@ -0,0 +1,933 @@
+# frozen_string_literal: false
+
+require 'tempfile'
+require 'rbconfig/sizeof'
+
+class TestIOBuffer < Test::Unit::TestCase
+ experimental = Warning[:experimental]
+ begin
+ Warning[:experimental] = false
+ IO::Buffer.new(0)
+ ensure
+ Warning[:experimental] = experimental
+ end
+
+ def assert_negative(value)
+ assert(value < 0, "Expected #{value} to be negative!")
+ end
+
+ def assert_positive(value)
+ assert(value > 0, "Expected #{value} to be positive!")
+ end
+
+ def test_flags
+ assert_equal 1, IO::Buffer::EXTERNAL
+ assert_equal 2, IO::Buffer::INTERNAL
+ assert_equal 4, IO::Buffer::MAPPED
+
+ assert_equal 32, IO::Buffer::LOCKED
+ assert_equal 64, IO::Buffer::PRIVATE
+
+ assert_equal 128, IO::Buffer::READONLY
+ end
+
+ def test_endian
+ assert_equal 4, IO::Buffer::LITTLE_ENDIAN
+ assert_equal 8, IO::Buffer::BIG_ENDIAN
+ assert_equal 8, IO::Buffer::NETWORK_ENDIAN
+
+ assert_include [IO::Buffer::LITTLE_ENDIAN, IO::Buffer::BIG_ENDIAN], IO::Buffer::HOST_ENDIAN
+ end
+
+ def test_default_size
+ assert_equal IO::Buffer::DEFAULT_SIZE, IO::Buffer.new.size
+ end
+
+ def test_new_internal
+ buffer = IO::Buffer.new(1024, IO::Buffer::INTERNAL)
+ assert_equal 1024, buffer.size
+ 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_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_predicate buffer, :readonly?
+
+ assert_raise IO::Buffer::AccessError do
+ buffer.set_string("")
+ end
+
+ assert_raise IO::Buffer::AccessError do
+ buffer.set_string("!", 1)
+ end
+ end
+
+ def test_file_mapped
+ buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)}
+ 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")
+ end
+ end
+
+ def test_string_mapped
+ string = "Hello World"
+ buffer = IO::Buffer.for(string)
+ assert_predicate buffer, :readonly?
+ end
+
+ def test_string_mapped_frozen
+ string = "Hello World".freeze
+ buffer = IO::Buffer.for(string)
+ assert_predicate buffer, :readonly?
+ end
+
+ def test_string_mapped_mutable
+ string = "Hello World"
+ IO::Buffer.for(string) do |buffer|
+ refute_predicate buffer, :readonly?
+
+ buffer.set_value(:U8, 0, "h".ord)
+
+ # Buffer releases it's ownership of the string:
+ buffer.free
+
+ assert_equal "hello World", string
+ end
+ end
+
+ def test_string_mapped_buffer_locked
+ string = "Hello World"
+ IO::Buffer.for(string) do |buffer|
+ # Cannot modify string as it's locked by the buffer:
+ assert_raise RuntimeError do
+ string[0] = "h"
+ end
+ 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
+
+ assert_raise TypeError do
+ IO::Buffer.for(not_string)
+ end
+ end
+
+ def test_string
+ result = IO::Buffer.string(12) do |buffer|
+ buffer.set_string("Hello World!")
+ end
+
+ assert_equal "Hello World!", result
+ end
+
+ def test_string_negative
+ assert_raise ArgumentError do
+ IO::Buffer.string(-1)
+ end
+ end
+
+ def test_resize_mapped
+ buffer = IO::Buffer.new
+
+ buffer.resize(2048)
+ assert_equal 2048, buffer.size
+
+ buffer.resize(4096)
+ assert_equal 4096, buffer.size
+ end
+
+ def test_resize_preserve
+ message = "Hello World"
+ buffer = IO::Buffer.new(1024)
+ buffer.set_string(message)
+ buffer.resize(2048)
+ assert_equal message, buffer.get_string(0, message.bytesize)
+ end
+
+ def test_resize_zero_internal
+ buffer = IO::Buffer.new(1)
+
+ buffer.resize(0)
+ assert_equal 0, buffer.size
+
+ buffer.resize(1)
+ assert_equal 1, buffer.size
+ end
+
+ def test_resize_zero_external
+ buffer = IO::Buffer.for('1')
+
+ assert_raise IO::Buffer::AccessError do
+ buffer.resize(0)
+ end
+ end
+
+ def test_compare_same_size
+ buffer1 = IO::Buffer.new(1)
+ assert_equal buffer1, buffer1
+
+ buffer2 = IO::Buffer.new(1)
+ buffer1.set_value(:U8, 0, 0x10)
+ buffer2.set_value(:U8, 0, 0x20)
+
+ assert_negative buffer1 <=> buffer2
+ assert_positive buffer2 <=> buffer1
+ end
+
+ def test_compare_different_size
+ buffer1 = IO::Buffer.new(3)
+ buffer2 = IO::Buffer.new(5)
+
+ assert_negative buffer1 <=> buffer2
+ assert_positive buffer2 <=> buffer1
+ end
+
+ def test_compare_zero_length
+ buffer1 = IO::Buffer.new(0)
+ buffer2 = IO::Buffer.new(1)
+
+ assert_negative buffer1 <=> buffer2
+ assert_positive buffer2 <=> buffer1
+ end
+
+ def test_slice
+ buffer = IO::Buffer.new(128)
+ slice = buffer.slice(8, 32)
+ slice.set_string("Hello World")
+ assert_equal("Hello World", buffer.get_string(8, 11))
+ end
+
+ def test_slice_arguments
+ buffer = IO::Buffer.for("Hello World")
+
+ slice = buffer.slice
+ assert_equal "Hello World", slice.get_string
+
+ slice = buffer.slice(2)
+ assert_equal("llo World", slice.get_string)
+ end
+
+ def test_slice_bounds_error
+ buffer = IO::Buffer.new(128)
+
+ assert_raise ArgumentError do
+ buffer.slice(128, 10)
+ end
+
+ assert_raise ArgumentError do
+ buffer.slice(-10, 10)
+ end
+ end
+
+ def test_slice_readonly
+ hello = %w"Hello World".join(" ").freeze
+ buffer = IO::Buffer.for(hello)
+ slice = buffer.slice
+ assert_predicate slice, :readonly?
+ assert_raise IO::Buffer::AccessError do
+ # This breaks the literal in string pool and many other tests in this file.
+ slice.set_string("Adios", 0, 5)
+ end
+ assert_equal "Hello World", hello
+ end
+
+ def test_transfer
+ hello = %w"Hello World".join(" ")
+ buffer = IO::Buffer.for(hello)
+ transferred = buffer.transfer
+ assert_equal "Hello World", transferred.get_string
+ assert_predicate buffer, :null?
+ assert_raise IO::Buffer::AccessError do
+ transferred.set_string("Goodbye")
+ end
+ assert_equal "Hello World", hello
+ end
+
+ def test_transfer_in_block
+ hello = %w"Hello World".join(" ")
+ buffer = IO::Buffer.for(hello, &:transfer)
+ assert_equal "Hello World", buffer.get_string
+ buffer.set_string("Ciao!")
+ assert_equal "Ciao! World", hello
+ hello.freeze
+ assert_raise IO::Buffer::AccessError do
+ buffer.set_string("Hola")
+ end
+ assert_equal "Ciao! World", hello
+ end
+
+ def test_locked
+ buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::LOCKED)
+
+ assert_raise IO::Buffer::LockedError do
+ buffer.resize(256)
+ end
+
+ assert_equal 128, buffer.size
+
+ assert_raise IO::Buffer::LockedError do
+ buffer.free
+ end
+
+ assert_equal 128, buffer.size
+ end
+
+ def test_get_string
+ message = "Hello World 🤓"
+
+ buffer = IO::Buffer.new(128)
+ buffer.set_string(message)
+
+ chunk = buffer.get_string(0, message.bytesize, Encoding::UTF_8)
+ assert_equal message, chunk
+ assert_equal Encoding::UTF_8, chunk.encoding
+
+ chunk = buffer.get_string(0, message.bytesize, Encoding::BINARY)
+ assert_equal Encoding::BINARY, chunk.encoding
+
+ assert_raise_with_message(ArgumentError, /bigger than the buffer size/) do
+ buffer.get_string(0, 129)
+ end
+
+ assert_raise_with_message(ArgumentError, /bigger than the buffer size/) do
+ buffer.get_string(129)
+ end
+
+ assert_raise_with_message(ArgumentError, /Offset can't be negative/) do
+ buffer.get_string(-1)
+ end
+ end
+
+ def test_zero_length_get_string
+ buffer = IO::Buffer.new.slice(0, 0)
+ assert_equal "", buffer.get_string
+
+ buffer = IO::Buffer.new(0)
+ assert_equal "", buffer.get_string
+ end
+
+ # We check that values are correctly round tripped.
+ RANGES = {
+ :U8 => [0, 2**8-1],
+ :S8 => [-2**7, 0, 2**7-1],
+
+ :U16 => [0, 2**16-1],
+ :S16 => [-2**15, 0, 2**15-1],
+ :u16 => [0, 2**16-1],
+ :s16 => [-2**15, 0, 2**15-1],
+
+ :U32 => [0, 2**32-1],
+ :S32 => [-2**31, 0, 2**31-1],
+ :u32 => [0, 2**32-1],
+ :s32 => [-2**31, 0, 2**31-1],
+
+ :U64 => [0, 2**64-1],
+ :S64 => [-2**63, 0, 2**63-1],
+ :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)
+
+ RANGES.each do |data_type, values|
+ values.each do |value|
+ 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
+
+ def test_get_set_values
+ buffer = IO::Buffer.new(128)
+
+ RANGES.each do |data_type, values|
+ format = [data_type] * values.size
+
+ buffer.set_values(format, 0, values)
+ assert_equal values, buffer.get_values(format, 0), "Converting #{values} as #{format}."
+ end
+ end
+
+ def test_zero_length_get_set_values
+ buffer = IO::Buffer.new(0)
+
+ assert_equal [], buffer.get_values([], 0)
+ assert_equal 0, buffer.set_values([], 0, [])
+ end
+
+ def test_values
+ buffer = IO::Buffer.new(128)
+
+ RANGES.each do |data_type, values|
+ format = [data_type] * values.size
+
+ buffer.set_values(format, 0, values)
+ assert_equal values, buffer.values(data_type, 0, values.size), "Reading #{values} as #{format}."
+ end
+ end
+
+ def test_each
+ buffer = IO::Buffer.new(128)
+
+ RANGES.each do |data_type, values|
+ format = [data_type] * values.size
+ data_type_size = IO::Buffer.size_of(data_type)
+ values_with_offsets = values.map.with_index{|value, index| [index * data_type_size, value]}
+
+ buffer.set_values(format, 0, values)
+ assert_equal values_with_offsets, buffer.each(data_type, 0, values.size).to_a, "Reading #{values} as #{data_type}."
+ end
+ end
+
+ def test_zero_length_each
+ buffer = IO::Buffer.new(0)
+
+ assert_equal [], buffer.each(:U8).to_a
+ end
+
+ def test_each_byte
+ string = "The quick brown fox jumped over the lazy dog."
+ 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
+ buffer = IO::Buffer.new(0)
+
+ assert_equal [], buffer.each_byte.to_a
+ end
+
+ def test_clear
+ buffer = IO::Buffer.new(16)
+ 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
+ input, output = IO.pipe
+
+ # (1) rb_write_internal creates IO::Buffer object,
+ buffer = IO::Buffer.new(128)
+
+ # (2) it is passed to (malicious) scheduler
+ # (3) scheduler starts a thread which call system call with the buffer object
+ thread = Thread.new{buffer.locked{input.read}}
+
+ Thread.pass until thread.stop?
+
+ # (4) scheduler returns
+ # (5) rb_write_internal invalidate the buffer object
+ assert_raise IO::Buffer::LockedError do
+ buffer.free
+ end
+
+ # (6) the system call access the memory area after invalidation
+ output.write("Hello World")
+ output.close
+ thread.join
+
+ input.close
+ end
+
+ def hello_world_tempfile(repeats = 1)
+ io = Tempfile.new
+ repeats.times do
+ io.write("Hello World")
+ end
+ io.seek(0)
+
+ yield io
+ ensure
+ io&.close!
+ end
+
+ def test_read
+ hello_world_tempfile do |io|
+ buffer = IO::Buffer.new(128)
+ buffer.read(io)
+ assert_equal "Hello", buffer.get_string(0, 5)
+ end
+ end
+
+ def test_read_with_with_length
+ hello_world_tempfile do |io|
+ buffer = IO::Buffer.new(128)
+ buffer.read(io, 5)
+ assert_equal "Hello", buffer.get_string(0, 5)
+ end
+ end
+
+ def test_read_with_with_offset
+ hello_world_tempfile do |io|
+ buffer = IO::Buffer.new(128)
+ buffer.read(io, nil, 6)
+ assert_equal "Hello", buffer.get_string(6, 5)
+ end
+ end
+
+ def test_read_with_length_and_offset
+ hello_world_tempfile(100) do |io|
+ buffer = IO::Buffer.new(1024)
+ # Only read 24 bytes from the file, as we are starting at offset 1000 in the buffer.
+ assert_equal 24, buffer.read(io, 0, 1000)
+ assert_equal "Hello World", buffer.get_string(1000, 11)
+ end
+ end
+
+ def test_write
+ io = Tempfile.new
+
+ buffer = IO::Buffer.new(128)
+ buffer.set_string("Hello")
+ buffer.write(io)
+
+ io.seek(0)
+ assert_equal "Hello", io.read(5)
+ ensure
+ io.close!
+ end
+
+ def test_write_with_length_and_offset
+ io = Tempfile.new
+
+ buffer = IO::Buffer.new(5)
+ buffer.set_string("Hello")
+ buffer.write(io, 4, 1)
+
+ io.seek(0)
+ assert_equal "ello", io.read(4)
+ ensure
+ io.close!
+ end
+
+ def test_pread
+ io = Tempfile.new
+ io.write("Hello World")
+ io.seek(0)
+
+ buffer = IO::Buffer.new(128)
+ buffer.pread(io, 6, 5)
+
+ assert_equal "World", buffer.get_string(0, 5)
+ assert_equal 0, io.tell
+ ensure
+ io.close!
+ end
+
+ def test_pread_offset
+ io = Tempfile.new
+ io.write("Hello World")
+ io.seek(0)
+
+ buffer = IO::Buffer.new(128)
+ buffer.pread(io, 6, 5, 6)
+
+ assert_equal "World", buffer.get_string(6, 5)
+ assert_equal 0, io.tell
+ ensure
+ io.close!
+ end
+
+ def test_pwrite
+ io = Tempfile.new
+
+ buffer = IO::Buffer.new(128)
+ buffer.set_string("World")
+ buffer.pwrite(io, 6, 5)
+
+ assert_equal 0, io.tell
+
+ io.seek(6)
+ assert_equal "World", io.read(5)
+ ensure
+ io.close!
+ end
+
+ def test_pwrite_offset
+ io = Tempfile.new
+
+ buffer = IO::Buffer.new(128)
+ buffer.set_string("Hello World")
+ buffer.pwrite(io, 6, 5, 6)
+
+ assert_equal 0, io.tell
+
+ io.seek(6)
+ assert_equal "World", io.read(5)
+ ensure
+ io.close!
+ end
+
+ def test_operators
+ source = IO::Buffer.for("1234123412")
+ mask = IO::Buffer.for("133\x00")
+
+ assert_equal IO::Buffer.for("123\x00123\x0012"), (source & mask)
+ assert_equal IO::Buffer.for("1334133413"), (source | mask)
+ assert_equal IO::Buffer.for("\x00\x01\x004\x00\x01\x004\x00\x01"), (source ^ mask)
+ assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), ~source
+ end
+
+ def test_inplace_operators
+ source = IO::Buffer.for("1234123412")
+ mask = IO::Buffer.for("133\x00")
+
+ assert_equal IO::Buffer.for("123\x00123\x0012"), source.dup.and!(mask)
+ assert_equal IO::Buffer.for("1334133413"), source.dup.or!(mask)
+ assert_equal IO::Buffer.for("\x00\x01\x004\x00\x01\x004\x00\x01"), source.dup.xor!(mask)
+ assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), source.dup.not!
+ end
+
+ def test_shared
+ message = "Hello World"
+ buffer = IO::Buffer.new(64, IO::Buffer::MAPPED | IO::Buffer::SHARED)
+
+ pid = fork do
+ buffer.set_string(message)
+ end
+
+ Process.wait(pid)
+ string = buffer.get_string(0, message.bytesize)
+ assert_equal message, string
+ rescue NotImplementedError
+ omit "Fork/shared memory is not supported."
+ end
+
+ def test_private
+ Tempfile.create(%w"buffer .txt") do |file|
+ file.write("Hello World")
+
+ buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE)
+ begin
+ assert_predicate buffer, :private?
+ refute_predicate buffer, :readonly?
+
+ buffer.set_string("J")
+
+ # It was not changed because the mapping was private:
+ file.seek(0)
+ assert_equal "Hello World", file.read
+ ensure
+ buffer&.free
+ end
+ end
+ end
+
+ def test_copy_overlapped_fwd
+ buf = IO::Buffer.for('0123456789').dup
+ buf.copy(buf, 3, 7)
+ assert_equal '0120123456', buf.get_string
+ end
+
+ def test_copy_overlapped_bwd
+ buf = IO::Buffer.for('0123456789').dup
+ buf.copy(buf, 0, 7, 3)
+ assert_equal '3456789789', buf.get_string
+ end
+
+ def test_copy_null_destination
+ buf = IO::Buffer.new(0)
+ assert_predicate buf, :null?
+ buf.copy(IO::Buffer.for('a'), 0, 0)
+ assert_predicate buf, :empty?
+ end
+
+ def test_copy_null_source
+ buf = IO::Buffer.for('a').dup
+ src = IO::Buffer.new(0)
+ assert_predicate src, :null?
+ buf.copy(src, 0, 0)
+ assert_equal 'a', buf.get_string
+ end
+
+ def test_set_string_overlapped_fwd
+ str = +'0123456789'
+ IO::Buffer.for(str) do |buf|
+ buf.set_string(str, 3, 7)
+ end
+ assert_equal '0120123456', str
+ end
+
+ def test_set_string_overlapped_bwd
+ str = +'0123456789'
+ IO::Buffer.for(str) do |buf|
+ buf.set_string(str, 0, 7, 3)
+ end
+ assert_equal '3456789789', str
+ end
+
+ def test_set_string_null_destination
+ buf = IO::Buffer.new(0)
+ assert_predicate buf, :null?
+ 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 9ff5307fc3..83d4fb0c7b 100644
--- a/test/ruby/test_io_m17n.rb
+++ b/test/ruby/test_io_m17n.rb
@@ -23,7 +23,8 @@ class TestIO_M17N < Test::Unit::TestCase
def pipe(*args, wp, rp)
re, we = nil, nil
- r, w = IO.pipe(*args)
+ kw = args.last.is_a?(Hash) ? args.pop : {}
+ r, w = IO.pipe(*args, **kw)
rt = Thread.new do
begin
rp.call(r)
@@ -775,10 +776,10 @@ EOT
assert_equal(eucjp, r.read)
end)
- assert_raise_with_message(ArgumentError, /invalid name encoding/) do
+ assert_raise_with_message(ArgumentError, /invalid encoding name/) do
with_pipe("UTF-8", "UTF-8".encode("UTF-32BE")) {}
end
- assert_raise_with_message(ArgumentError, /invalid name encoding/) do
+ assert_raise_with_message(ArgumentError, /invalid encoding name/) do
with_pipe("UTF-8".encode("UTF-32BE")) {}
end
@@ -1141,12 +1142,94 @@ EOT
IO.pipe do |r, w|
assert_nothing_raised(bug5567) do
assert_warning(/Unsupported/, bug5567) {r.set_encoding("fffffffffffxx")}
+ w.puts("foo")
+ assert_equal("foo\n", r.gets)
assert_warning(/Unsupported/, bug5567) {r.set_encoding("fffffffffffxx", "us-ascii")}
+ w.puts("bar")
+ assert_equal("bar\n", r.gets)
assert_warning(/Unsupported/, bug5567) {r.set_encoding("us-ascii", "fffffffffffxx")}
+ w.puts("zot")
+ begin
+ assert_equal("zot\n", r.gets)
+ rescue Encoding::ConverterNotFoundError => e
+ assert_match(/\((\S+) to \1\)/, e.message)
+ end
end
end
end
+ def test_set_encoding_argument_parsing
+ File.open(File::NULL) do |f|
+ f.set_encoding('binary')
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding(Encoding.find('binary'))
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding('binary:utf-8')
+ assert_equal(nil, f.internal_encoding)
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding('binary', 'utf-8')
+ assert_equal(nil, f.internal_encoding)
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding(Encoding.find('binary'), Encoding.find('utf-8'))
+ assert_equal(nil, f.internal_encoding)
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding('binary', Encoding.find('utf-8'))
+ assert_equal(nil, f.internal_encoding)
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding(Encoding.find('binary'), 'utf-8')
+ assert_equal(nil, f.internal_encoding)
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding('iso-8859-1:utf-8')
+ assert_equal(Encoding::UTF_8, f.internal_encoding)
+ assert_equal(Encoding::ISO_8859_1, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding('iso-8859-1', 'utf-8')
+ assert_equal(Encoding::UTF_8, f.internal_encoding)
+ assert_equal(Encoding::ISO_8859_1, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding(Encoding.find('iso-8859-1'), Encoding.find('utf-8'))
+ assert_equal(Encoding::UTF_8, f.internal_encoding)
+ assert_equal(Encoding::ISO_8859_1, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding('iso-8859-1', Encoding.find('utf-8'))
+ assert_equal(Encoding::UTF_8, f.internal_encoding)
+ assert_equal(Encoding::ISO_8859_1, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding(Encoding.find('iso-8859-1'), 'utf-8')
+ assert_equal(Encoding::UTF_8, f.internal_encoding)
+ assert_equal(Encoding::ISO_8859_1, f.external_encoding)
+ end
+ end
+
def test_textmode_twice
assert_raise(ArgumentError) {
open(__FILE__, "rt", textmode: true) {|f|
@@ -1312,26 +1395,6 @@ EOT
}
end
- def test_open_pipe_r_enc
- 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
-
- def test_open_pipe_r_enc2
- 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
-
def test_s_foreach_enc
with_tmpdir {
generate_file("t", "\xff")
@@ -2046,19 +2109,19 @@ EOT
with_tmpdir {
open("raw.txt", "wb", xml: :attr) {|f| f.print '&<>"\''; f.puts "\u4E02\u3042" }
content = File.read("raw.txt", :mode=>"rb:ascii-8bit")
- assert_equal("\"&amp;&lt;&gt;&quot;'\u4E02\u3042\n\"".force_encoding("ascii-8bit"), content)
+ assert_equal("\"&amp;&lt;&gt;&quot;&apos;\u4E02\u3042\n\"".force_encoding("ascii-8bit"), content)
open("ascii.txt", "wb:us-ascii", xml: :attr) {|f| f.print '&<>"\''; f.puts "\u4E02\u3042" }
content = File.read("ascii.txt", :mode=>"rb:ascii-8bit")
- assert_equal("\"&amp;&lt;&gt;&quot;'&#x4E02;&#x3042;\n\"".force_encoding("ascii-8bit"), content)
+ assert_equal("\"&amp;&lt;&gt;&quot;&apos;&#x4E02;&#x3042;\n\"".force_encoding("ascii-8bit"), content)
open("iso-2022-jp.txt", "wb:iso-2022-jp", xml: :attr) {|f| f.print '&<>"\''; f.puts "\u4E02\u3042" }
content = File.read("iso-2022-jp.txt", :mode=>"rb:ascii-8bit")
- assert_equal("\"&amp;&lt;&gt;&quot;'&#x4E02;\e$B$\"\e(B\n\"".force_encoding("ascii-8bit"), content)
+ assert_equal("\"&amp;&lt;&gt;&quot;&apos;&#x4E02;\e$B$\"\e(B\n\"".force_encoding("ascii-8bit"), content)
open("utf-16be.txt", "wb:utf-16be", xml: :attr) {|f| f.print '&<>"\''; f.puts "\u4E02\u3042" }
content = File.read("utf-16be.txt", :mode=>"rb:ascii-8bit")
- assert_equal("\0\"\0&\0a\0m\0p\0;\0&\0l\0t\0;\0&\0g\0t\0;\0&\0q\0u\0o\0t\0;\0'\x4E\x02\x30\x42\0\n\0\"".force_encoding("ascii-8bit"), content)
+ assert_equal("\0\"\0&\0a\0m\0p\0;\0&\0l\0t\0;\0&\0g\0t\0;\0&\0q\0u\0o\0t\0;\0&\0a\0p\0o\0s\0;\x4E\x02\x30\x42\0\n\0\"".force_encoding("ascii-8bit"), content)
open("eucjp.txt", "w:euc-jp:utf-8", xml: :attr) {|f|
f.print "\u4E02" # U+4E02 is 0x3021 in JIS X 0212
@@ -2080,29 +2143,56 @@ EOT
}
end
- def test_strip_bom
- with_tmpdir {
- text = "\uFEFF\u0100a"
- stripped = "\u0100a"
- %w/UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE/.each do |name|
- path = '%s-bom.txt' % name
+ %w/UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE/.each do |name|
+ define_method("test_strip_bom:#{name}") do
+ path = "#{name}-bom.txt"
+ with_tmpdir {
+ text = "\uFEFF\u0100a"
+ stripped = "\u0100a"
content = text.encode(name)
generate_file(path, content)
result = File.read(path, mode: 'rb:BOM|UTF-8')
- assert_equal(content[1..-1].force_encoding("ascii-8bit"),
- result.force_encoding("ascii-8bit"))
- result = File.read(path, mode: 'rb:BOM|UTF-8:UTF-8')
- assert_equal(Encoding::UTF_8, result.encoding)
- assert_equal(stripped, result)
- end
+ assert_equal(Encoding.find(name), result.encoding, name)
+ assert_equal(content[1..-1].b, result.b, name)
+ %w[rb rt r].each do |mode|
+ message = "#{name}, mode: #{mode.dump}"
+ result = File.read(path, mode: "#{mode}:BOM|UTF-8:UTF-8")
+ assert_equal(Encoding::UTF_8, result.encoding, message)
+ assert_equal(stripped, result, message)
+ end
- bug3407 = '[ruby-core:30641]'
+ File.open(path, "rb") {|f|
+ assert_equal(Encoding.find(name), f.set_encoding_by_bom)
+ }
+ File.open(path, "rb", encoding: "iso-8859-1") {|f|
+ assert_raise(ArgumentError) {f.set_encoding_by_bom}
+ }
+ }
+ end
+ end
+
+ def test_strip_bom_no_conv
+ with_tmpdir {
path = 'UTF-8-bom.txt'
+ generate_file(path, "\uFEFFa")
+
+ bug3407 = '[ruby-core:30641]'
result = File.read(path, encoding: 'BOM|UTF-8')
- assert_equal(stripped.b, result.force_encoding("ascii-8bit"), bug3407)
+ assert_equal("a", result.b, bug3407)
+
+ File.open(path, "rb", encoding: "iso-8859-1") {|f|
+ assert_raise(ArgumentError) {f.set_encoding_by_bom}
+ }
+ }
+ end
+
+ def test_strip_bom_invalid
+ with_tmpdir {
+ path = 'UTF-8-bom.txt'
+ generate_file(path, "\uFEFFa")
bug8323 = '[ruby-core:54563] [Bug #8323]'
- expected = (stripped.b + "\xff").force_encoding("utf-8")
+ expected = "a\xff".force_encoding("utf-8")
open(path, 'ab') {|f| f.write("\xff")}
result = File.read(path, encoding: 'BOM|UTF-8')
assert_not_predicate(result, :valid_encoding?, bug8323)
@@ -2110,23 +2200,38 @@ EOT
result = File.read(path, encoding: 'BOM|UTF-8:UTF-8')
assert_not_predicate(result, :valid_encoding?, bug8323)
assert_equal(expected, result, bug8323)
+ }
+ end
+ def test_strip_bom_no_bom
+ with_tmpdir {
+ bug8323 = '[ruby-core:54563] [Bug #8323]'
path = 'ascii.txt'
+ stripped = "a"
generate_file(path, stripped)
result = File.read(path, encoding: 'BOM|UTF-8')
assert_equal(stripped, result, bug8323)
result = File.read(path, encoding: 'BOM|UTF-8:UTF-8')
assert_equal(stripped, result, bug8323)
+
+ File.open(path, "rb") {|f|
+ assert_nil(f.set_encoding_by_bom)
+ }
+ File.open(path, "rb", encoding: "iso-8859-1") {|f|
+ assert_raise(ArgumentError) {f.set_encoding_by_bom}
+ }
}
end
def test_bom_too_long_utfname
- assert_separately([], <<-'end;') # do
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
assert_warn(/Unsupported encoding/) {
open(IO::NULL, "r:bom|utf-" + "x" * 10000) {}
}
end;
- assert_separately([], <<-'end;') # do
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
assert_warn(/Unsupported encoding/) {
open(IO::NULL, encoding: "bom|utf-" + "x" * 10000) {}
}
@@ -2619,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|
@@ -2701,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_io_timeout.rb b/test/ruby/test_io_timeout.rb
new file mode 100644
index 0000000000..e017395980
--- /dev/null
+++ b/test/ruby/test_io_timeout.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: false
+
+require 'io/nonblock'
+
+class TestIOTimeout < Test::Unit::TestCase
+ def with_pipe
+ omit "UNIXSocket is not defined!" unless defined?(UNIXSocket)
+
+ begin
+ i, o = UNIXSocket.pair
+
+ yield i, o
+ ensure
+ i.close
+ o.close
+ end
+ end
+
+ def test_timeout_attribute
+ with_pipe do |i, o|
+ assert_nil i.timeout
+
+ i.timeout = 10
+ assert_equal 10, i.timeout
+ assert_nil o.timeout
+
+ o.timeout = 20
+ assert_equal 20, o.timeout
+ assert_equal 10, i.timeout
+ end
+ end
+
+ def test_timeout_read_exception
+ with_pipe do |i, o|
+ i.timeout = 0.0001
+
+ assert_raise(IO::TimeoutError) {i.read}
+ end
+ end
+
+ def test_timeout_gets_exception
+ with_pipe do |i, o|
+ i.timeout = 0.0001
+
+ assert_raise(IO::TimeoutError) {i.gets}
+ end
+ end
+
+ def test_timeout_puts
+ with_pipe do |i, o|
+ i.timeout = 0.0001
+ o.puts("Hello World")
+ o.close
+
+ assert_equal "Hello World", i.gets.chomp
+ end
+ end
+end
diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb
index ed88c9b43d..fa716787fe 100644
--- a/test/ruby/test_iseq.rb
+++ b/test/ruby/test_iseq.rb
@@ -10,13 +10,16 @@ class TestISeq < Test::Unit::TestCase
end
def compile(src, line = nil, opt = nil)
+ unless line
+ line = caller_locations(1).first.lineno
+ end
EnvUtil.suppress_warning do
ISeq.new(src, __FILE__, __FILE__, line, opt)
end
end
- def lines src
- body = compile(src).to_a[13]
+ def lines src, lines = nil
+ body = compile(src, lines).to_a[13]
body.find_all{|e| e.kind_of? Integer}
end
@@ -25,24 +28,22 @@ class TestISeq < Test::Unit::TestCase
end
def test_to_a_lines
- src = <<-EOS
+ assert_equal [__LINE__+1, __LINE__+2, __LINE__+4], lines(<<-EOS, __LINE__+1)
p __LINE__ # 1
p __LINE__ # 2
# 3
p __LINE__ # 4
EOS
- assert_equal [1, 2, 4], lines(src)
- src = <<-EOS
+ assert_equal [__LINE__+2, __LINE__+4], lines(<<-EOS, __LINE__+1)
# 1
p __LINE__ # 2
# 3
p __LINE__ # 4
# 5
EOS
- assert_equal [2, 4], lines(src)
- src = <<-EOS
+ assert_equal [__LINE__+3, __LINE__+4, __LINE__+7, __LINE__+9], lines(<<~EOS, __LINE__+1)
1 # should be optimized out
2 # should be optimized out
p __LINE__ # 3
@@ -53,10 +54,9 @@ class TestISeq < Test::Unit::TestCase
8 # should be optimized out
9
EOS
- assert_equal [3, 4, 7, 9], lines(src)
end
- def test_unsupport_type
+ def test_unsupported_type
ary = compile("p").to_a
ary[9] = :foobar
assert_raise_with_message(TypeError, /:foobar/) {ISeq.load(ary)}
@@ -82,8 +82,106 @@ class TestISeq < Test::Unit::TestCase
end;
end if defined?(RubyVM::InstructionSequence.load)
+ def test_cdhash_after_roundtrip
+ # CDHASH was not built properly when loading from binary and
+ # was causing opt_case_dispatch to clobber its stack canary
+ # for its "leaf" instruction attribute.
+ iseq = compile(<<~EOF, __LINE__+1)
+ case Class.new(String).new("foo")
+ when "foo"
+ 42
+ end
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
+ end
+
+ def test_forwardable
+ iseq = compile(<<~EOF, __LINE__+1)
+ Class.new {
+ def bar(a, b); a + b; end
+ def foo(...); bar(...); end
+ }
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval.new.foo(40, 2))
+ end
+
+ def test_super_with_block
+ iseq = compile(<<~EOF, __LINE__+1)
+ def (Object.new).touch(*) # :nodoc:
+ foo { super }
+ end
+ 42
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
+ end
+
+ def test_super_with_block_hash_0
+ iseq = compile(<<~EOF, __LINE__+1)
+ # [Bug #18250] `req` specifically cause `Assertion failed: (key != 0), function hash_table_raw_insert`
+ def (Object.new).touch(req, *)
+ foo { super }
+ end
+ 42
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
+ end
+
+ def test_super_with_block_and_kwrest
+ iseq = compile(<<~EOF, __LINE__+1)
+ def (Object.new).touch(**) # :nodoc:
+ foo { super }
+ end
+ 42
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
+ end
+
+ def test_lambda_with_ractor_roundtrip
+ iseq = compile(<<~EOF, __LINE__+1)
+ x = 42
+ y = Ractor.shareable_lambda{x}
+ y.call
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
+ end
+
+ def test_super_with_anonymous_block
+ iseq = compile(<<~EOF, __LINE__+1)
+ def (Object.new).touch(&) # :nodoc:
+ foo { super }
+ end
+ 42
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
+ end
+
+ def test_ractor_unshareable_outer_variable
+ name = "\u{2603 26a1}"
+ assert_raise_with_message(ArgumentError, /\(#{name}\)/) do
+ eval("#{name} = nil; Ractor.shareable_proc{#{name} = nil}")
+ end
+
+ assert_raise_with_message(Ractor::IsolationError, /\'#{name}\'/) do
+ eval("#{name} = []; Ractor.shareable_proc{#{name}}")
+ end
+
+ obj = Object.new
+ 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
+
+ def test_ractor_shareable_value_frozen_core
+ iseq = RubyVM::InstructionSequence.compile(<<~'RUBY')
+ # shareable_constant_value: literal
+ REGEX = /#{}/ # [Bug #20569]
+ RUBY
+ assert_includes iseq_to_binary(iseq), "REGEX".b
+ end
+
def test_disasm_encoding
- src = "\u{3042} = 1; \u{3042}; \u{3043}"
+ src = +"\u{3042} = 1; \u{3042}; \u{3043}"
asm = compile(src).disasm
assert_equal(src.encoding, asm.encoding)
assert_predicate(asm, :valid_encoding?)
@@ -91,6 +189,27 @@ class TestISeq < Test::Unit::TestCase
asm = compile(src).disasm
assert_equal(src.encoding, asm.encoding)
assert_predicate(asm, :valid_encoding?)
+
+ obj = Object.new
+ name = "\u{2603 26a1}"
+ obj.instance_eval("def #{name}; tap {}; end")
+ assert_include(RubyVM::InstructionSequence.of(obj.method(name)).disasm, name)
+ end
+
+ def test_compile_file_encoding
+ Tempfile.create(%w"test_iseq .rb") do |f|
+ f.puts "{ '\u00de' => 'Th', '\u00df' => 'ss', '\u00e0' => 'a' }"
+ f.close
+
+ EnvUtil.with_default_external(Encoding::US_ASCII) do
+ assert_warn('') {
+ load f.path
+ }
+ assert_nothing_raised(SyntaxError) {
+ RubyVM::InstructionSequence.compile_file(f.path)
+ }
+ end
+ end
end
LINE_BEFORE_METHOD = __LINE__
@@ -103,16 +222,16 @@ class TestISeq < Test::Unit::TestCase
end
def test_line_trace
- iseq = compile \
- %q{ a = 1
+ iseq = compile(<<~EOF, __LINE__+1)
+ a = 1
b = 2
c = 3
# d = 4
e = 5
# f = 6
g = 7
+ EOF
- }
assert_equal([1, 2, 3, 5, 7], iseq.line_trace_all)
iseq.line_trace_specify(1, true) # line 2
iseq.line_trace_specify(3, true) # line 5
@@ -173,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
@@ -182,8 +351,8 @@ class TestISeq < Test::Unit::TestCase
s1, s2, s3, s4 = compile(code, line, {frozen_string_literal: true}).eval
assert_predicate(s1, :frozen?)
assert_predicate(s2, :frozen?)
- assert_predicate(s3, :frozen?)
- assert_predicate(s4, :frozen?)
+ assert_not_predicate(s3, :frozen?)
+ assert_not_predicate(s4, :frozen?)
end
# Safe call chain is not optimized when Coverage is running.
@@ -241,9 +410,25 @@ class TestISeq < Test::Unit::TestCase
end
end
assert_equal([m1, e1.message], [m2, e2.message], feature11951)
- e1, e2 = e1.message.lines
- assert_send([e1, :start_with?, __FILE__])
- assert_send([e2, :start_with?, __FILE__])
+
+ if e1.message.lines[0] == "#{__FILE__}:#{line}: syntax errors found\n"
+ # Prism lays out the error messages in line with the source, so the
+ # following assertions do not make sense in that context.
+ else
+ message = e1.message.each_line
+ message.with_index(1) do |line, i|
+ next if /^ / =~ line
+ assert_send([line, :start_with?, __FILE__],
+ proc {message.map {|l, j| (i == j ? ">" : " ") + l}.join("")})
+ end
+ end
+ end
+
+ # [Bug #19173]
+ def test_compile_error
+ assert_raise SyntaxError do
+ RubyVM::InstructionSequence.compile 'using Module.new; yield'
+ end
end
def test_compile_file_error
@@ -251,7 +436,7 @@ class TestISeq < Test::Unit::TestCase
f.puts "end"
f.close
path = f.path
- assert_in_out_err(%W[- #{path}], "#{<<-"begin;"}\n#{<<-"end;"}", /keyword_end/, [], success: true)
+ assert_in_out_err(%W[- #{path}], "#{<<-"begin;"}\n#{<<-"end;"}", /unexpected 'end'/, [], success: true)
begin;
path = ARGV[0]
begin
@@ -275,14 +460,42 @@ class TestISeq < Test::Unit::TestCase
def test_inspect
%W[foo \u{30d1 30b9}].each do |name|
- assert_match /@#{name}/, ISeq.compile("", name).inspect, name
+ assert_match(/@#{name}/, ISeq.compile("", name).inspect, name)
m = ISeq.compile("class TestISeq::Inspect; def #{name}; end; instance_method(:#{name}); end").eval
- assert_match /:#{name}@/, ISeq.of(m).inspect, name
+ assert_match(/:#{name}@/, ISeq.of(m).inspect, name)
end
end
+ def anon_star(*); end
+
+ def test_anon_rest_param_in_disasm
+ iseq = RubyVM::InstructionSequence.of(method(:anon_star))
+ param_names = iseq.to_a[iseq.to_a.index(:method) + 1]
+ assert_equal [:*], param_names
+ end
+
+ def anon_keyrest(**); end
+
+ def test_anon_keyrest_param_in_disasm
+ iseq = RubyVM::InstructionSequence.of(method(:anon_keyrest))
+ param_names = iseq.to_a[iseq.to_a.index(:method) + 1]
+ assert_equal [:**], param_names
+ end
+
+ def anon_block(&); end
+
+ def test_anon_block_param_in_disasm
+ iseq = RubyVM::InstructionSequence.of(method(:anon_block))
+ param_names = iseq.to_a[iseq.to_a.index(:method) + 1]
+ assert_equal [:&], param_names
+ end
+
+ def strip_lineno(source)
+ source.gsub(/^.*?: /, "")
+ end
+
def sample_iseq
- ISeq.compile <<-EOS.gsub(/^.*?: /, "")
+ ISeq.compile(strip_lineno(<<-EOS))
1: class C
2: def foo
3: begin
@@ -319,7 +532,7 @@ class TestISeq < Test::Unit::TestCase
["<class:C>@1",
["bar@10", ["block in bar@11",
["block (2 levels) in bar@12"]]],
- ["foo@2", ["ensure in foo@2"],
+ ["foo@2", ["ensure in foo@7"],
["rescue in foo@4"]]],
["<class:D>@17"]]
@@ -352,8 +565,9 @@ class TestISeq < Test::Unit::TestCase
[4, :line],
[7, :line],
[9, :return]]],
- [["ensure in foo@2", [[7, :line]]]],
- [["rescue in foo@4", [[5, :line]]]]]],
+ [["ensure in foo@7", [[7, :line]]]],
+ [["rescue in foo@4", [[5, :line],
+ [5, :rescue]]]]]],
[["<class:D>@17", [[17, :class],
[18, :end]]]]], collect_iseq.call(sample_iseq)
end
@@ -375,7 +589,7 @@ class TestISeq < Test::Unit::TestCase
type = ary[9]
name = ary[5]
line = ary[13].first
- case ary[9]
+ case type
when :method
assert_equal "foo", name
assert_equal 3, line
@@ -391,44 +605,392 @@ class TestISeq < Test::Unit::TestCase
}
end
- def test_to_binary_with_objects
- # conceptually backport from r62856.
- # ISeq binary dump doesn't consider alignment in 2.5 and older
- skip "does not work on other than x86" unless /x(?:86|64)|i\d86/ =~ RUBY_PLATFORM
- code = "[]"+100.times.map{|i|"<</#{i}/"}.join
+ def hexdump(bin)
+ bin.unpack1("H*").gsub(/.{1,32}/) {|s|
+ "#{'%04x:' % $~.begin(0)}#{s.gsub(/../, " \\&").tap{|_|_[24]&&="-"}}\n"
+ }
+ end
+
+ def iseq_to_binary(iseq)
+ iseq.to_binary
+ rescue RuntimeError => e
+ omit e.message if /compile with coverage/ =~ e.message
+ raise
+ end
+
+ def assert_iseq_to_binary(code, mesg = nil)
iseq = RubyVM::InstructionSequence.compile(code)
- bin = assert_nothing_raised do
- iseq.to_binary
- rescue RuntimeError => e
- skip e.message if /compile with coverage/ =~ e.message
- raise
+ bin = assert_nothing_raised(mesg) do
+ iseq_to_binary(iseq)
+ end
+ 10.times do
+ bin2 = iseq_to_binary(iseq)
+ assert_equal(bin, bin2, message(mesg) {diff hexdump(bin), hexdump(bin2)})
end
iseq2 = RubyVM::InstructionSequence.load_from_binary(bin)
- assert_equal(iseq2.to_a, iseq.to_a)
+ a1 = iseq.to_a
+ a2 = iseq2.to_a
+ assert_equal(a1, a2, message(mesg) {diff iseq.disassemble, iseq2.disassemble})
+ if iseq2.script_lines
+ assert_kind_of(Array, iseq2.script_lines)
+ else
+ assert_nil(iseq2.script_lines)
+ end
+ iseq2
end
- def test_to_binary_tracepoint
- # conceptually backport from r62856.
- # ISeq binary dump doesn't consider alignment in 2.5 and older
- skip "does not work on other than x86" unless /x(?:86|64)|i\d86/ =~ RUBY_PLATFORM
- filename = "#{File.basename(__FILE__)}_#{__LINE__}"
- iseq = RubyVM::InstructionSequence.compile("x = 1\n y = 2", filename)
- # conceptually partial backport from r63103, r65567.
- # All ISeq#to_binary should rescue to skip when running with coverage.
- # current trunk (2.6-) has assert_iseq_to_binary.
- begin
- iseq_bin = iseq.to_binary
- rescue RuntimeError => e
- skip e.message if /compile with coverage/ =~ e.message
- raise
+ def test_to_binary_with_hidden_local_variables
+ assert_iseq_to_binary("for _foo in bar; end")
+
+ bin = iseq_to_binary(RubyVM::InstructionSequence.compile(<<-RUBY))
+ Object.new.instance_eval do
+ a = []
+ def self.bar; [1] end
+ for foo in bar
+ a << (foo * 2)
+ end
+ a
+ end
+ RUBY
+ v = RubyVM::InstructionSequence.load_from_binary(bin).eval
+ assert_equal([2], v)
+ end
+
+ def test_to_binary_with_objects
+ assert_iseq_to_binary("[]"+100.times.map{|i|"<</#{i}/"}.join)
+ assert_iseq_to_binary("@x ||= (1..2)")
+ end
+
+ def test_to_binary_pattern_matching
+ code = "case foo; in []; end"
+ iseq = compile(code)
+ assert_include(iseq.disasm, "TypeError")
+ assert_include(iseq.disasm, "NoMatchingPatternError")
+ EnvUtil.suppress_warning do
+ assert_iseq_to_binary(code, "[Feature #14912]")
end
- ary = []
- TracePoint.new(:line){|tp|
+ end
+
+ def test_to_binary_dumps_nokey
+ iseq = assert_iseq_to_binary(<<-RUBY)
+ o = Object.new
+ class << o
+ def foo(**nil); end
+ end
+ o
+ RUBY
+ assert_equal([[:nokey]], iseq.eval.singleton_method(:foo).parameters)
+ end
+
+ def test_to_binary_line_info
+ assert_iseq_to_binary("#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #14660]').eval
+ begin;
+ class P
+ def p; end
+ def q; end
+ E = ""
+ N = "#{E}"
+ attr_reader :i
+ end
+ end;
+
+ # cleanup
+ ::Object.class_eval do
+ remove_const :P
+ end
+ end
+
+ def collect_from_binary_tracepoint_lines(tracepoint_type, filename)
+ iseq = RubyVM::InstructionSequence.compile(strip_lineno(<<-RUBY), filename)
+ class A
+ class B
+ 2.times {
+ def self.foo
+ _a = 'good day'
+ raise
+ rescue
+ 'dear reader'
+ end
+ }
+ end
+ B.foo
+ end
+ RUBY
+
+ iseq_bin = iseq_to_binary(iseq)
+ iseq = ISeq.load_from_binary(iseq_bin)
+ lines = []
+ TracePoint.new(tracepoint_type){|tp|
next unless tp.path == filename
- ary << [tp.path, tp.lineno]
+ lines << tp.lineno
}.enable{
- ISeq.load_from_binary(iseq_bin).eval
+ EnvUtil.suppress_warning {iseq.eval}
}
- assert_equal [[filename, 1], [filename, 2]], ary, '[Bug #14702]'
+
+ lines
+ ensure
+ Object.send(:remove_const, :A) rescue nil
+ end
+
+ def test_to_binary_line_tracepoint
+ filename = "#{File.basename(__FILE__)}_#{__LINE__}"
+ lines = collect_from_binary_tracepoint_lines(:line, filename)
+
+ assert_equal [1, 2, 3, 4, 4, 12, 5, 6, 8], lines, '[Bug #14702]'
+ end
+
+ def test_to_binary_class_tracepoint
+ filename = "#{File.basename(__FILE__)}_#{__LINE__}"
+ lines = collect_from_binary_tracepoint_lines(:class, filename)
+
+ assert_equal [1, 2], lines, '[Bug #14702]'
+ end
+
+ def test_to_binary_end_tracepoint
+ filename = "#{File.basename(__FILE__)}_#{__LINE__}"
+ lines = collect_from_binary_tracepoint_lines(:end, filename)
+
+ assert_equal [11, 13], lines, '[Bug #14702]'
+ end
+
+ def test_to_binary_return_tracepoint
+ filename = "#{File.basename(__FILE__)}_#{__LINE__}"
+ lines = collect_from_binary_tracepoint_lines(:return, filename)
+
+ assert_equal [9], lines, '[Bug #14702]'
+ end
+
+ def test_to_binary_b_call_tracepoint
+ filename = "#{File.basename(__FILE__)}_#{__LINE__}"
+ lines = collect_from_binary_tracepoint_lines(:b_call, filename)
+
+ assert_equal [3, 3], lines, '[Bug #14702]'
+ end
+
+ def test_to_binary_b_return_tracepoint
+ filename = "#{File.basename(__FILE__)}_#{__LINE__}"
+ lines = collect_from_binary_tracepoint_lines(:b_return, filename)
+
+ assert_equal [10, 10], lines, '[Bug #14702]'
+ end
+
+ def test_iseq_of
+ [
+ proc{},
+ method(:test_iseq_of),
+ RubyVM::InstructionSequence.compile("p 1", __FILE__),
+ begin; raise "error"; rescue => error; error.backtrace_locations[0]; end
+ ].each{|src|
+ iseq = RubyVM::InstructionSequence.of(src)
+ assert_equal __FILE__, iseq.path
+ }
+ end
+
+ def test_iseq_of_twice_for_same_code
+ [
+ proc{},
+ method(:test_iseq_of_twice_for_same_code),
+ RubyVM::InstructionSequence.compile("p 1"),
+ begin; raise "error"; rescue => error; error.backtrace_locations[0]; end
+ ].each{|src|
+ iseq1 = RubyVM::InstructionSequence.of(src)
+ iseq2 = RubyVM::InstructionSequence.of(src)
+
+ # ISeq objects should be same for same src
+ assert_equal iseq1.object_id, iseq2.object_id
+ }
+ end
+
+ def test_iseq_builtin_to_a
+ invokebuiltin = eval(EnvUtil.invoke_ruby(['-e', <<~EOS], '', true).first)
+ insns = RubyVM::InstructionSequence.of([].method(:pack)).to_a.last
+ p insns.find { |insn| insn.is_a?(Array) && insn[0] == :opt_invokebuiltin_delegate_leave }
+ EOS
+ assert_not_nil(invokebuiltin)
+ assert_equal([:func_ptr, :argc, :index, :name], invokebuiltin[1].keys)
+ end
+
+ def test_iseq_builtin_load
+ Tempfile.create(["builtin", ".iseq"]) do |f|
+ f.binmode
+ f.write(iseq_to_binary(RubyVM::InstructionSequence.of(1.method(:abs))))
+ f.close
+ assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ bin = File.binread(ARGV[0])
+ assert_raise(ArgumentError) do
+ RubyVM::InstructionSequence.load_from_binary(bin)
+ end
+ end;
+ end
+ end
+
+ def test_iseq_option_debug_level
+ assert_raise(TypeError) {ISeq.compile("", debug_level: "")}
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ RubyVM::InstructionSequence.compile("", debug_level: 5)
+ end;
+ end
+
+ def test_mandatory_only
+ assert_separately [], <<~RUBY
+ at0 = Time.at(0)
+ assert_equal at0, Time.public_send(:at, 0, 0)
+ RUBY
+ end
+
+ def test_mandatory_only_redef
+ assert_separately ['-W0'], <<~RUBY
+ r = Ractor.new{
+ Float(10)
+ module Kernel
+ undef Float
+ def Float(n)
+ :new
+ end
+ end
+ GC.start
+ Float(30)
+ }
+ assert_equal :new, r.value
+ RUBY
+ end
+
+ def test_ever_condition_loop
+ assert_ruby_status([], "BEGIN {exit}; while true && true; end")
+ end
+
+ def test_unreachable_syntax_error
+ mesg = /Invalid break/
+ assert_syntax_error("false and break", mesg)
+ assert_syntax_error("if false and break; end", mesg)
+ end
+
+ def test_unreachable_pattern_matching
+ assert_in_out_err([], "true or 1 in 1")
+ assert_in_out_err([], "true or (case 1; in 1; 1; in 2; 2; end)")
+ end
+
+ def test_unreachable_pattern_matching_in_if_condition
+ assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", %w[1])
+ begin;
+ if true or {a: 0} in {a:}
+ p 1
+ else
+ p a
+ end
+ end;
+ end
+
+ def test_unreachable_next_in_block
+ bug20344 = '[ruby-core:117210] [Bug #20344]'
+ assert_nothing_raised(SyntaxError, bug20344) do
+ compile(<<~RUBY)
+ proc do
+ next
+
+ case nil
+ when "a"
+ next
+ when "b"
+ when "c"
+ proc {}
+ end
+
+ next
+ end
+ RUBY
+ 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 = RubyVM::InstructionSequence.compile("foo(bar: :baz)").to_binary
+ begin;
+ 1_000_000.times do
+ RubyVM::InstructionSequence.load_from_binary(a)
+ end
+ end;
+ end
+
+ def test_ibf_bignum
+ iseq = RubyVM::InstructionSequence.compile("0x0"+"_0123_4567_89ab_cdef"*5)
+ expected = iseq.eval
+ result = RubyVM::InstructionSequence.load_from_binary(iseq_to_binary(iseq)).eval
+ assert_equal expected, result, proc {sprintf("expected: %x, result: %x", expected, result)}
+ end
+
+ def test_compile_prism_with_file
+ Tempfile.create(%w"test_iseq .rb") do |f|
+ f.puts "_name = 'Prism'; puts 'hello'"
+ f.close
+
+ assert_nothing_raised(TypeError) do
+ RubyVM::InstructionSequence.compile_prism(f)
+ end
+ end
+ end
+
+ def block_using_method
+ yield
+ end
+
+ def block_unused_method
+ end
+
+ def test_unused_param
+ a = RubyVM::InstructionSequence.of(method(:block_using_method)).to_a
+
+ assert_equal true, a.dig(11, :use_block)
+
+ b = RubyVM::InstructionSequence.of(method(:block_unused_method)).to_a
+ assert_equal nil, b.dig(11, :use_block)
+ end
+
+ def test_compile_prism_with_invalid_object_type
+ assert_raise(TypeError) do
+ RubyVM::InstructionSequence.compile_prism(Object.new)
+ end
+ end
+
+ def test_load_from_binary_only_accepts_string_param
+ assert_raise(TypeError) do
+ var_0 = 0
+ RubyVM::InstructionSequence.load_from_binary(var_0)
+ end
+ end
+
+ def test_while_in_until_condition
+ assert_in_out_err(["--dump=i", "-e", "until while 1; end; end"]) do |stdout, stderr, status|
+ assert_include(stdout.shift, "== disasm:")
+ assert_include(stdout.pop, "leave")
+ assert_predicate(status, :success?)
+ end
+ end
+
+ def test_compile_empty_under_gc_stress
+ EnvUtil.under_gc_stress do
+ RubyVM::InstructionSequence.compile_file(File::NULL)
+ end
end
end
diff --git a/test/ruby/test_iterator.rb b/test/ruby/test_iterator.rb
index 9bfa947607..1bb655d52e 100644
--- a/test/ruby/test_iterator.rb
+++ b/test/ruby/test_iterator.rb
@@ -12,17 +12,12 @@ class Array
end
class TestIterator < Test::Unit::TestCase
- def ttt
- assert(iterator?)
- end
-
- def test_iterator
- assert(!iterator?)
-
- ttt{}
-
- # yield at top level !! here's not toplevel
- assert(!defined?(yield))
+ def test_yield_at_toplevel
+ assert_separately([],"#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert(!block_given?)
+ assert(!defined?(yield))
+ end;
end
def test_array
@@ -107,6 +102,16 @@ class TestIterator < Test::Unit::TestCase
assert_equal([1, 2, 3, 4, 5, 6, 7], x)
end
+ def test_array_for_masgn
+ a = [Struct.new(:to_ary).new([1,2])]
+ x = []
+ a.each {|i,j|x << [i,j]}
+ assert_equal([[1,2]], x)
+ x = []
+ for i,j in a; x << [i,j]; end
+ assert_equal([[1,2]], x)
+ end
+
def test_append_method_to_built_in_class
x = [[1,2],[3,4],[5,6]]
assert_equal(x.iter_test1{|e|e}, x.iter_test2{|e|e})
@@ -170,10 +175,13 @@ class TestIterator < Test::Unit::TestCase
end
def test_block_given
+ verbose_bak, $VERBOSE = $VERBOSE, nil
assert(m1{p 'test'})
assert(m2{p 'test'})
assert(!m1())
assert(!m2())
+ ensure
+ $VERBOSE = verbose_bak
end
def m3(var, &block)
@@ -303,7 +311,18 @@ class TestIterator < Test::Unit::TestCase
def test_ljump
assert_raise(LocalJumpError) {get_block{break}.call}
- assert_raise(LocalJumpError) {proc_call2(get_block{break}){}}
+ begin
+ verbose_bak, $VERBOSE = $VERBOSE, nil
+ # See the commit https://github.com/ruby/ruby/commit/7d8a415bc2d08a1b5e9d1ea802493b6eeb99c219
+ # This block is not used but this is intentional.
+ # |
+ # +-----------------------------------------------------+
+ # |
+ # vv
+ assert_raise(LocalJumpError) {proc_call2(get_block{break}){}}
+ ensure
+ $VERBOSE = verbose_bak
+ end
# cannot use assert_nothing_raised due to passing block.
begin
@@ -334,8 +353,7 @@ class TestIterator < Test::Unit::TestCase
marity_test(:marity_test)
marity_test(:p)
- lambda(&method(:assert)).call(true)
- lambda(&get_block{|a,n| assert(a,n)}).call(true, "marity")
+ get_block{|a,n| assert(a,n)}.call(true, "marity")
end
def foo
diff --git a/test/ruby/test_key_error.rb b/test/ruby/test_key_error.rb
new file mode 100644
index 0000000000..fe1d5bb5ab
--- /dev/null
+++ b/test/ruby/test_key_error.rb
@@ -0,0 +1,42 @@
+require 'test/unit'
+
+class TestKeyError < Test::Unit::TestCase
+ def test_default
+ error = KeyError.new
+ assert_equal("KeyError", error.message)
+ end
+
+ def test_message
+ error = KeyError.new("Message")
+ assert_equal("Message", error.message)
+ end
+
+ def test_receiver
+ receiver = Object.new
+ error = KeyError.new(receiver: receiver)
+ assert_equal(receiver, error.receiver)
+ error = KeyError.new
+ assert_raise(ArgumentError) {error.receiver}
+ end
+
+ def test_key
+ error = KeyError.new(key: :key)
+ assert_equal(:key, error.key)
+ error = KeyError.new
+ assert_raise(ArgumentError) {error.key}
+ end
+
+ def test_receiver_and_key
+ receiver = Object.new
+ error = KeyError.new(receiver: receiver, key: :key)
+ assert_equal([receiver, :key],
+ [error.receiver, error.key])
+ end
+
+ def test_all
+ receiver = Object.new
+ error = KeyError.new("Message", receiver: receiver, key: :key)
+ assert_equal(["Message", receiver, :key],
+ [error.message, error.receiver, error.key])
+ end
+end
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index 8a45016c13..c836abd0c6 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: false
require 'test/unit'
+require '-test-/rb_call_super_kw'
+require '-test-/iter'
class TestKeywordArguments < Test::Unit::TestCase
def f1(str: "foo", num: 424242)
@@ -22,7 +24,7 @@ class TestKeywordArguments < Test::Unit::TestCase
def test_f2
assert_equal([:xyz, "foo", 424242], f2(:xyz))
- assert_equal([{"bar"=>42}, "foo", 424242], f2("bar"=>42))
+ assert_raise(ArgumentError) { f2("bar"=>42) }
end
@@ -126,6 +128,47 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal(1, f10(b: 42))
end
+ def f11(**nil)
+ local_variables
+ end
+
+ def test_f11
+ h = {}
+
+ assert_equal([], f11)
+ assert_equal([], f11(**{}))
+ assert_equal([], f11(**h))
+ end
+
+ def f12(**nil, &b)
+ [b, local_variables]
+ end
+
+ def test_f12
+ h = {}
+ b = proc{}
+
+ assert_equal([nil, [:b]], f12)
+ assert_equal([nil, [:b]], f12(**{}))
+ assert_equal([nil, [:b]], f12(**h))
+ assert_equal([b, [:b]], f12(&b))
+ assert_equal([b, [:b]], f12(**{}, &b))
+ assert_equal([b, [:b]], f12(**h, &b))
+ end
+
+ def f13(a, **nil)
+ a
+ end
+
+ def test_f13
+ assert_equal(1, f13(1))
+ assert_equal(1, f13(1, **{}))
+ assert_raise(ArgumentError) { f13(a: 1) }
+ assert_raise(ArgumentError) { f13(1, a: 1) }
+ assert_raise(ArgumentError) { f13(**{a: 1}) }
+ assert_raise(ArgumentError) { f13(1, **{a: 1}) }
+ end
+
def test_method_parameters
assert_equal([[:key, :str], [:key, :num]], method(:f1).parameters);
assert_equal([[:req, :x], [:key, :str], [:key, :num]], method(:f2).parameters);
@@ -139,6 +182,52 @@ class TestKeywordArguments < Test::Unit::TestCase
[:keyrest, :kw], [:block, :b]], method(:f9).parameters)
end
+ def test_keyword_with_anonymous_keyword_splat
+ def self.a(b: 1, **) [b, **] end
+ kw = {b: 2, c: 3}
+ assert_equal([2, {c: 3}], a(**kw))
+ assert_equal({b: 2, c: 3}, kw)
+ end
+
+ def test_keyword_splat_nil
+ # cfunc call
+ assert_equal(nil, p(**nil))
+
+ def self.a0(&); end
+ assert_equal(nil, a0(**nil))
+ assert_equal(nil, :a0.to_proc.call(self, **nil))
+ assert_equal(nil, a0(**nil, &:block))
+
+ def self.o(x=1); x end
+ assert_equal(1, o(**nil))
+ assert_equal(2, o(2, **nil))
+ assert_equal(1, o(*nil, **nil))
+ assert_equal(1, o(**nil, **nil))
+ assert_equal({a: 1}, o(a: 1, **nil))
+ assert_equal({a: 1}, o(**nil, a: 1))
+
+ # symproc call
+ assert_equal(1, :o.to_proc.call(self, **nil))
+
+ def self.s(*a); a end
+ assert_equal([], s(**nil))
+ assert_equal([1], s(1, **nil))
+ assert_equal([], s(*nil, **nil))
+
+ def self.kws(**a); a end
+ assert_equal({}, kws(**nil))
+ assert_equal({}, kws(*nil, **nil))
+
+ def self.skws(*a, **kw); [a, kw] end
+ assert_equal([[], {}], skws(**nil))
+ assert_equal([[1], {}], skws(1, **nil))
+ assert_equal([[], {}], skws(*nil, **nil))
+
+ assert_equal({}, {**nil})
+ assert_equal({a: 1}, {a: 1, **nil})
+ assert_equal({a: 1}, {**nil, a: 1})
+ end
+
def test_lambda
f = ->(str: "foo", num: 424242) { [str, num] }
assert_equal(["foo", 424242], f[])
@@ -147,6 +236,3362 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal(["bar", 111111], f[str: "bar", num: 111111])
end
+ def test_unset_hash_flag
+ bug18625 = "[ruby-core: 107847]"
+ singleton_class.class_eval do
+ ruby2_keywords def foo(*args)
+ args
+ end
+
+ def single(arg)
+ arg
+ end
+
+ def splat(*args)
+ args.last
+ end
+
+ def kwargs(**kw)
+ kw
+ end
+ end
+
+ h = { a: 1 }
+ args = foo(**h)
+ marked = args.last
+ assert_equal(true, Hash.ruby2_keywords_hash?(marked))
+
+ after_usage = single(*args)
+ assert_equal(h, after_usage)
+ assert_same(marked, args.last)
+ assert_not_same(marked, after_usage)
+ assert_equal(false, Hash.ruby2_keywords_hash?(after_usage))
+
+ after_usage = splat(*args)
+ assert_equal(h, after_usage)
+ assert_same(marked, args.last)
+ assert_not_same(marked, after_usage, bug18625)
+ assert_equal(false, Hash.ruby2_keywords_hash?(after_usage), bug18625)
+
+ after_usage = kwargs(*args)
+ assert_equal(h, after_usage)
+ assert_same(marked, args.last)
+ assert_not_same(marked, after_usage, bug18625)
+ assert_not_same(marked, after_usage)
+ assert_equal(false, Hash.ruby2_keywords_hash?(after_usage))
+
+ assert_equal(true, Hash.ruby2_keywords_hash?(marked))
+ end
+
+ def assert_equal_not_same(kw, res)
+ assert_instance_of(Hash, res)
+ assert_equal(kw, res)
+ assert_not_same(kw, res)
+ end
+
+ def test_keyword_splat_new
+ kw = {}
+ h = {a: 1}
+
+ def self.yo(**kw) kw end
+ m = method(:yo)
+ assert_equal(false, yo(**{}).frozen?)
+ assert_equal_not_same(kw, yo(**kw))
+ assert_equal_not_same(h, yo(**h))
+ assert_equal(false, send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, send(:yo, **kw))
+ assert_equal_not_same(h, send(:yo, **h))
+ assert_equal(false, public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, public_send(:yo, **kw))
+ assert_equal_not_same(h, public_send(:yo, **h))
+ assert_equal(false, m.(**{}).frozen?)
+ assert_equal_not_same(kw, m.(**kw))
+ assert_equal_not_same(h, m.(**h))
+ assert_equal(false, m.send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, m.send(:call, **kw))
+ assert_equal_not_same(h, m.send(:call, **h))
+
+ m = method(:send)
+ assert_equal(false, m.(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, m.(:yo, **kw))
+ assert_equal_not_same(h, m.(:yo, **h))
+ assert_equal(false, m.send(:call, :yo, **{}).frozen?)
+ assert_equal_not_same(kw, m.send(:call, :yo, **kw))
+ assert_equal_not_same(h, m.send(:call, :yo, **h))
+
+ singleton_class.send(:remove_method, :yo)
+ define_singleton_method(:yo) { |**kw| kw }
+ m = method(:yo)
+ assert_equal(false, yo(**{}).frozen?)
+ assert_equal_not_same(kw, yo(**kw))
+ assert_equal_not_same(h, yo(**h))
+ assert_equal(false, send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, send(:yo, **kw))
+ assert_equal_not_same(h, send(:yo, **h))
+ assert_equal(false, public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, public_send(:yo, **kw))
+ assert_equal_not_same(h, public_send(:yo, **h))
+ assert_equal(false, m.(**{}).frozen?)
+ assert_equal_not_same(kw, m.(**kw))
+ assert_equal_not_same(h, m.(**h))
+ assert_equal(false, m.send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, m.send(:call, **kw))
+ assert_equal_not_same(h, m.send(:call, **h))
+
+ yo = lambda { |**kw| kw }
+ m = yo.method(:call)
+ assert_equal(false, yo.(**{}).frozen?)
+ assert_equal_not_same(kw, yo.(**kw))
+ assert_equal_not_same(h, yo.(**h))
+ assert_equal(false, yo.send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, yo.send(:call, **kw))
+ assert_equal_not_same(h, yo.send(:call, **h))
+ assert_equal(false, yo.public_send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, yo.public_send(:call, **kw))
+ assert_equal_not_same(h, yo.public_send(:call, **h))
+ assert_equal(false, m.(**{}).frozen?)
+ assert_equal_not_same(kw, m.(**kw))
+ assert_equal_not_same(h, m.(**h))
+ assert_equal(false, m.send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, m.send(:call, **kw))
+ assert_equal_not_same(h, m.send(:call, **h))
+
+ yo = :yo.to_proc
+ m = yo.method(:call)
+ assert_equal(false, yo.(self, **{}).frozen?)
+ assert_equal_not_same(kw, yo.(self, **kw))
+ assert_equal_not_same(h, yo.(self, **h))
+ assert_equal(false, yo.send(:call, self, **{}).frozen?)
+ assert_equal_not_same(kw, yo.send(:call, self, **kw))
+ assert_equal_not_same(h, yo.send(:call, self, **h))
+ assert_equal(false, yo.public_send(:call, self, **{}).frozen?)
+ assert_equal_not_same(kw, yo.public_send(:call, self, **kw))
+ assert_equal_not_same(h, yo.public_send(:call, self, **h))
+ assert_equal(false, m.(self, **{}).frozen?)
+ assert_equal_not_same(kw, m.(self, **kw))
+ assert_equal_not_same(h, m.(self, **h))
+ assert_equal(false, m.send(:call, self, **{}).frozen?)
+ assert_equal_not_same(kw, m.send(:call, self, **kw))
+ assert_equal_not_same(h, m.send(:call, self, **h))
+
+ c = Class.new do
+ def yo(**kw) kw end
+ end
+ o = c.new
+ def o.yo(**kw) super end
+ m = o.method(:yo)
+ assert_equal(false, o.yo(**{}).frozen?)
+ assert_equal_not_same(kw, o.yo(**kw))
+ assert_equal_not_same(h, o.yo(**h))
+ assert_equal(false, o.send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.send(:yo, **kw))
+ assert_equal_not_same(h, o.send(:yo, **h))
+ assert_equal(false, o.public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.public_send(:yo, **kw))
+ assert_equal_not_same(h, o.public_send(:yo, **h))
+ assert_equal(false, m.(**{}).frozen?)
+ assert_equal_not_same(kw, m.(**kw))
+ assert_equal_not_same(h, m.(**h))
+ assert_equal(false, m.send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, m.send(:call, **kw))
+ assert_equal_not_same(h, m.send(:call, **h))
+
+ o.singleton_class.send(:remove_method, :yo)
+ def o.yo(**kw) super(**kw) end
+ assert_equal(false, o.yo(**{}).frozen?)
+ assert_equal_not_same(kw, o.yo(**kw))
+ assert_equal_not_same(h, o.yo(**h))
+ assert_equal(false, o.send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.send(:yo, **kw))
+ assert_equal_not_same(h, o.send(:yo, **h))
+ assert_equal(false, o.public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.public_send(:yo, **kw))
+ assert_equal_not_same(h, o.public_send(:yo, **h))
+ assert_equal(false, m.(**{}).frozen?)
+ assert_equal_not_same(kw, m.(**kw))
+ assert_equal_not_same(h, m.(**h))
+ assert_equal(false, m.send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, m.send(:call, **kw))
+ assert_equal_not_same(h, m.send(:call, **h))
+
+ c = Class.new do
+ def method_missing(_, **kw) kw end
+ end
+ o = c.new
+ def o.yo(**kw) super end
+ m = o.method(:yo)
+ assert_equal(false, o.yo(**{}).frozen?)
+ assert_equal_not_same(kw, o.yo(**kw))
+ assert_equal_not_same(h, o.yo(**h))
+ assert_equal(false, o.send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.send(:yo, **kw))
+ assert_equal_not_same(h, o.send(:yo, **h))
+ assert_equal(false, o.public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.public_send(:yo, **kw))
+ assert_equal_not_same(h, o.public_send(:yo, **h))
+ assert_equal(false, m.(**{}).frozen?)
+ assert_equal_not_same(kw, m.(**kw))
+ assert_equal_not_same(h, m.(**h))
+ assert_equal(false, m.send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, m.send(:call, **kw))
+ assert_equal_not_same(h, m.send(:call, **h))
+
+ o.singleton_class.send(:remove_method, :yo)
+ def o.yo(**kw) super(**kw) end
+ assert_equal(false, o.yo(**{}).frozen?)
+ assert_equal_not_same(kw, o.yo(**kw))
+ assert_equal_not_same(h, o.yo(**h))
+ assert_equal(false, o.send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.send(:yo, **kw))
+ assert_equal_not_same(h, o.send(:yo, **h))
+ assert_equal(false, o.public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.public_send(:yo, **kw))
+ assert_equal_not_same(h, o.public_send(:yo, **h))
+ assert_equal(false, m.(**{}).frozen?)
+ assert_equal_not_same(kw, m.(**kw))
+ assert_equal_not_same(h, m.(**h))
+ assert_equal(false, m.send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, m.send(:call, **kw))
+ assert_equal_not_same(h, m.send(:call, **h))
+
+ c = Class.new do
+ attr_reader :kw
+ def initialize(**kw) @kw = kw end
+ end
+ m = c.method(:new)
+ assert_equal(false, c.new(**{}).kw.frozen?)
+ assert_equal_not_same(kw, c.new(**kw).kw)
+ assert_equal_not_same(h, c.new(**h).kw)
+ assert_equal(false, c.send(:new, **{}).kw.frozen?)
+ assert_equal_not_same(kw, c.send(:new, **kw).kw)
+ assert_equal_not_same(h, c.send(:new, **h).kw)
+ assert_equal(false, c.public_send(:new, **{}).kw.frozen?)
+ assert_equal_not_same(kw, c.public_send(:new, **kw).kw)
+ assert_equal_not_same(h, c.public_send(:new, **h).kw)
+ assert_equal(false, m.(**{}).kw.frozen?)
+ assert_equal_not_same(kw, m.(**kw).kw)
+ assert_equal_not_same(h, m.(**h).kw)
+ assert_equal(false, m.send(:call, **{}).kw.frozen?)
+ assert_equal_not_same(kw, m.send(:call, **kw).kw)
+ assert_equal_not_same(h, m.send(:call, **h).kw)
+
+ singleton_class.send(:attr_writer, :y)
+ m = method(:y=)
+ assert_equal_not_same(h, send(:y=, **h))
+ assert_equal_not_same(h, public_send(:y=, **h))
+ assert_equal_not_same(h, m.(**h))
+ assert_equal_not_same(h, m.send(:call, **h))
+
+ singleton_class.send(:remove_method, :yo)
+ def self.method_missing(_, **kw) kw end
+ assert_equal(false, yo(**{}).frozen?)
+ assert_equal_not_same(kw, yo(**kw))
+ assert_equal_not_same(h, yo(**h))
+ assert_equal(false, send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, send(:yo, **kw))
+ assert_equal_not_same(h, send(:yo, **h))
+ assert_equal(false, public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, public_send(:yo, **kw))
+ assert_equal_not_same(h, public_send(:yo, **h))
+
+ def self.yo(*a, **kw) = kw
+ assert_equal_not_same kw, yo(**kw)
+ assert_equal_not_same kw, yo(**kw, **kw)
+
+ singleton_class.send(:remove_method, :yo)
+ def self.yo(opts) = opts
+ assert_equal_not_same h, yo(*[], **h)
+ a = []
+ assert_equal_not_same h, yo(*a, **h)
+ end
+
+ def test_keyword_splat_to_non_keyword_method
+ h = {a: 1}.freeze
+
+ def self.yo(kw) kw end
+ assert_equal_not_same(h, yo(**h))
+ assert_equal_not_same(h, method(:yo).(**h))
+ assert_equal_not_same(h, :yo.to_proc.(self, **h))
+
+ def self.yoa(*kw) kw[0] end
+ assert_equal_not_same(h, yoa(**h))
+ assert_equal_not_same(h, method(:yoa).(**h))
+ assert_equal_not_same(h, :yoa.to_proc.(self, **h))
+ end
+
+ def test_regular_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.m(*args)
+ args
+ end
+ assert_equal([], c.m(**{}))
+ assert_equal([], c.m(**kw))
+ assert_equal([h], c.m(**h))
+ assert_equal([h], c.m(a: 1))
+ assert_equal([h2], c.m(**h2))
+ assert_equal([h3], c.m(**h3))
+ assert_equal([h3], c.m(a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m; end
+ assert_nil(c.m(**{}))
+ assert_nil(c.m(**kw))
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(args)
+ args
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_equal(kw, c.m(kw, **kw))
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(**args)
+ args
+ end
+ assert_equal(kw, c.m(**{}))
+ assert_equal(kw, c.m(**kw))
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+ assert_raise(ArgumentError) { c.m(h) }
+ assert_raise(ArgumentError) { c.m(h2) }
+ assert_raise(ArgumentError) { c.m(h3) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+ assert_equal([h, kw], c.m(h))
+ assert_equal([h2, kw], c.m(h2))
+ assert_equal([h3, kw], c.m(h3))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], c.m(**{}))
+ assert_equal([1, kw], c.m(**kw))
+ assert_equal([1, h], c.m(**h))
+ assert_equal([1, h], c.m(a: 1))
+ assert_equal([1, h2], c.m(**h2))
+ assert_equal([1, h3], c.m(**h3))
+ assert_equal([1, h3], c.m(a: 1, **h2))
+ assert_equal([h, kw], c.m(h))
+ assert_equal([h2, kw], c.m(h2))
+ assert_equal([h3, kw], c.m(h3))
+ end
+
+ def test_implicit_super_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ sc = Class.new
+ c = sc.new
+ def c.m(*args, **kw)
+ super(*args, **kw)
+ end
+ sc.class_eval do
+ def m(*args)
+ args
+ end
+ end
+ assert_equal([], c.m(**{}))
+ assert_equal([], c.m(**kw))
+ assert_equal([h], c.m(**h))
+ assert_equal([h], c.m(a: 1))
+ assert_equal([h2], c.m(**h2))
+ assert_equal([h3], c.m(**h3))
+ assert_equal([h3], c.m(a: 1, **h2))
+
+ sc.class_eval do
+ remove_method(:m)
+ def m; end
+ end
+ assert_nil(c.m(**{}))
+ assert_nil(c.m(**kw))
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ sc.class_eval do
+ remove_method(:m)
+ def m(args)
+ args
+ end
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+
+ sc.class_eval do
+ remove_method(:m)
+ def m(**args)
+ args
+ end
+ end
+ assert_equal(kw, c.m(**{}))
+ assert_equal(kw, c.m(**kw))
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+ assert_raise(ArgumentError) { c.m(h) }
+ assert_raise(ArgumentError) { c.m(h2) }
+ assert_raise(ArgumentError) { c.m(h3) }
+
+ sc.class_eval do
+ remove_method(:m)
+ def m(arg, **args)
+ [arg, args]
+ end
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ sc.class_eval do
+ remove_method(:m)
+ def m(arg=1, **args)
+ [arg, args]
+ end
+ end
+ assert_equal([1, kw], c.m(**{}))
+ assert_equal([1, kw], c.m(**kw))
+ assert_equal([1, h], c.m(**h))
+ assert_equal([1, h], c.m(a: 1))
+ assert_equal([1, h2], c.m(**h2))
+ assert_equal([1, h3], c.m(**h3))
+ assert_equal([1, h3], c.m(a: 1, **h2))
+ end
+
+ def test_explicit_super_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ sc = Class.new
+ c = sc.new
+ def c.m(*args, **kw)
+ super(*args, **kw)
+ end
+ sc.class_eval do
+ def m(*args)
+ args
+ end
+ end
+ assert_equal([], c.m(**{}))
+ assert_equal([], c.m(**kw))
+ assert_equal([h], c.m(**h))
+ assert_equal([h], c.m(a: 1))
+ assert_equal([h2], c.m(**h2))
+ assert_equal([h3], c.m(**h3))
+ assert_equal([h3], c.m(a: 1, **h2))
+
+ sc.class_eval do
+ remove_method(:m)
+ def m; end
+ end
+ assert_nil(c.m(**{}))
+ assert_nil(c.m(**kw))
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ sc.class_eval do
+ remove_method(:m)
+ def m(args)
+ args
+ end
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+
+ sc.class_eval do
+ remove_method(:m)
+ def m(**args)
+ args
+ end
+ end
+ assert_equal(kw, c.m(**{}))
+ assert_equal(kw, c.m(**kw))
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+ assert_raise(ArgumentError) { c.m(h) }
+ assert_raise(ArgumentError) { c.m(h2) }
+ assert_raise(ArgumentError) { c.m(h3) }
+
+ sc.class_eval do
+ remove_method(:m)
+ def m(arg, **args)
+ [arg, args]
+ end
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ sc.class_eval do
+ remove_method(:m)
+ def m(arg=1, **args)
+ [arg, args]
+ end
+ end
+ assert_equal([1, kw], c.m(**{}))
+ assert_equal([1, kw], c.m(**kw))
+ assert_equal([1, h], c.m(**h))
+ assert_equal([1, h], c.m(a: 1))
+ assert_equal([1, h2], c.m(**h2))
+ assert_equal([1, h3], c.m(**h3))
+ assert_equal([1, h3], c.m(a: 1, **h2))
+ end
+
+ def test_lambda_kwsplat_call
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ f = -> { true }
+ assert_equal(true, f[**{}])
+ assert_equal(true, f[**kw])
+ assert_raise(ArgumentError) { f[**h] }
+ assert_raise(ArgumentError) { f[a: 1] }
+ assert_raise(ArgumentError) { f[**h2] }
+ assert_raise(ArgumentError) { f[**h3] }
+
+ f = ->(a) { a }
+ assert_raise(ArgumentError) { f[**{}] }
+ assert_raise(ArgumentError) { f[**kw] }
+ assert_equal(h, f[**h])
+ assert_equal(h, f[a: 1])
+ assert_equal(h2, f[**h2])
+ assert_equal(h3, f[**h3])
+ assert_equal(h3, f[a: 1, **h2])
+
+ f = ->(**x) { x }
+ assert_equal(kw, f[**{}])
+ assert_equal(kw, f[**kw])
+ assert_equal(h, f[**h])
+ assert_equal(h, f[a: 1])
+ assert_equal(h2, f[**h2])
+ assert_equal(h3, f[**h3])
+ assert_equal(h3, f[a: 1, **h2])
+ assert_raise(ArgumentError) { f[h] }
+ assert_raise(ArgumentError) { f[h2] }
+ assert_raise(ArgumentError) { f[h3] }
+
+ f = ->(a, **x) { [a,x] }
+ assert_raise(ArgumentError) { f[**{}] }
+ assert_raise(ArgumentError) { f[**kw] }
+ assert_raise(ArgumentError) { f[**h] }
+ assert_raise(ArgumentError) { f[a: 1] }
+ assert_raise(ArgumentError) { f[**h2] }
+ assert_raise(ArgumentError) { f[**h3] }
+ assert_raise(ArgumentError) { f[a: 1, **h2] }
+
+ f = ->(a=1, **x) { [a, x] }
+ assert_equal([1, kw], f[**{}])
+ assert_equal([1, kw], f[**kw])
+ assert_equal([1, h], f[**h])
+ assert_equal([1, h], f[a: 1])
+ assert_equal([1, h2], f[**h2])
+ assert_equal([1, h3], f[**h3])
+ assert_equal([1, h3], f[a: 1, **h2])
+ end
+
+ def test_lambda_method_kwsplat_call
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ f = -> { true }
+ f = f.method(:call)
+ assert_equal(true, f[**{}])
+ assert_equal(true, f[**kw])
+ assert_raise(ArgumentError) { f[**h] }
+ assert_raise(ArgumentError) { f[a: 1] }
+ assert_raise(ArgumentError) { f[**h2] }
+ assert_raise(ArgumentError) { f[**h3] }
+
+ f = ->(a) { a }
+ f = f.method(:call)
+ assert_raise(ArgumentError) { f[**{}] }
+ assert_raise(ArgumentError) { f[**kw] }
+ assert_equal(h, f[**h])
+ assert_equal(h, f[a: 1])
+ assert_equal(h2, f[**h2])
+ assert_equal(h3, f[**h3])
+ assert_equal(h3, f[a: 1, **h2])
+
+ f = ->(**x) { x }
+ f = f.method(:call)
+ assert_equal(kw, f[**{}])
+ assert_equal(kw, f[**kw])
+ assert_equal(h, f[**h])
+ assert_equal(h, f[a: 1])
+ assert_equal(h2, f[**h2])
+ assert_equal(h3, f[**h3])
+ assert_equal(h3, f[a: 1, **h2])
+ assert_raise(ArgumentError) { f[h] }
+ assert_raise(ArgumentError) { f[h2] }
+ assert_raise(ArgumentError) { f[h3] }
+
+ f = ->(a, **x) { [a,x] }
+ f = f.method(:call)
+ assert_raise(ArgumentError) { f[**{}] }
+ assert_raise(ArgumentError) { f[**kw] }
+ assert_raise(ArgumentError) { f[**h] }
+ assert_raise(ArgumentError) { f[a: 1] }
+ assert_raise(ArgumentError) { f[**h2] }
+ assert_raise(ArgumentError) { f[**h3] }
+ assert_raise(ArgumentError) { f[a: 1, **h2] }
+
+ f = ->(a=1, **x) { [a, x] }
+ f = f.method(:call)
+ assert_equal([1, kw], f[**{}])
+ assert_equal([1, kw], f[**kw])
+ assert_equal([1, h], f[**h])
+ assert_equal([1, h], f[a: 1])
+ assert_equal([1, h2], f[**h2])
+ assert_equal([1, h3], f[**h3])
+ assert_equal([1, h3], f[a: 1, **h2])
+ end
+
+ def test_Thread_new_kwsplat
+ Thread.report_on_exception = false
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ t = Thread
+ f = -> { true }
+ assert_equal(true, t.new(**{}, &f).value)
+ assert_equal(true, t.new(**kw, &f).value)
+ assert_raise(ArgumentError) { t.new(**h, &f).value }
+ assert_raise(ArgumentError) { t.new(a: 1, &f).value }
+ assert_raise(ArgumentError) { t.new(**h2, &f).value }
+ assert_raise(ArgumentError) { t.new(**h3, &f).value }
+
+ f = ->(a) { a }
+ assert_raise(ArgumentError) { t.new(**{}, &f).value }
+ assert_raise(ArgumentError) { t.new(**kw, &f).value }
+ assert_equal(h, t.new(**h, &f).value)
+ assert_equal(h, t.new(a: 1, &f).value)
+ assert_equal(h2, t.new(**h2, &f).value)
+ assert_equal(h3, t.new(**h3, &f).value)
+ assert_equal(h3, t.new(a: 1, **h2, &f).value)
+
+ f = ->(**x) { x }
+ assert_equal(kw, t.new(**{}, &f).value)
+ assert_equal(kw, t.new(**kw, &f).value)
+ assert_equal(h, t.new(**h, &f).value)
+ assert_equal(h, t.new(a: 1, &f).value)
+ assert_equal(h2, t.new(**h2, &f).value)
+ assert_equal(h3, t.new(**h3, &f).value)
+ assert_equal(h3, t.new(a: 1, **h2, &f).value)
+ assert_raise(ArgumentError) { t.new(h, &f).value }
+ assert_raise(ArgumentError) { t.new(h2, &f).value }
+ assert_raise(ArgumentError) { t.new(h3, &f).value }
+
+ f = ->(a, **x) { [a,x] }
+ assert_raise(ArgumentError) { t.new(**{}, &f).value }
+ assert_raise(ArgumentError) { t.new(**kw, &f).value }
+ assert_raise(ArgumentError) { t.new(**h, &f).value }
+ assert_raise(ArgumentError) { t.new(a: 1, &f).value }
+ assert_raise(ArgumentError) { t.new(**h2, &f).value }
+ assert_raise(ArgumentError) { t.new(**h3, &f).value }
+ assert_raise(ArgumentError) { t.new(a: 1, **h2, &f).value }
+
+ f = ->(a=1, **x) { [a, x] }
+ assert_equal([1, kw], t.new(**{}, &f).value)
+ assert_equal([1, kw], t.new(**kw, &f).value)
+ assert_equal([1, h], t.new(**h, &f).value)
+ assert_equal([1, h], t.new(a: 1, &f).value)
+ assert_equal([1, h2], t.new(**h2, &f).value)
+ assert_equal([1, h3], t.new(**h3, &f).value)
+ assert_equal([1, h3], t.new(a: 1, **h2, &f).value)
+ ensure
+ Thread.report_on_exception = true
+ end
+
+ def test_Fiber_resume_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ t = Fiber
+ f = -> { true }
+ assert_equal(true, t.new(&f).resume(**{}))
+ assert_equal(true, t.new(&f).resume(**kw))
+ assert_raise(ArgumentError) { t.new(&f).resume(**h) }
+ assert_raise(ArgumentError) { t.new(&f).resume(a: 1) }
+ assert_raise(ArgumentError) { t.new(&f).resume(**h2) }
+ assert_raise(ArgumentError) { t.new(&f).resume(**h3) }
+
+ f = ->(a) { a }
+ assert_raise(ArgumentError) { t.new(&f).resume(**{}) }
+ assert_raise(ArgumentError) { t.new(&f).resume(**kw) }
+ assert_equal(h, t.new(&f).resume(**h))
+ assert_equal(h, t.new(&f).resume(a: 1))
+ assert_equal(h2, t.new(&f).resume(**h2))
+ assert_equal(h3, t.new(&f).resume(**h3))
+ assert_equal(h3, t.new(&f).resume(a: 1, **h2))
+
+ f = ->(**x) { x }
+ assert_equal(kw, t.new(&f).resume(**{}))
+ assert_equal(kw, t.new(&f).resume(**kw))
+ assert_equal(h, t.new(&f).resume(**h))
+ assert_equal(h, t.new(&f).resume(a: 1))
+ assert_equal(h2, t.new(&f).resume(**h2))
+ assert_equal(h3, t.new(&f).resume(**h3))
+ assert_equal(h3, t.new(&f).resume(a: 1, **h2))
+ assert_raise(ArgumentError) { t.new(&f).resume(h) }
+ assert_raise(ArgumentError) { t.new(&f).resume(h2) }
+ assert_raise(ArgumentError) { t.new(&f).resume(h3) }
+
+ f = ->(a, **x) { [a,x] }
+ assert_raise(ArgumentError) { t.new(&f).resume(**{}) }
+ assert_raise(ArgumentError) { t.new(&f).resume(**kw) }
+ assert_raise(ArgumentError) { t.new(&f).resume(**h) }
+ assert_raise(ArgumentError) { t.new(&f).resume(a: 1) }
+ assert_raise(ArgumentError) { t.new(&f).resume(**h2) }
+ assert_raise(ArgumentError) { t.new(&f).resume(**h3) }
+ assert_raise(ArgumentError) { t.new(&f).resume(a: 1, **h2) }
+
+ f = ->(a=1, **x) { [a, x] }
+ assert_equal([1, kw], t.new(&f).resume(**{}))
+ assert_equal([1, kw], t.new(&f).resume(**kw))
+ assert_equal([1, h], t.new(&f).resume(**h))
+ assert_equal([1, h], t.new(&f).resume(a: 1))
+ assert_equal([1, h2], t.new(&f).resume(**h2))
+ assert_equal([1, h3], t.new(&f).resume(**h3))
+ assert_equal([1, h3], t.new(&f).resume(a: 1, **h2))
+ end
+
+ def test_Enumerator_Generator_each_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ g = Enumerator::Generator
+ f = ->(_) { true }
+ assert_equal(true, g.new(&f).each(**{}))
+ assert_equal(true, g.new(&f).each(**kw))
+ assert_raise(ArgumentError) { g.new(&f).each(**h) }
+ assert_raise(ArgumentError) { g.new(&f).each(a: 1) }
+ assert_raise(ArgumentError) { g.new(&f).each(**h2) }
+ assert_raise(ArgumentError) { g.new(&f).each(**h3) }
+
+ f = ->(_, a) { a }
+ assert_raise(ArgumentError) { g.new(&f).each(**{}) }
+ assert_raise(ArgumentError) { g.new(&f).each(**kw) }
+ assert_equal(h, g.new(&f).each(**h))
+ assert_equal(h, g.new(&f).each(a: 1))
+ assert_equal(h2, g.new(&f).each(**h2))
+ assert_equal(h3, g.new(&f).each(**h3))
+ assert_equal(h3, g.new(&f).each(a: 1, **h2))
+
+ f = ->(_, **x) { x }
+ assert_equal(kw, g.new(&f).each(**{}))
+ assert_equal(kw, g.new(&f).each(**kw))
+ assert_equal(h, g.new(&f).each(**h))
+ assert_equal(h, g.new(&f).each(a: 1))
+ assert_equal(h2, g.new(&f).each(**h2))
+ assert_equal(h3, g.new(&f).each(**h3))
+ assert_equal(h3, g.new(&f).each(a: 1, **h2))
+ assert_raise(ArgumentError) { g.new(&f).each(h) }
+ assert_raise(ArgumentError) { g.new(&f).each(h2) }
+ assert_raise(ArgumentError) { g.new(&f).each(h3) }
+
+ f = ->(_, a, **x) { [a,x] }
+ assert_raise(ArgumentError) { g.new(&f).each(**{}) }
+ assert_raise(ArgumentError) { g.new(&f).each(**kw) }
+ assert_raise(ArgumentError) { g.new(&f).each(**h) }
+ assert_raise(ArgumentError) { g.new(&f).each(a: 1) }
+ assert_raise(ArgumentError) { g.new(&f).each(**h2) }
+ assert_raise(ArgumentError) { g.new(&f).each(**h3) }
+ assert_raise(ArgumentError) { g.new(&f).each(a: 1, **h2) }
+
+ f = ->(_, a=1, **x) { [a, x] }
+ assert_equal([1, kw], g.new(&f).each(**{}))
+ assert_equal([1, kw], g.new(&f).each(**kw))
+ assert_equal([1, h], g.new(&f).each(**h))
+ assert_equal([1, h], g.new(&f).each(a: 1))
+ assert_equal([1, h2], g.new(&f).each(**h2))
+ assert_equal([1, h3], g.new(&f).each(**h3))
+ assert_equal([1, h3], g.new(&f).each(a: 1, **h2))
+ end
+
+ def test_Enumerator_Yielder_yield_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ g = Enumerator::Generator
+ f = -> { true }
+ assert_equal(true, g.new{|y| y.yield(**{})}.each(&f))
+ assert_equal(true, g.new{|y| y.yield(**kw)}.each(&f))
+ assert_raise(ArgumentError) { g.new{|y| y.yield(**h)}.each(&f) }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(a: 1)}.each(&f) }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(**h2)}.each(&f) }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(**h3)}.each(&f) }
+
+ f = ->(a) { a }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(**{})}.each(&f) }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(**kw)}.each(&f) }
+ assert_equal(h, g.new{|y| y.yield(**h)}.each(&f))
+ assert_equal(h, g.new{|y| y.yield(a: 1)}.each(&f))
+ assert_equal(h2, g.new{|y| y.yield(**h2)}.each(&f))
+ assert_equal(h3, g.new{|y| y.yield(**h3)}.each(&f))
+ assert_equal(h3, g.new{|y| y.yield(a: 1, **h2)}.each(&f))
+
+ f = ->(**x) { x }
+ assert_equal(kw, g.new{|y| y.yield(**{})}.each(&f))
+ assert_equal(kw, g.new{|y| y.yield(**kw)}.each(&f))
+ assert_equal(h, g.new{|y| y.yield(**h)}.each(&f))
+ assert_equal(h, g.new{|y| y.yield(a: 1)}.each(&f))
+ assert_equal(h2, g.new{|y| y.yield(**h2)}.each(&f))
+ assert_equal(h3, g.new{|y| y.yield(**h3)}.each(&f))
+ assert_equal(h3, g.new{|y| y.yield(a: 1, **h2)}.each(&f))
+ assert_raise(ArgumentError) { g.new{|y| y.yield(h)}.each(&f) }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(h2)}.each(&f) }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(h3)}.each(&f) }
+
+ f = ->(a, **x) { [a,x] }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(**{})}.each(&f) }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(**kw)}.each(&f) }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(**h)}.each(&f) }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(a: 1)}.each(&f) }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(**h2)}.each(&f) }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(**h3)}.each(&f) }
+ assert_raise(ArgumentError) { g.new{|y| y.yield(a: 1, **h2)}.each(&f) }
+
+ f = ->(a=1, **x) { [a, x] }
+ assert_equal([1, kw], g.new{|y| y.yield(**{})}.each(&f))
+ assert_equal([1, kw], g.new{|y| y.yield(**kw)}.each(&f))
+ assert_equal([1, h], g.new{|y| y.yield(**h)}.each(&f))
+ assert_equal([1, h], g.new{|y| y.yield(a: 1)}.each(&f))
+ assert_equal([1, h2], g.new{|y| y.yield(**h2)}.each(&f))
+ assert_equal([1, h3], g.new{|y| y.yield(**h3)}.each(&f))
+ assert_equal([1, h3], g.new{|y| y.yield(a: 1, **h2)}.each(&f))
+ end
+
+ def test_Class_new_kwsplat_call
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ sc = Class.new do
+ attr_reader :args
+ class << self
+ alias [] new
+ end
+ end
+
+ c = Class.new(sc) do
+ def initialize(*args)
+ @args = args
+ end
+ end
+ assert_equal([], c[**{}].args)
+ assert_equal([], c[**kw].args)
+ assert_equal([h], c[**h].args)
+ assert_equal([h], c[a: 1].args)
+ assert_equal([h2], c[**h2].args)
+ assert_equal([h3], c[**h3].args)
+ assert_equal([h3], c[a: 1, **h2].args)
+
+ c = Class.new(sc) do
+ def initialize; end
+ end
+ assert_nil(c[**{}].args)
+ assert_nil(c[**kw].args)
+ assert_raise(ArgumentError) { c[**h] }
+ assert_raise(ArgumentError) { c[a: 1] }
+ assert_raise(ArgumentError) { c[**h2] }
+ assert_raise(ArgumentError) { c[**h3] }
+ assert_raise(ArgumentError) { c[a: 1, **h2] }
+
+ c = Class.new(sc) do
+ def initialize(args)
+ @args = args
+ end
+ end
+ assert_raise(ArgumentError) { c[**{}] }
+ assert_raise(ArgumentError) { c[**kw] }
+ assert_equal(h, c[**h].args)
+ assert_equal(h, c[a: 1].args)
+ assert_equal(h2, c[**h2].args)
+ assert_equal(h3, c[**h3].args)
+ assert_equal(h3, c[a: 1, **h2].args)
+
+ c = Class.new(sc) do
+ def initialize(**args)
+ @args = args
+ end
+ end
+ assert_equal(kw, c[**{}].args)
+ assert_equal(kw, c[**kw].args)
+ assert_equal(h, c[**h].args)
+ assert_equal(h, c[a: 1].args)
+ assert_equal(h2, c[**h2].args)
+ assert_equal(h3, c[**h3].args)
+ assert_equal(h3, c[a: 1, **h2].args)
+ assert_raise(ArgumentError) { c[h].args }
+ assert_raise(ArgumentError) { c[h2].args }
+ assert_raise(ArgumentError) { c[h3].args }
+
+ c = Class.new(sc) do
+ def initialize(arg, **args)
+ @args = [arg, args]
+ end
+ end
+ assert_raise(ArgumentError) { c[**{}].args }
+ assert_raise(ArgumentError) { c[**kw].args }
+ assert_raise(ArgumentError) { c[**h].args }
+ assert_raise(ArgumentError) { c[a: 1].args }
+ assert_raise(ArgumentError) { c[**h2].args }
+ assert_raise(ArgumentError) { c[**h3].args }
+ assert_raise(ArgumentError) { c[a: 1, **h2].args }
+
+ c = Class.new(sc) do
+ def initialize(arg=1, **args)
+ @args = [arg, args]
+ end
+ end
+ assert_equal([1, kw], c[**{}].args)
+ assert_equal([1, kw], c[**kw].args)
+ assert_equal([1, h], c[**h].args)
+ assert_equal([1, h], c[a: 1].args)
+ assert_equal([1, h2], c[**h2].args)
+ assert_equal([1, h3], c[**h3].args)
+ assert_equal([1, h3], c[a: 1, **h2].args)
+ end
+
+ def test_Class_new_method_kwsplat_call
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ sc = Class.new do
+ attr_reader :args
+ end
+
+ c = Class.new(sc) do
+ def initialize(*args)
+ @args = args
+ end
+ end.method(:new)
+ assert_equal([], c[**{}].args)
+ assert_equal([], c[**kw].args)
+ assert_equal([h], c[**h].args)
+ assert_equal([h], c[a: 1].args)
+ assert_equal([h2], c[**h2].args)
+ assert_equal([h3], c[**h3].args)
+ assert_equal([h3], c[a: 1, **h2].args)
+
+ c = Class.new(sc) do
+ def initialize; end
+ end.method(:new)
+ assert_nil(c[**{}].args)
+ assert_nil(c[**kw].args)
+ assert_raise(ArgumentError) { c[**h] }
+ assert_raise(ArgumentError) { c[a: 1] }
+ assert_raise(ArgumentError) { c[**h2] }
+ assert_raise(ArgumentError) { c[**h3] }
+ assert_raise(ArgumentError) { c[a: 1, **h2] }
+
+ c = Class.new(sc) do
+ def initialize(args)
+ @args = args
+ end
+ end.method(:new)
+ assert_raise(ArgumentError) { c[**{}] }
+ assert_raise(ArgumentError) { c[**kw] }
+ assert_equal(h, c[**h].args)
+ assert_equal(h, c[a: 1].args)
+ assert_equal(h2, c[**h2].args)
+ assert_equal(h3, c[**h3].args)
+ assert_equal(h3, c[a: 1, **h2].args)
+
+ c = Class.new(sc) do
+ def initialize(**args)
+ @args = args
+ end
+ end.method(:new)
+ assert_equal(kw, c[**{}].args)
+ assert_equal(kw, c[**kw].args)
+ assert_equal(h, c[**h].args)
+ assert_equal(h, c[a: 1].args)
+ assert_equal(h2, c[**h2].args)
+ assert_equal(h3, c[**h3].args)
+ assert_equal(h3, c[a: 1, **h2].args)
+ assert_raise(ArgumentError) { c[h].args }
+ assert_raise(ArgumentError) { c[h2].args }
+ assert_raise(ArgumentError) { c[h3].args }
+
+ c = Class.new(sc) do
+ def initialize(arg, **args)
+ @args = [arg, args]
+ end
+ end.method(:new)
+ assert_raise(ArgumentError) { c[**{}].args }
+ assert_raise(ArgumentError) { c[**kw].args }
+ assert_raise(ArgumentError) { c[**h].args }
+ assert_raise(ArgumentError) { c[a: 1].args }
+ assert_raise(ArgumentError) { c[**h2].args }
+ assert_raise(ArgumentError) { c[**h3].args }
+ assert_raise(ArgumentError) { c[a: 1, **h2].args }
+
+ c = Class.new(sc) do
+ def initialize(arg=1, **args)
+ @args = [arg, args]
+ end
+ end.method(:new)
+ assert_equal([1, kw], c[**{}].args)
+ assert_equal([1, kw], c[**kw].args)
+ assert_equal([1, h], c[**h].args)
+ assert_equal([1, h], c[a: 1].args)
+ assert_equal([1, h2], c[**h2].args)
+ assert_equal([1, h3], c[**h3].args)
+ assert_equal([1, h3], c[a: 1, **h2].args)
+ end
+
+ def test_Method_call_kwsplat_call
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.m(*args)
+ args
+ end
+ assert_equal([], c.method(:m)[**{}])
+ assert_equal([], c.method(:m)[**kw])
+ assert_equal([h], c.method(:m)[**h])
+ assert_equal([h], c.method(:m)[a: 1])
+ assert_equal([h2], c.method(:m)[**h2])
+ assert_equal([h3], c.method(:m)[**h3])
+ assert_equal([h3], c.method(:m)[a: 1, **h2])
+
+ c.singleton_class.remove_method(:m)
+ def c.m; end
+ assert_nil(c.method(:m)[**{}])
+ assert_nil(c.method(:m)[**kw])
+ assert_raise(ArgumentError) { c.method(:m)[**h] }
+ assert_raise(ArgumentError) { c.method(:m)[a: 1] }
+ assert_raise(ArgumentError) { c.method(:m)[**h2] }
+ assert_raise(ArgumentError) { c.method(:m)[**h3] }
+ assert_raise(ArgumentError) { c.method(:m)[a: 1, **h2] }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(args)
+ args
+ end
+ assert_raise(ArgumentError) { c.method(:m)[**{}] }
+ assert_raise(ArgumentError) { c.method(:m)[**kw] }
+ assert_equal(h, c.method(:m)[**h])
+ assert_equal(h, c.method(:m)[a: 1])
+ assert_equal(h2, c.method(:m)[**h2])
+ assert_equal(h3, c.method(:m)[**h3])
+ assert_equal(h3, c.method(:m)[a: 1, **h2])
+
+ c.singleton_class.remove_method(:m)
+ def c.m(**args)
+ args
+ end
+ assert_equal(kw, c.method(:m)[**{}])
+ assert_equal(kw, c.method(:m)[**kw])
+ assert_equal(h, c.method(:m)[**h])
+ assert_equal(h, c.method(:m)[a: 1])
+ assert_equal(h2, c.method(:m)[**h2])
+ assert_equal(h3, c.method(:m)[**h3])
+ assert_equal(h3, c.method(:m)[a: 1, **h2])
+ assert_raise(ArgumentError) { c.method(:m)[h] }
+ assert_raise(ArgumentError) { c.method(:m)[h2] }
+ assert_raise(ArgumentError) { c.method(:m)[h3] }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { c.method(:m)[**{}] }
+ assert_raise(ArgumentError) { c.method(:m)[**kw] }
+ assert_raise(ArgumentError) { c.method(:m)[**h] }
+ assert_raise(ArgumentError) { c.method(:m)[a: 1] }
+ assert_raise(ArgumentError) { c.method(:m)[**h2] }
+ assert_raise(ArgumentError) { c.method(:m)[**h3] }
+ assert_raise(ArgumentError) { c.method(:m)[a: 1, **h2] }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], c.method(:m)[**{}])
+ assert_equal([1, kw], c.method(:m)[**kw])
+ assert_equal([1, h], c.method(:m)[**h])
+ assert_equal([1, h], c.method(:m)[a: 1])
+ assert_equal([1, h2], c.method(:m)[**h2])
+ assert_equal([1, h3], c.method(:m)[**h3])
+ assert_equal([1, h3], c.method(:m)[a: 1, **h2])
+ end
+
+ def test_UnboundMethod_bindcall_kwsplat_call
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ sc = c.singleton_class
+ def c.m(*args)
+ args
+ end
+ assert_equal([], sc.instance_method(:m).bind_call(c, **{}))
+ assert_equal([], sc.instance_method(:m).bind_call(c, **kw))
+ assert_equal([h], sc.instance_method(:m).bind_call(c, **h))
+ assert_equal([h], sc.instance_method(:m).bind_call(c, a: 1))
+ assert_equal([h2], sc.instance_method(:m).bind_call(c, **h2))
+ assert_equal([h3], sc.instance_method(:m).bind_call(c, **h3))
+ assert_equal([h3], sc.instance_method(:m).bind_call(c, a: 1, **h2))
+
+ sc.remove_method(:m)
+ def c.m; end
+ assert_nil(sc.instance_method(:m).bind_call(c, **{}))
+ assert_nil(sc.instance_method(:m).bind_call(c, **kw))
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, a: 1) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h2) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h3) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, a: 1, **h2) }
+
+ sc.remove_method(:m)
+ def c.m(args)
+ args
+ end
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **{}) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **kw) }
+ assert_equal(h, sc.instance_method(:m).bind_call(c, **h))
+ assert_equal(h, sc.instance_method(:m).bind_call(c, a: 1))
+ assert_equal(h2, sc.instance_method(:m).bind_call(c, **h2))
+ assert_equal(h3, sc.instance_method(:m).bind_call(c, **h3))
+ assert_equal(h3, sc.instance_method(:m).bind_call(c, a: 1, **h2))
+
+ sc.remove_method(:m)
+ def c.m(**args)
+ args
+ end
+ assert_equal(kw, sc.instance_method(:m).bind_call(c, **{}))
+ assert_equal(kw, sc.instance_method(:m).bind_call(c, **kw))
+ assert_equal(h, sc.instance_method(:m).bind_call(c, **h))
+ assert_equal(h, sc.instance_method(:m).bind_call(c, a: 1))
+ assert_equal(h2, sc.instance_method(:m).bind_call(c, **h2))
+ assert_equal(h3, sc.instance_method(:m).bind_call(c, **h3))
+ assert_equal(h3, sc.instance_method(:m).bind_call(c, a: 1, **h2))
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h2) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h3) }
+
+ sc.remove_method(:m)
+ def c.m(arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **{}) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **kw) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, a: 1) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h2) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h3) }
+ assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, a: 1, **h2) }
+
+ sc.remove_method(:m)
+ def c.m(arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], sc.instance_method(:m).bind_call(c, **{}))
+ assert_equal([1, kw], sc.instance_method(:m).bind_call(c, **kw))
+ assert_equal([1, h], sc.instance_method(:m).bind_call(c, **h))
+ assert_equal([1, h], sc.instance_method(:m).bind_call(c, a: 1))
+ assert_equal([1, h2], sc.instance_method(:m).bind_call(c, **h2))
+ assert_equal([1, h3], sc.instance_method(:m).bind_call(c, **h3))
+ assert_equal([1, h3], sc.instance_method(:m).bind_call(c, a: 1, **h2))
+ end
+
+ def test_send_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.m(*args)
+ args
+ end
+ assert_equal([], c.send(:m, **{}))
+ assert_equal([], c.send(:m, **kw))
+ assert_equal([h], c.send(:m, **h))
+ assert_equal([h], c.send(:m, a: 1))
+ assert_equal([h2], c.send(:m, **h2))
+ assert_equal([h3], c.send(:m, **h3))
+ assert_equal([h3], c.send(:m, a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m; end
+ assert_nil(c.send(:m, **{}))
+ assert_nil(c.send(:m, **kw))
+ assert_raise(ArgumentError) { c.send(:m, **h) }
+ assert_raise(ArgumentError) { c.send(:m, a: 1) }
+ assert_raise(ArgumentError) { c.send(:m, **h2) }
+ assert_raise(ArgumentError) { c.send(:m, **h3) }
+ assert_raise(ArgumentError) { c.send(:m, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(args)
+ args
+ end
+ assert_raise(ArgumentError) { c.send(:m, **{}) }
+ assert_raise(ArgumentError) { c.send(:m, **kw) }
+ assert_equal(h, c.send(:m, **h))
+ assert_equal(h, c.send(:m, a: 1))
+ assert_equal(h2, c.send(:m, **h2))
+ assert_equal(h3, c.send(:m, **h3))
+ assert_equal(h3, c.send(:m, a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(**args)
+ args
+ end
+ assert_equal(kw, c.send(:m, **{}))
+ assert_equal(kw, c.send(:m, **kw))
+ assert_equal(h, c.send(:m, **h))
+ assert_equal(h, c.send(:m, a: 1))
+ assert_equal(h2, c.send(:m, **h2))
+ assert_equal(h3, c.send(:m, **h3))
+ assert_equal(h3, c.send(:m, a: 1, **h2))
+ assert_raise(ArgumentError) { c.send(:m, h) }
+ assert_raise(ArgumentError) { c.send(:m, h2) }
+ assert_raise(ArgumentError) { c.send(:m, h3) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { c.send(:m, **{}) }
+ assert_raise(ArgumentError) { c.send(:m, **kw) }
+ assert_raise(ArgumentError) { c.send(:m, **h) }
+ assert_raise(ArgumentError) { c.send(:m, a: 1) }
+ assert_raise(ArgumentError) { c.send(:m, **h2) }
+ assert_raise(ArgumentError) { c.send(:m, **h3) }
+ assert_raise(ArgumentError) { c.send(:m, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], c.send(:m, **{}))
+ assert_equal([1, kw], c.send(:m, **kw))
+ assert_equal([1, h], c.send(:m, **h))
+ assert_equal([1, h], c.send(:m, a: 1))
+ assert_equal([1, h2], c.send(:m, **h2))
+ assert_equal([1, h3], c.send(:m, **h3))
+ assert_equal([1, h3], c.send(:m, a: 1, **h2))
+ end
+
+ def test_public_send_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.m(*args)
+ args
+ end
+ assert_equal([], c.public_send(:m, **{}))
+ assert_equal([], c.public_send(:m, **kw))
+ assert_equal([h], c.public_send(:m, **h))
+ assert_equal([h], c.public_send(:m, a: 1))
+ assert_equal([h2], c.public_send(:m, **h2))
+ assert_equal([h3], c.public_send(:m, **h3))
+ assert_equal([h3], c.public_send(:m, a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m; end
+ assert_nil(c.public_send(:m, **{}))
+ assert_nil(c.public_send(:m, **kw))
+ assert_raise(ArgumentError) { c.public_send(:m, **h) }
+ assert_raise(ArgumentError) { c.public_send(:m, a: 1) }
+ assert_raise(ArgumentError) { c.public_send(:m, **h2) }
+ assert_raise(ArgumentError) { c.public_send(:m, **h3) }
+ assert_raise(ArgumentError) { c.public_send(:m, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(args)
+ args
+ end
+ assert_raise(ArgumentError) { c.public_send(:m, **{}) }
+ assert_raise(ArgumentError) { c.public_send(:m, **kw) }
+ assert_equal(h, c.public_send(:m, **h))
+ assert_equal(h, c.public_send(:m, a: 1))
+ assert_equal(h2, c.public_send(:m, **h2))
+ assert_equal(h3, c.public_send(:m, **h3))
+ assert_equal(h3, c.public_send(:m, a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(**args)
+ args
+ end
+ assert_equal(kw, c.public_send(:m, **{}))
+ assert_equal(kw, c.public_send(:m, **kw))
+ assert_equal(h, c.public_send(:m, **h))
+ assert_equal(h, c.public_send(:m, a: 1))
+ assert_equal(h2, c.public_send(:m, **h2))
+ assert_equal(h3, c.public_send(:m, **h3))
+ assert_equal(h3, c.public_send(:m, a: 1, **h2))
+ assert_raise(ArgumentError) { c.public_send(:m, h) }
+ assert_raise(ArgumentError) { c.public_send(:m, h2) }
+ assert_raise(ArgumentError) { c.public_send(:m, h3) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { c.public_send(:m, **{}) }
+ assert_raise(ArgumentError) { c.public_send(:m, **kw) }
+ assert_raise(ArgumentError) { c.public_send(:m, **h) }
+ assert_raise(ArgumentError) { c.public_send(:m, a: 1) }
+ assert_raise(ArgumentError) { c.public_send(:m, **h2) }
+ assert_raise(ArgumentError) { c.public_send(:m, **h3) }
+ assert_raise(ArgumentError) { c.public_send(:m, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], c.public_send(:m, **{}))
+ assert_equal([1, kw], c.public_send(:m, **kw))
+ assert_equal([1, h], c.public_send(:m, **h))
+ assert_equal([1, h], c.public_send(:m, a: 1))
+ assert_equal([1, h2], c.public_send(:m, **h2))
+ assert_equal([1, h3], c.public_send(:m, **h3))
+ assert_equal([1, h3], c.public_send(:m, a: 1, **h2))
+ end
+
+ def test_send_method_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.m(*args)
+ args
+ end
+ m = c.method(:send)
+ assert_equal([], m.call(:m, **{}))
+ assert_equal([], m.call(:m, **kw))
+ assert_equal([h], m.call(:m, **h))
+ assert_equal([h], m.call(:m, a: 1))
+ assert_equal([h2], m.call(:m, **h2))
+ assert_equal([h3], m.call(:m, **h3))
+ assert_equal([h3], m.call(:m, a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m; end
+ m = c.method(:send)
+ assert_nil(m.call(:m, **{}))
+ assert_nil(m.call(:m, **kw))
+ assert_raise(ArgumentError) { m.call(:m, **h) }
+ assert_raise(ArgumentError) { m.call(:m, a: 1) }
+ assert_raise(ArgumentError) { m.call(:m, **h2) }
+ assert_raise(ArgumentError) { m.call(:m, **h3) }
+ assert_raise(ArgumentError) { m.call(:m, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(args)
+ args
+ end
+ m = c.method(:send)
+ assert_raise(ArgumentError) { m.call(:m, **{}) }
+ assert_raise(ArgumentError) { m.call(:m, **kw) }
+ assert_equal(h, m.call(:m, **h))
+ assert_equal(h, m.call(:m, a: 1))
+ assert_equal(h2, m.call(:m, **h2))
+ assert_equal(h3, m.call(:m, **h3))
+ assert_equal(h3, m.call(:m, a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(**args)
+ args
+ end
+ m = c.method(:send)
+ assert_equal(kw, m.call(:m, **{}))
+ assert_equal(kw, m.call(:m, **kw))
+ assert_equal(h, m.call(:m, **h))
+ assert_equal(h, m.call(:m, a: 1))
+ assert_equal(h2, m.call(:m, **h2))
+ assert_equal(h3, m.call(:m, **h3))
+ assert_equal(h3, m.call(:m, a: 1, **h2))
+ assert_raise(ArgumentError) { m.call(:m, h) }
+ assert_raise(ArgumentError) { m.call(:m, h2) }
+ assert_raise(ArgumentError) { m.call(:m, h3) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg, **args)
+ [arg, args]
+ end
+ m = c.method(:send)
+ assert_raise(ArgumentError) { m.call(:m, **{}) }
+ assert_raise(ArgumentError) { m.call(:m, **kw) }
+ assert_raise(ArgumentError) { m.call(:m, **h) }
+ assert_raise(ArgumentError) { m.call(:m, a: 1) }
+ assert_raise(ArgumentError) { m.call(:m, **h2) }
+ assert_raise(ArgumentError) { m.call(:m, **h3) }
+ assert_raise(ArgumentError) { m.call(:m, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg=1, **args)
+ [arg, args]
+ end
+ m = c.method(:send)
+ assert_equal([1, kw], m.call(:m, **{}))
+ assert_equal([1, kw], m.call(:m, **kw))
+ assert_equal([1, h], m.call(:m, **h))
+ assert_equal([1, h], m.call(:m, a: 1))
+ assert_equal([1, h2], m.call(:m, **h2))
+ assert_equal([1, h3], m.call(:m, **h3))
+ assert_equal([1, h3], m.call(:m, a: 1, **h2))
+ end
+
+ def test_sym_proc_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.m(*args)
+ args
+ end
+ assert_equal([], :m.to_proc.call(c, **{}))
+ assert_equal([], :m.to_proc.call(c, **kw))
+ assert_equal([h], :m.to_proc.call(c, **h))
+ assert_equal([h], :m.to_proc.call(c, a: 1))
+ assert_equal([h2], :m.to_proc.call(c, **h2))
+ assert_equal([h3], :m.to_proc.call(c, **h3))
+ assert_equal([h3], :m.to_proc.call(c, a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m; end
+ assert_nil(:m.to_proc.call(c, **{}))
+ assert_nil(:m.to_proc.call(c, **kw))
+ assert_raise(ArgumentError) { :m.to_proc.call(c, **h) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, a: 1) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, **h2) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, **h3) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(args)
+ args
+ end
+ assert_raise(ArgumentError) { :m.to_proc.call(c, **{}) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, **kw) }
+ assert_equal(h, :m.to_proc.call(c, **h))
+ assert_equal(h, :m.to_proc.call(c, a: 1))
+ assert_equal(h2, :m.to_proc.call(c, **h2))
+ assert_equal(h3, :m.to_proc.call(c, **h3))
+ assert_equal(h3, :m.to_proc.call(c, a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(**args)
+ args
+ end
+ assert_equal(kw, :m.to_proc.call(c, **{}))
+ assert_equal(kw, :m.to_proc.call(c, **kw))
+ assert_equal(h, :m.to_proc.call(c, **h))
+ assert_equal(h, :m.to_proc.call(c, a: 1))
+ assert_equal(h2, :m.to_proc.call(c, **h2))
+ assert_equal(h3, :m.to_proc.call(c, **h3))
+ assert_equal(h3, :m.to_proc.call(c, a: 1, **h2))
+ assert_raise(ArgumentError) { :m.to_proc.call(c, h) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, h2) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, h3) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { :m.to_proc.call(c, **{}) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, **kw) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, **h) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, a: 1) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, **h2) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, **h3) }
+ assert_raise(ArgumentError) { :m.to_proc.call(c, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], :m.to_proc.call(c, **{}))
+ assert_equal([1, kw], :m.to_proc.call(c, **kw))
+ assert_equal([1, h], :m.to_proc.call(c, **h))
+ assert_equal([1, h], :m.to_proc.call(c, a: 1))
+ assert_equal([1, h2], :m.to_proc.call(c, **h2))
+ assert_equal([1, h3], :m.to_proc.call(c, **h3))
+ assert_equal([1, h3], :m.to_proc.call(c, a: 1, **h2))
+ end
+
+ def test_sym_proc_method_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.m(*args)
+ args
+ end
+ m = :m.to_proc.method(:call)
+ assert_equal([], m.call(c, **{}))
+ assert_equal([], m.call(c, **kw))
+ assert_equal([h], m.call(c, **h))
+ assert_equal([h], m.call(c, a: 1))
+ assert_equal([h2], m.call(c, **h2))
+ assert_equal([h3], m.call(c, **h3))
+ assert_equal([h3], m.call(c, a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m; end
+ assert_nil(m.call(c, **{}))
+ assert_nil(m.call(c, **kw))
+ assert_raise(ArgumentError) { m.call(c, **h) }
+ assert_raise(ArgumentError) { m.call(c, a: 1) }
+ assert_raise(ArgumentError) { m.call(c, **h2) }
+ assert_raise(ArgumentError) { m.call(c, **h3) }
+ assert_raise(ArgumentError) { m.call(c, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(args)
+ args
+ end
+ assert_raise(ArgumentError) { m.call(c, **{}) }
+ assert_raise(ArgumentError) { m.call(c, **kw) }
+ assert_equal(h, m.call(c, **h))
+ assert_equal(h, m.call(c, a: 1))
+ assert_equal(h2, m.call(c, **h2))
+ assert_equal(h3, m.call(c, **h3))
+ assert_equal(h3, m.call(c, a: 1, **h2))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(**args)
+ args
+ end
+ assert_equal(kw, m.call(c, **{}))
+ assert_equal(kw, m.call(c, **kw))
+ assert_equal(h, m.call(c, **h))
+ assert_equal(h, m.call(c, a: 1))
+ assert_equal(h2, m.call(c, **h2))
+ assert_equal(h3, m.call(c, **h3))
+ assert_equal(h3, m.call(c, a: 1, **h2))
+ assert_raise(ArgumentError) { m.call(c, h) }
+ assert_raise(ArgumentError) { m.call(c, h2) }
+ assert_raise(ArgumentError) { m.call(c, h3) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { m.call(c, **{}) }
+ assert_raise(ArgumentError) { m.call(c, **kw) }
+ assert_raise(ArgumentError) { m.call(c, **h) }
+ assert_raise(ArgumentError) { m.call(c, a: 1) }
+ assert_raise(ArgumentError) { m.call(c, **h2) }
+ assert_raise(ArgumentError) { m.call(c, **h3) }
+ assert_raise(ArgumentError) { m.call(c, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], m.call(c, **{}))
+ assert_equal([1, kw], m.call(c, **kw))
+ assert_equal([1, h], m.call(c, **h))
+ assert_equal([1, h], m.call(c, a: 1))
+ assert_equal([1, h2], m.call(c, **h2))
+ assert_equal([1, h3], m.call(c, **h3))
+ assert_equal([1, h3], m.call(c, a: 1, **h2))
+ end
+
+ def test_method_missing_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.method_missing(_, *args)
+ args
+ end
+ assert_equal([], c.m(**{}))
+ assert_equal([], c.m(**kw))
+ assert_equal([h], c.m(**h))
+ assert_equal([h], c.m(a: 1))
+ assert_equal([h2], c.m(**h2))
+ assert_equal([h3], c.m(**h3))
+ assert_equal([h3], c.m(a: 1, **h2))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_); end
+ assert_nil(c.m(**{}))
+ assert_nil(c.m(**kw))
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, args)
+ args
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, **args)
+ args
+ end
+ assert_equal(kw, c.m(**{}))
+ assert_equal(kw, c.m(**kw))
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+ assert_raise(ArgumentError) { c.m(h) }
+ assert_raise(ArgumentError) { c.m(h2) }
+ assert_raise(ArgumentError) { c.m(h3) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], c.m(**{}))
+ assert_equal([1, kw], c.m(**kw))
+ assert_equal([1, h], c.m(**h))
+ assert_equal([1, h], c.m(a: 1))
+ assert_equal([1, h2], c.m(**h2))
+ assert_equal([1, h3], c.m(**h3))
+ assert_equal([1, h3], c.m(a: 1, **h2))
+ end
+
+ def test_super_method_missing_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Class.new do
+ def m(*args, **kw)
+ super
+ end
+ end.new
+ def c.method_missing(_, *args)
+ args
+ end
+ assert_equal([], c.m(**{}))
+ assert_equal([], c.m(**kw))
+ assert_equal([h], c.m(**h))
+ assert_equal([h], c.m(a: 1))
+ assert_equal([h2], c.m(**h2))
+ assert_equal([h3], c.m(**h3))
+ assert_equal([h3], c.m(a: 1, **h2))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_); end
+ assert_nil(c.m(**{}))
+ assert_nil(c.m(**kw))
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, args)
+ args
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, **args)
+ args
+ end
+ assert_equal(kw, c.m(**{}))
+ assert_equal(kw, c.m(**kw))
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+ assert_raise(ArgumentError) { c.m(h) }
+ assert_raise(ArgumentError) { c.m(h2) }
+ assert_raise(ArgumentError) { c.m(h3) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], c.m(**{}))
+ assert_equal([1, kw], c.m(**kw))
+ assert_equal([1, h], c.m(**h))
+ assert_equal([1, h], c.m(a: 1))
+ assert_equal([1, h2], c.m(**h2))
+ assert_equal([1, h3], c.m(**h3))
+ assert_equal([1, h3], c.m(a: 1, **h2))
+ end
+
+ def test_rb_call_super_kw_method_missing_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ c.extend Bug::RbCallSuperKw
+ def c.method_missing(_, *args)
+ args
+ end
+ assert_equal([], c.m(**{}))
+ assert_equal([], c.m(**kw))
+ assert_equal([h], c.m(**h))
+ assert_equal([h], c.m(a: 1))
+ assert_equal([h2], c.m(**h2))
+ assert_equal([h3], c.m(**h3))
+ assert_equal([h3], c.m(a: 1, **h2))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_); end
+ assert_nil(c.m(**{}))
+ assert_nil(c.m(**kw))
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, args)
+ args
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, **args)
+ args
+ end
+ assert_equal(kw, c.m(**{}))
+ assert_equal(kw, c.m(**kw))
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+ assert_raise(ArgumentError) { c.m(h) }
+ assert_raise(ArgumentError) { c.m(h2) }
+ assert_raise(ArgumentError) { c.m(h3) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], c.m(**{}))
+ assert_equal([1, kw], c.m(**kw))
+ assert_equal([1, h], c.m(**h))
+ assert_equal([1, h], c.m(a: 1))
+ assert_equal([1, h2], c.m(**h2))
+ assert_equal([1, h3], c.m(**h3))
+ assert_equal([1, h3], c.m(a: 1, **h2))
+ end
+
+ def test_define_method_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ class << c
+ define_method(:m) { }
+ end
+ assert_nil(c.m(**{}))
+ assert_nil(c.m(**kw))
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ c = Object.new
+ class << c
+ define_method(:m) {|arg| arg }
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+
+ c = Object.new
+ class << c
+ define_method(:m) {|*args| args }
+ end
+ assert_equal([], c.m(**{}))
+ assert_equal([], c.m(**kw))
+ assert_equal([h], c.m(**h))
+ assert_equal([h], c.m(a: 1))
+ assert_equal([h2], c.m(**h2))
+ assert_equal([h3], c.m(**h3))
+ assert_equal([h3], c.m(a: 1, **h2))
+
+ c = Object.new
+ class << c
+ define_method(:m) {|**opt| opt}
+ end
+ assert_equal(kw, c.m(**{}))
+ assert_equal(kw, c.m(**kw))
+ assert_equal(h, c.m(**h))
+ assert_equal(h, c.m(a: 1))
+ assert_equal(h2, c.m(**h2))
+ assert_equal(h3, c.m(**h3))
+ assert_equal(h3, c.m(a: 1, **h2))
+ assert_raise(ArgumentError) { c.m(h) }
+ assert_raise(ArgumentError) { c.m(h2) }
+ assert_raise(ArgumentError) { c.m(h3) }
+
+ c = Object.new
+ class << c
+ define_method(:m) {|arg, **opt| [arg, opt] }
+ end
+ assert_raise(ArgumentError) { c.m(**{}) }
+ assert_raise(ArgumentError) { c.m(**kw) }
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+
+ c = Object.new
+ class << c
+ define_method(:m) {|arg=1, **opt| [arg, opt] }
+ end
+ assert_equal([1, kw], c.m(**{}))
+ assert_equal([1, kw], c.m(**kw))
+ assert_equal([1, h], c.m(**h))
+ assert_equal([1, h], c.m(a: 1))
+ assert_equal([1, h2], c.m(**h2))
+ assert_equal([1, h3], c.m(**h3))
+ assert_equal([1, h3], c.m(a: 1, **h2))
+
+ c = Object.new
+ class << c
+ define_method(:m) {|*args, **opt| [args, opt] }
+ end
+ assert_equal([[h], kw], c.m(h))
+ assert_equal([[h, h], kw], c.m(h, h))
+
+ c = Object.new
+ class << c
+ define_method(:m) {|arg=nil, a: nil| [arg, a] }
+ end
+ assert_equal([h3, nil], c.m(h3))
+ assert_raise(ArgumentError) { c.m(**h3) }
+ end
+
+ def test_define_method_method_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ class << c
+ define_method(:m) { }
+ end
+ m = c.method(:m)
+ assert_nil(m.call(**{}))
+ assert_nil(m.call(**kw))
+ assert_raise(ArgumentError) { m.call(**h) }
+ assert_raise(ArgumentError) { m.call(a: 1) }
+ assert_raise(ArgumentError) { m.call(**h2) }
+ assert_raise(ArgumentError) { m.call(**h3) }
+ assert_raise(ArgumentError) { m.call(a: 1, **h2) }
+
+ c = Object.new
+ class << c
+ define_method(:m) {|arg| arg }
+ end
+ m = c.method(:m)
+ assert_raise(ArgumentError) { m.call(**{}) }
+ assert_raise(ArgumentError) { m.call(**kw) }
+ assert_equal(h, m.call(**h))
+ assert_equal(h, m.call(a: 1))
+ assert_equal(h2, m.call(**h2))
+ assert_equal(h3, m.call(**h3))
+ assert_equal(h3, m.call(a: 1, **h2))
+
+ c = Object.new
+ class << c
+ define_method(:m) {|*args| args }
+ end
+ m = c.method(:m)
+ assert_equal([], m.call(**{}))
+ assert_equal([], m.call(**kw))
+ assert_equal([h], m.call(**h))
+ assert_equal([h], m.call(a: 1))
+ assert_equal([h2], m.call(**h2))
+ assert_equal([h3], m.call(**h3))
+ assert_equal([h3], m.call(a: 1, **h2))
+
+ c = Object.new
+ class << c
+ define_method(:m) {|**opt| opt}
+ end
+ m = c.method(:m)
+ assert_equal(kw, m.call(**{}))
+ assert_equal(kw, m.call(**kw))
+ assert_equal(h, m.call(**h))
+ assert_equal(h, m.call(a: 1))
+ assert_equal(h2, m.call(**h2))
+ assert_equal(h3, m.call(**h3))
+ assert_equal(h3, m.call(a: 1, **h2))
+ assert_raise(ArgumentError) { m.call(h) }
+ assert_raise(ArgumentError) { m.call(h2) }
+ assert_raise(ArgumentError) { m.call(h3) }
+
+ c = Object.new
+ class << c
+ define_method(:m) {|arg, **opt| [arg, opt] }
+ end
+ m = c.method(:m)
+ assert_raise(ArgumentError) { m.call(**{}) }
+ assert_raise(ArgumentError) { m.call(**kw) }
+ assert_raise(ArgumentError) { m.call(**h) }
+ assert_raise(ArgumentError) { m.call(a: 1) }
+ assert_raise(ArgumentError) { m.call(**h2) }
+ assert_raise(ArgumentError) { m.call(**h3) }
+ assert_raise(ArgumentError) { m.call(a: 1, **h2) }
+
+ c = Object.new
+ class << c
+ define_method(:m) {|arg=1, **opt| [arg, opt] }
+ end
+ m = c.method(:m)
+ assert_equal([1, kw], m.call(**{}))
+ assert_equal([1, kw], m.call(**kw))
+ assert_equal([1, h], m.call(**h))
+ assert_equal([1, h], m.call(a: 1))
+ assert_equal([1, h2], m.call(**h2))
+ assert_equal([1, h3], m.call(**h3))
+ assert_equal([1, h3], m.call(a: 1, **h2))
+
+ c = Object.new
+ class << c
+ define_method(:m) {|*args, **opt| [args, opt] }
+ end
+ m = c.method(:m)
+ assert_equal([[h], kw], m.call(h))
+ assert_equal([[h, h], kw], m.call(h, h))
+
+ c = Object.new
+ class << c
+ define_method(:m) {|arg=nil, a: nil| [arg, a] }
+ end
+ m = c.method(:m)
+ assert_equal([h3, nil], m.call(h3))
+ assert_raise(ArgumentError) { m.call(**h3) }
+ end
+
+ def test_attr_reader_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ class << c
+ attr_reader :m
+ end
+ assert_nil(c.m(**{}))
+ assert_nil(c.m(**kw))
+ assert_raise(ArgumentError) { c.m(**h) }
+ assert_raise(ArgumentError) { c.m(a: 1) }
+ assert_raise(ArgumentError) { c.m(**h2) }
+ assert_raise(ArgumentError) { c.m(**h3) }
+ assert_raise(ArgumentError) { c.m(a: 1, **h2) }
+ end
+
+ def test_attr_reader_method_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ class << c
+ attr_reader :m
+ end
+ m = c.method(:m)
+ assert_nil(m.call(**{}))
+ assert_nil(m.call(**kw))
+ assert_raise(ArgumentError) { m.call(**h) }
+ assert_raise(ArgumentError) { m.call(a: 1) }
+ assert_raise(ArgumentError) { m.call(**h2) }
+ assert_raise(ArgumentError) { m.call(**h3) }
+ assert_raise(ArgumentError) { m.call(a: 1, **h2) }
+ end
+
+ def test_attr_writer_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ class << c
+ attr_writer :m
+ end
+ assert_raise(ArgumentError) { c.send(:m=, **{}) }
+ assert_raise(ArgumentError) { c.send(:m=, **kw) }
+ assert_equal(h, c.send(:m=, **h))
+ assert_equal(h, c.send(:m=, a: 1))
+ assert_equal(h2, c.send(:m=, **h2))
+ assert_equal(h3, c.send(:m=, **h3))
+ assert_equal(h3, c.send(:m=, a: 1, **h2))
+
+ assert_equal(42, c.send(:m=, 42, **{}))
+ assert_equal(42, c.send(:m=, 42, **kw))
+ assert_raise(ArgumentError) { c.send(:m=, 42, **h) }
+ assert_raise(ArgumentError) { c.send(:m=, 42, a: 1) }
+ assert_raise(ArgumentError) { c.send(:m=, 42, **h2) }
+ assert_raise(ArgumentError) { c.send(:m=, 42, **h3) }
+ assert_raise(ArgumentError) { c.send(:m=, 42, a: 1, **h2) }
+ end
+
+ def test_attr_writer_method_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ class << c
+ attr_writer :m
+ end
+ m = c.method(:m=)
+ assert_raise(ArgumentError) { m.call(**{}) }
+ assert_raise(ArgumentError) { m.call(**kw) }
+ assert_equal(h, m.call(**h))
+ assert_equal(h, m.call(a: 1))
+ assert_equal(h2, m.call(**h2))
+ assert_equal(h3, m.call(**h3))
+ assert_equal(h3, m.call(a: 1, **h2))
+
+ assert_equal(42, m.call(42, **{}))
+ assert_equal(42, m.call(42, **kw))
+ assert_raise(ArgumentError) { m.call(42, **h) }
+ assert_raise(ArgumentError) { m.call(42, a: 1) }
+ assert_raise(ArgumentError) { m.call(42, **h2) }
+ assert_raise(ArgumentError) { m.call(42, **h3) }
+ 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)}
+ assert_same(foo, foo.ruby2_keywords)
+
+ assert_equal([[1], h1], foo.call(1, :a=>1, &->(*args, **kw){[args, kw]}))
+ assert_equal([1, h1], foo.call(1, :a=>1, &->(*args){args}))
+ assert_equal([[1, h1], {}], foo.call(1, {:a=>1}, &->(*args, **kw){[args, kw]}))
+ assert_equal([1, h1], foo.call(1, {:a=>1}, &->(*args){args}))
+ assert_raise(ArgumentError) { foo.call(:a=>1, &->(arg, **kw){[arg, kw]}) }
+ assert_equal(h1, foo.call(:a=>1, &->(arg){arg}))
+
+ [->(){}, ->(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
+
+ o = Object.new
+ def o.foo(*args)
+ yield(*args)
+ end
+ foo = o.method(:foo).to_proc
+ assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc created from method\)/) do
+ foo.ruby2_keywords
+ end
+
+ foo = :foo.to_proc
+ assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc not defined in Ruby\)/) do
+ foo.ruby2_keywords
+ end
+
+ assert_raise(FrozenError) { ->(*args){}.freeze.ruby2_keywords }
+ end
+
+ def test_ruby2_keywords
+ assert_raise(ArgumentError) do
+ Class.new do
+ ruby2_keywords
+ end
+ end
+
+ c = Class.new do
+ ruby2_keywords def foo(meth, *args)
+ send(meth, *args)
+ end
+
+ ruby2_keywords(define_method(:bfoo) do |meth, *args|
+ send(meth, *args)
+ end)
+
+ ruby2_keywords def foo_bar(*args)
+ bar(*args)
+ end
+
+ ruby2_keywords def foo_baz(*args)
+ baz(*args)
+ end
+
+ define_method(:block_splat) {|*args| }
+ ruby2_keywords :block_splat, def foo_bar_after_bmethod(*args)
+ bar(*args)
+ end
+
+ ruby2_keywords def foo_baz2(*args)
+ baz(*args)
+ baz(*args)
+ end
+
+ ruby2_keywords def foo_foo_bar(meth, *args)
+ foo_bar(meth, *args)
+ end
+
+ ruby2_keywords def foo_foo_baz(meth, *args)
+ foo_baz(meth, *args)
+ end
+
+ ruby2_keywords def foo_mod(meth, *args)
+ args << 1
+ send(meth, *args)
+ end
+
+ ruby2_keywords def foo_bar_mod(*args)
+ args << 1
+ bar(*args)
+ end
+
+ ruby2_keywords def foo_baz_mod(*args)
+ args << 1
+ baz(*args)
+ end
+
+ def pass_bar(*args)
+ bar(*args)
+ end
+
+ def bar(*args, **kw)
+ [args, kw]
+ end
+
+ def baz(*args)
+ args
+ end
+
+ def empty_method
+ end
+
+ def opt(arg = :opt)
+ arg
+ end
+
+ ruby2_keywords def foo_dbar(*args)
+ dbar(*args)
+ end
+
+ ruby2_keywords def foo_dbaz(*args)
+ dbaz(*args)
+ end
+
+ ruby2_keywords def clear_last_empty_method(*args)
+ args.last.clear
+ empty_method(*args)
+ end
+
+ ruby2_keywords def clear_last_opt(*args)
+ args.last.clear
+ opt(*args)
+ end
+
+ define_method(:dbar) do |*args, **kw|
+ [args, kw]
+ end
+
+ define_method(:dbaz) do |*args|
+ args
+ end
+
+ def pass_cfunc(*args)
+ self.class.new(*args).init_args
+ end
+
+ ruby2_keywords def block(*args)
+ ->(*args, **kw){[args, kw]}.(*args)
+ end
+
+ ruby2_keywords def cfunc(*args)
+ self.class.new(*args).init_args
+ end
+
+ ruby2_keywords def store_foo(meth, *args)
+ @stored_args = args
+ use(meth)
+ end
+ def use(meth)
+ send(meth, *@stored_args)
+ end
+
+ attr_reader :init_args
+ def initialize(*args, **kw)
+ @init_args = [args, kw]
+ end
+ end
+
+ mmkw = Class.new do
+ def method_missing(*args, **kw)
+ [args, kw]
+ end
+ end
+
+ mmnokw = Class.new do
+ def method_missing(*args)
+ args
+ end
+ end
+
+ implicit_super = Class.new(c) do
+ ruby2_keywords def bar(*args)
+ super
+ end
+
+ ruby2_keywords def baz(*args)
+ super
+ end
+ end
+
+ explicit_super = Class.new(c) do
+ ruby2_keywords def bar(*args)
+ super(*args)
+ end
+
+ ruby2_keywords def baz(*args)
+ super(*args)
+ end
+ end
+
+ h1 = {a: 1}
+ o = c.new
+
+ assert_equal([1, h1], o.foo_baz2(1, :a=>1))
+ assert_equal([1], o.foo_baz2(1, **{}))
+ assert_equal([h1], o.foo_baz2(h1, **{}))
+
+ assert_equal([[1], h1], o.foo(:bar, 1, :a=>1))
+ assert_equal([1, h1], o.foo(:baz, 1, :a=>1))
+ assert_equal([[1], h1], o.bfoo(:bar, 1, :a=>1))
+ assert_equal([1, h1], o.bfoo(:baz, 1, :a=>1))
+ assert_equal([[1], h1], o.store_foo(:bar, 1, :a=>1))
+ assert_equal([1, h1], o.store_foo(:baz, 1, :a=>1))
+ assert_equal([[1], h1], o.foo_bar(1, :a=>1))
+ assert_equal([1, h1], o.foo_baz(1, :a=>1))
+ assert_equal([[1], h1], o.foo(:foo, :bar, 1, :a=>1))
+ assert_equal([1, h1], o.foo(:foo, :baz, 1, :a=>1))
+ assert_equal([[1], h1], o.foo(:foo_bar, 1, :a=>1))
+ assert_equal([1, h1], o.foo(:foo_baz, 1, :a=>1))
+ assert_equal([[1], h1], o.foo_foo_bar(1, :a=>1))
+ assert_equal([1, h1], o.foo_foo_baz(1, :a=>1))
+ assert_equal([[1], h1], o.foo_bar_after_bmethod(1, :a=>1))
+
+ assert_equal([[1], h1], o.foo(:bar, 1, **h1))
+ assert_equal([1, h1], o.foo(:baz, 1, **h1))
+ assert_equal([[1], h1], o.bfoo(:bar, 1, **h1))
+ assert_equal([1, h1], o.bfoo(:baz, 1, **h1))
+ assert_equal([[1], h1], o.store_foo(:bar, 1, **h1))
+ assert_equal([1, h1], o.store_foo(:baz, 1, **h1))
+ assert_equal([[1], h1], o.foo_bar(1, **h1))
+ assert_equal([1, h1], o.foo_baz(1, **h1))
+ assert_equal([[1], h1], o.foo(:foo, :bar, 1, **h1))
+ assert_equal([1, h1], o.foo(:foo, :baz, 1, **h1))
+ assert_equal([[1], h1], o.foo(:foo_bar, 1, **h1))
+ assert_equal([1, h1], o.foo(:foo_baz, 1, **h1))
+ assert_equal([[1], h1], o.foo_foo_bar(1, **h1))
+ assert_equal([1, h1], o.foo_foo_baz(1, **h1))
+ assert_equal([[1], h1], o.foo_bar_after_bmethod(1, **h1))
+
+ assert_equal([[h1], {}], o.foo(:bar, h1, **{}))
+ assert_equal([h1], o.foo(:baz, h1, **{}))
+ assert_equal([[h1], {}], o.bfoo(:bar, h1, **{}))
+ assert_equal([h1], o.bfoo(:baz, h1, **{}))
+ assert_equal([[h1], {}], o.store_foo(:bar, h1, **{}))
+ assert_equal([h1], o.store_foo(:baz, h1, **{}))
+ assert_equal([[h1], {}], o.foo_bar(h1, **{}))
+ assert_equal([h1], o.foo_baz(h1, **{}))
+ assert_equal([[h1], {}], o.foo(:foo, :bar, h1, **{}))
+ assert_equal([h1], o.foo(:foo, :baz, h1, **{}))
+ assert_equal([[h1], {}], o.foo(:foo_bar, h1, **{}))
+ assert_equal([h1], o.foo(:foo_baz, h1, **{}))
+ assert_equal([[h1], {}], o.foo_foo_bar(h1, **{}))
+ assert_equal([h1], o.foo_foo_baz(h1, **{}))
+ assert_equal([[h1], {}], o.foo_bar_after_bmethod(h1, **{}))
+
+ assert_equal([[1, h1], {}], o.foo(:bar, 1, h1))
+ assert_equal([1, h1], o.foo(:baz, 1, h1))
+ assert_equal([[1, h1], {}], o.bfoo(:bar, 1, h1))
+ assert_equal([1, h1], o.bfoo(:baz, 1, h1))
+ assert_equal([[1, h1], {}], o.store_foo(:bar, 1, h1))
+ assert_equal([1, h1], o.store_foo(:baz, 1, h1))
+ assert_equal([[1, h1], {}], o.foo_bar(1, h1))
+ assert_equal([1, h1], o.foo_baz(1, h1))
+ assert_equal([[1, h1], {}], o.foo_bar_after_bmethod(1, h1))
+
+ assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, :a=>1))
+ assert_equal([1, h1, 1], o.foo_mod(:baz, 1, :a=>1))
+ assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, :a=>1))
+ assert_equal([1, h1, 1], o.foo_baz_mod(1, :a=>1))
+
+ assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, **h1))
+ assert_equal([1, h1, 1], o.foo_mod(:baz, 1, **h1))
+ assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, **h1))
+ assert_equal([1, h1, 1], o.foo_baz_mod(1, **h1))
+
+ assert_equal([[h1, 1], {}], o.foo_mod(:bar, h1, **{}))
+ assert_equal([h1, 1], o.foo_mod(:baz, h1, **{}))
+ assert_equal([[h1, 1], {}], o.foo_bar_mod(h1, **{}))
+ assert_equal([h1, 1], o.foo_baz_mod(h1, **{}))
+
+ assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, h1))
+ assert_equal([1, h1, 1], o.foo_mod(:baz, 1, h1))
+ assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, h1))
+ assert_equal([1, h1, 1], o.foo_baz_mod(1, h1))
+
+ assert_equal([[1], h1], o.foo(:dbar, 1, :a=>1))
+ assert_equal([1, h1], o.foo(:dbaz, 1, :a=>1))
+ assert_equal([[1], h1], o.bfoo(:dbar, 1, :a=>1))
+ assert_equal([1, h1], o.bfoo(:dbaz, 1, :a=>1))
+ assert_equal([[1], h1], o.store_foo(:dbar, 1, :a=>1))
+ assert_equal([1, h1], o.store_foo(:dbaz, 1, :a=>1))
+ assert_equal([[1], h1], o.foo_dbar(1, :a=>1))
+ assert_equal([1, h1], o.foo_dbaz(1, :a=>1))
+
+ assert_equal([[1], h1], o.foo(:dbar, 1, **h1))
+ assert_equal([1, h1], o.foo(:dbaz, 1, **h1))
+ assert_equal([[1], h1], o.bfoo(:dbar, 1, **h1))
+ assert_equal([1, h1], o.bfoo(:dbaz, 1, **h1))
+ assert_equal([[1], h1], o.store_foo(:dbar, 1, **h1))
+ assert_equal([1, h1], o.store_foo(:dbaz, 1, **h1))
+ assert_equal([[1], h1], o.foo_dbar(1, **h1))
+ assert_equal([1, h1], o.foo_dbaz(1, **h1))
+
+ assert_equal([[h1], {}], o.foo(:dbar, h1, **{}))
+ assert_equal([h1], o.foo(:dbaz, h1, **{}))
+ assert_equal([[h1], {}], o.bfoo(:dbar, h1, **{}))
+ assert_equal([h1], o.bfoo(:dbaz, h1, **{}))
+ assert_equal([[h1], {}], o.store_foo(:dbar, h1, **{}))
+ assert_equal([h1], o.store_foo(:dbaz, h1, **{}))
+ assert_equal([[h1], {}], o.foo_dbar(h1, **{}))
+ assert_equal([h1], o.foo_dbaz(h1, **{}))
+
+ assert_equal([[1, h1], {}], o.foo(:dbar, 1, h1))
+ assert_equal([1, h1], o.foo(:dbaz, 1, h1))
+ assert_equal([[1, h1], {}], o.bfoo(:dbar, 1, h1))
+ assert_equal([1, h1], o.bfoo(:dbaz, 1, h1))
+ assert_equal([[1, h1], {}], o.store_foo(:dbar, 1, h1))
+ assert_equal([1, h1], o.store_foo(:dbaz, 1, h1))
+ assert_equal([[1, h1], {}], o.foo_dbar(1, h1))
+ assert_equal([1, h1], o.foo_dbaz(1, h1))
+
+ assert_equal([[1], h1], o.block(1, :a=>1))
+ assert_equal([[1], h1], o.block(1, **h1))
+ assert_equal([[1, h1], {}], o.block(1, h1))
+ assert_equal([[h1], {}], o.block(h1, **{}))
+
+ assert_equal([[1], h1], o.cfunc(1, :a=>1))
+ assert_equal([[1], h1], o.cfunc(1, **h1))
+ assert_equal([[1, h1], {}], o.cfunc(1, h1))
+ assert_equal([[h1], {}], o.cfunc(h1, **{}))
+
+ o = mmkw.new
+ assert_equal([[:b, 1], h1], o.b(1, :a=>1))
+ assert_equal([[:b, 1], h1], o.b(1, **h1))
+ assert_equal([[:b, 1, h1], {}], o.b(1, h1))
+ assert_equal([[:b, h1], {}], o.b(h1, **{}))
+
+ o = mmnokw.new
+ assert_equal([:b, 1, h1], o.b(1, :a=>1))
+ assert_equal([:b, 1, h1], o.b(1, **h1))
+ assert_equal([:b, 1, h1], o.b(1, h1))
+ assert_equal([:b, h1], o.b(h1, **{}))
+
+ o = implicit_super.new
+ assert_equal([[1], h1], o.bar(1, :a=>1))
+ assert_equal([[1], h1], o.bar(1, **h1))
+ assert_equal([[1, h1], {}], o.bar(1, h1))
+ assert_equal([[h1], {}], o.bar(h1, **{}))
+
+ assert_equal([1, h1], o.baz(1, :a=>1))
+ assert_equal([1, h1], o.baz(1, **h1))
+ assert_equal([1, h1], o.baz(1, h1))
+ assert_equal([h1], o.baz(h1, **{}))
+
+ o = explicit_super.new
+ assert_equal([[1], h1], o.bar(1, :a=>1))
+ assert_equal([[1], h1], o.bar(1, **h1))
+ assert_equal([[1, h1], {}], o.bar(1, h1))
+ assert_equal([[h1], {}], o.bar(h1, **{}))
+
+ assert_equal([1, h1], o.baz(1, :a=>1))
+ assert_equal([1, h1], o.baz(1, **h1))
+ assert_equal([1, h1], o.baz(1, h1))
+ assert_equal([h1], o.baz(h1, **{}))
+
+ assert_equal([[1, h1], {}], o.foo(:pass_bar, 1, :a=>1))
+ assert_equal([[1, h1], {}], o.foo(:pass_cfunc, 1, :a=>1))
+
+ 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 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
+ assert_nil(c.send(:ruby2_keywords, utf16_sym))
+ end
+
+ o = Object.new
+ class << o
+ alias bar p
+ end
+ assert_warn(/Skipping set of ruby2_keywords flag for bar \(method not defined in Ruby\)/) do
+ assert_nil(o.singleton_class.send(:ruby2_keywords, :bar))
+ end
+ sc = Class.new(c)
+ assert_warn(/Skipping set of ruby2_keywords flag for bar \(can only set in method defining module\)/) do
+ sc.send(:ruby2_keywords, :bar)
+ end
+ m = Module.new
+ assert_warn(/Skipping set of ruby2_keywords flag for system \(can only set in method defining module\)/) do
+ m.send(:ruby2_keywords, :system)
+ end
+
+ assert_raise(NameError) { c.send(:ruby2_keywords, "a5e36ccec4f5080a1d5e63f8") }
+ assert_raise(NameError) { c.send(:ruby2_keywords, :quux) }
+
+ c.freeze
+ assert_raise(FrozenError) { c.send(:ruby2_keywords, :baz) }
+ end
+
+ def test_anon_splat_ruby2_keywords
+ singleton_class.class_exec do
+ def bar(*a, **kw)
+ [a, kw]
+ end
+
+ ruby2_keywords def bar_anon(*)
+ bar(*)
+ end
+ end
+
+ a = [1, 2]
+ kw = {a: 1}
+ assert_equal([[1, 2], {a: 1}], bar_anon(*a, **kw))
+ assert_equal([1, 2], a)
+ assert_equal({a: 1}, kw)
+ end
+
+ def test_anon_splat_ruby2_keywords_bug_20388
+ extend(Module.new{def process(action, ...) 1 end})
+ extend(Module.new do
+ def process(action, *args)
+ args.freeze
+ super
+ end
+ ruby2_keywords :process
+ end)
+
+ assert_equal(1, process(:foo, bar: :baz))
+ end
+
+ def test_ruby2_keywords_bug_20679
+ c = Class.new do
+ def self.get(_, _, h, &block)
+ h[1]
+ end
+
+ ruby2_keywords def get(*args, &block)
+ self.class.get(*args, &block)
+ end
+ end
+
+ assert_equal 2, c.new.get(true, {}, 1 => 2)
+ end
+
+ def test_top_ruby2_keywords
+ assert_in_out_err([], <<-INPUT, ["[1, 2, 3]", "{k: 1}"], [])
+ def bar(*a, **kw)
+ p a, kw
+ end
+ ruby2_keywords def foo(*a)
+ bar(*a)
+ end
+ foo(1, 2, 3, k:1)
+ INPUT
+ end
+
+ def test_dig_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.dig(*args)
+ args
+ end
+ assert_equal(c, [c].dig(0, **{}))
+ assert_equal(c, [c].dig(0, **kw))
+ assert_equal([h], [c].dig(0, **h))
+ assert_equal([h], [c].dig(0, a: 1))
+ assert_equal([h2], [c].dig(0, **h2))
+ assert_equal([h3], [c].dig(0, **h3))
+ assert_equal([h3], [c].dig(0, a: 1, **h2))
+
+ c.singleton_class.remove_method(:dig)
+ def c.dig; end
+ assert_equal(c, [c].dig(0, **{}))
+ assert_equal(c, [c].dig(0, **kw))
+ assert_raise(ArgumentError) { [c].dig(0, **h) }
+ assert_raise(ArgumentError) { [c].dig(0, a: 1) }
+ assert_raise(ArgumentError) { [c].dig(0, **h2) }
+ assert_raise(ArgumentError) { [c].dig(0, **h3) }
+ assert_raise(ArgumentError) { [c].dig(0, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:dig)
+ def c.dig(args)
+ args
+ end
+ assert_equal(c, [c].dig(0, **{}))
+ assert_equal(c, [c].dig(0, **kw))
+ assert_equal(kw, [c].dig(0, kw, **kw))
+ assert_equal(h, [c].dig(0, **h))
+ assert_equal(h, [c].dig(0, a: 1))
+ assert_equal(h2, [c].dig(0, **h2))
+ assert_equal(h3, [c].dig(0, **h3))
+ assert_equal(h3, [c].dig(0, a: 1, **h2))
+
+ c.singleton_class.remove_method(:dig)
+ def c.dig(**args)
+ args
+ end
+ assert_equal(c, [c].dig(0, **{}))
+ assert_equal(c, [c].dig(0, **kw))
+ assert_raise(ArgumentError) { [c].dig(0, **h) }
+ assert_raise(ArgumentError) { [c].dig(0, a: 1) }
+ assert_raise(ArgumentError) { [c].dig(0, **h2) }
+ assert_raise(ArgumentError) { [c].dig(0, **h3) }
+ assert_raise(ArgumentError) { [c].dig(0, a: 1, **h2) }
+ assert_raise(ArgumentError) { [c].dig(0, h) }
+ assert_raise(ArgumentError) { [c].dig(0, h2) }
+ assert_raise(ArgumentError) { [c].dig(0, h3) }
+
+ c.singleton_class.remove_method(:dig)
+ def c.dig(arg, **args)
+ [arg, args]
+ end
+ assert_equal(c, [c].dig(0, **{}))
+ assert_equal(c, [c].dig(0, **kw))
+ assert_equal([h, kw], [c].dig(0, **h))
+ assert_equal([h, kw], [c].dig(0, a: 1))
+ assert_equal([h2, kw], [c].dig(0, **h2))
+ assert_equal([h3, kw], [c].dig(0, **h3))
+ assert_equal([h3, kw], [c].dig(0, a: 1, **h2))
+
+ c.singleton_class.remove_method(:dig)
+ def c.dig(arg=1, **args)
+ [arg, args]
+ end
+ assert_equal(c, [c].dig(0, **{}))
+ assert_equal(c, [c].dig(0, **kw))
+ assert_equal([h, kw], [c].dig(0, **h))
+ assert_equal([h, kw], [c].dig(0, a: 1))
+ assert_equal([h2, kw], [c].dig(0, **h2))
+ assert_equal([h3, kw], [c].dig(0, **h3))
+ assert_equal([h3, kw], [c].dig(0, a: 1, **h2))
+ assert_equal([h, {}], [c].dig(0, h))
+ assert_equal([h2, kw], [c].dig(0, h2))
+ assert_equal([h3, kw], [c].dig(0, h3))
+ assert_equal([h, kw], [c].dig(0, h, **{}))
+ assert_equal([h2, kw], [c].dig(0, h2, **{}))
+ assert_equal([h3, kw], [c].dig(0, h3, **{}))
+ end
+
+ def test_dig_method_missing_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.method_missing(_, *args)
+ args
+ end
+ assert_equal(c, [c].dig(0, **{}))
+ assert_equal(c, [c].dig(0, **kw))
+ assert_equal([h], [c].dig(0, **h))
+ assert_equal([h], [c].dig(0, a: 1))
+ assert_equal([h2], [c].dig(0, **h2))
+ assert_equal([h3], [c].dig(0, **h3))
+ assert_equal([h3], [c].dig(0, a: 1, **h2))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing; end
+ assert_equal(c, [c].dig(0, **{}))
+ assert_equal(c, [c].dig(0, **kw))
+ assert_raise(ArgumentError) { [c].dig(0, **h) }
+ assert_raise(ArgumentError) { [c].dig(0, a: 1) }
+ assert_raise(ArgumentError) { [c].dig(0, **h2) }
+ assert_raise(ArgumentError) { [c].dig(0, **h3) }
+ assert_raise(ArgumentError) { [c].dig(0, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, args)
+ args
+ end
+ assert_equal(c, [c].dig(0, **{}))
+ assert_equal(c, [c].dig(0, **kw))
+ assert_equal(kw, [c].dig(0, kw, **kw))
+ assert_equal(h, [c].dig(0, **h))
+ assert_equal(h, [c].dig(0, a: 1))
+ assert_equal(h2, [c].dig(0, **h2))
+ assert_equal(h3, [c].dig(0, **h3))
+ assert_equal(h3, [c].dig(0, a: 1, **h2))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, **args)
+ args
+ end
+ assert_equal(c, [c].dig(0, **{}))
+ assert_equal(c, [c].dig(0, **kw))
+ assert_raise(ArgumentError) { [c].dig(0, **h) }
+ assert_raise(ArgumentError) { [c].dig(0, a: 1) }
+ assert_raise(ArgumentError) { [c].dig(0, **h2) }
+ assert_raise(ArgumentError) { [c].dig(0, **h3) }
+ assert_raise(ArgumentError) { [c].dig(0, a: 1, **h2) }
+ assert_raise(ArgumentError) { [c].dig(0, h) }
+ assert_raise(ArgumentError) { [c].dig(0, h2) }
+ assert_raise(ArgumentError) { [c].dig(0, h3) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, arg, **args)
+ [arg, args]
+ end
+ assert_equal(c, [c].dig(0, **{}))
+ assert_equal(c, [c].dig(0, **kw))
+ assert_equal([h, kw], [c].dig(0, **h))
+ assert_equal([h, kw], [c].dig(0, a: 1))
+ assert_equal([h2, kw], [c].dig(0, **h2))
+ assert_equal([h3, kw], [c].dig(0, **h3))
+ assert_equal([h3, kw], [c].dig(0, a: 1, **h2))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, arg=1, **args)
+ [arg, args]
+ end
+ assert_equal(c, [c].dig(0, **{}))
+ assert_equal(c, [c].dig(0, **kw))
+ assert_equal([h, kw], [c].dig(0, **h))
+ assert_equal([h, kw], [c].dig(0, a: 1))
+ assert_equal([h2, kw], [c].dig(0, **h2))
+ assert_equal([h3, kw], [c].dig(0, **h3))
+ assert_equal([h3, kw], [c].dig(0, a: 1, **h2))
+ assert_equal([h, kw], [c].dig(0, h))
+ assert_equal([h2, kw], [c].dig(0, h2))
+ assert_equal([h3, kw], [c].dig(0, h3))
+ assert_equal([h, kw], [c].dig(0, h, **{}))
+ assert_equal([h2, kw], [c].dig(0, h2, **{}))
+ assert_equal([h3, kw], [c].dig(0, h3, **{}))
+ end
+
+ def test_enumerator_size_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ c.to_enum(:each){|*args| args}.size
+ m = ->(*args){ args }
+ assert_equal([], c.to_enum(:each, **{}, &m).size)
+ assert_equal([], c.to_enum(:each, **kw, &m).size)
+ assert_equal([h], c.to_enum(:each, **h, &m).size)
+ assert_equal([h], c.to_enum(:each, a: 1, &m).size)
+ assert_equal([h2], c.to_enum(:each, **h2, &m).size)
+ assert_equal([h3], c.to_enum(:each, **h3, &m).size)
+ assert_equal([h3], c.to_enum(:each, a: 1, **h2, &m).size)
+
+ m = ->(){ }
+ assert_nil(c.to_enum(:each, **{}, &m).size)
+ assert_nil(c.to_enum(:each, **kw, &m).size)
+ assert_raise(ArgumentError) { c.to_enum(:each, **h, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, a: 1, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, **h2, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, **h3, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, a: 1, **h2, &m).size }
+
+ m = ->(args){ args }
+ assert_raise(ArgumentError) { c.to_enum(:each, **{}, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, **kw, &m).size }
+ assert_equal(kw, c.to_enum(:each, kw, **kw, &m).size)
+ assert_equal(h, c.to_enum(:each, **h, &m).size)
+ assert_equal(h, c.to_enum(:each, a: 1, &m).size)
+ assert_equal(h2, c.to_enum(:each, **h2, &m).size)
+ assert_equal(h3, c.to_enum(:each, **h3, &m).size)
+ assert_equal(h3, c.to_enum(:each, a: 1, **h2, &m).size)
+
+ m = ->(**args){ args }
+ assert_equal(kw, c.to_enum(:each, **{}, &m).size)
+ assert_equal(kw, c.to_enum(:each, **kw, &m).size)
+ assert_equal(h, c.to_enum(:each, **h, &m).size)
+ assert_equal(h, c.to_enum(:each, a: 1, &m).size)
+ assert_equal(h2, c.to_enum(:each, **h2, &m).size)
+ assert_equal(h3, c.to_enum(:each, **h3, &m).size)
+ assert_equal(h3, c.to_enum(:each, a: 1, **h2, &m).size)
+ assert_raise(ArgumentError) { c.to_enum(:each, h, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, h2, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, h3, &m).size }
+
+ m = ->(arg, **args){ [arg, args] }
+ assert_raise(ArgumentError) { c.to_enum(:each, **{}, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, **kw, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, **h, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, a: 1, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, **h2, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, **h3, &m).size }
+ assert_raise(ArgumentError) { c.to_enum(:each, a: 1, **h2, &m).size }
+ assert_equal([h, kw], c.to_enum(:each, h, &m).size)
+ assert_equal([h2, kw], c.to_enum(:each, h2, &m).size)
+ assert_equal([h3, kw], c.to_enum(:each, h3, &m).size)
+
+ m = ->(arg=1, **args){ [arg, args] }
+ assert_equal([1, kw], c.to_enum(:each, **{}, &m).size)
+ assert_equal([1, kw], c.to_enum(:each, **kw, &m).size)
+ assert_equal([1, h], c.to_enum(:each, **h, &m).size)
+ assert_equal([1, h], c.to_enum(:each, a: 1, &m).size)
+ assert_equal([1, h2], c.to_enum(:each, **h2, &m).size)
+ assert_equal([1, h3], c.to_enum(:each, **h3, &m).size)
+ assert_equal([1, h3], c.to_enum(:each, a: 1, **h2, &m).size)
+ assert_equal([h, kw], c.to_enum(:each, h, &m).size)
+ assert_equal([h2, kw], c.to_enum(:each, h2, &m).size)
+ assert_equal([h3, kw], c.to_enum(:each, h3, &m).size)
+ end
+
+ def test_instance_exec_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ m = ->(*args) { args }
+ assert_equal([], c.instance_exec(**{}, &m))
+ assert_equal([], c.instance_exec(**kw, &m))
+ assert_equal([h], c.instance_exec(**h, &m))
+ assert_equal([h], c.instance_exec(a: 1, &m))
+ assert_equal([h2], c.instance_exec(**h2, &m))
+ assert_equal([h3], c.instance_exec(**h3, &m))
+ assert_equal([h3], c.instance_exec(a: 1, **h2, &m))
+
+ m = ->() { nil }
+ assert_nil(c.instance_exec(**{}, &m))
+ assert_nil(c.instance_exec(**kw, &m))
+ assert_raise(ArgumentError) { c.instance_exec(**h, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h2, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h3, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) }
+
+ m = ->(args) { args }
+ assert_raise(ArgumentError) { c.instance_exec(**{}, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**kw, &m) }
+ assert_equal(kw, c.instance_exec(kw, **kw, &m))
+ assert_equal(h, c.instance_exec(**h, &m))
+ assert_equal(h, c.instance_exec(a: 1, &m))
+ assert_equal(h2, c.instance_exec(**h2, &m))
+ assert_equal(h3, c.instance_exec(**h3, &m))
+ assert_equal(h3, c.instance_exec(a: 1, **h2, &m))
+
+ m = ->(**args) { args }
+ assert_equal(kw, c.instance_exec(**{}, &m))
+ assert_equal(kw, c.instance_exec(**kw, &m))
+ assert_equal(h, c.instance_exec(**h, &m))
+ assert_equal(h, c.instance_exec(a: 1, &m))
+ assert_equal(h2, c.instance_exec(**h2, &m))
+ assert_equal(h3, c.instance_exec(**h3, &m))
+ assert_equal(h3, c.instance_exec(a: 1, **h2, &m))
+ assert_raise(ArgumentError) { c.instance_exec(h, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(h2, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(h3, &m) }
+
+ m = ->(arg, **args) { [arg, args] }
+ assert_raise(ArgumentError) { c.instance_exec(**{}, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**kw, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h2, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h3, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) }
+ assert_equal([h, kw], c.instance_exec(h, &m))
+ assert_equal([h2, kw], c.instance_exec(h2, &m))
+ assert_equal([h3, kw], c.instance_exec(h3, &m))
+
+ m = ->(arg=1, **args) { [arg, args] }
+ assert_equal([1, kw], c.instance_exec(**{}, &m))
+ assert_equal([1, kw], c.instance_exec(**kw, &m))
+ assert_equal([1, h], c.instance_exec(**h, &m))
+ assert_equal([1, h], c.instance_exec(a: 1, &m))
+ assert_equal([1, h2], c.instance_exec(**h2, &m))
+ assert_equal([1, h3], c.instance_exec(**h3, &m))
+ assert_equal([1, h3], c.instance_exec(a: 1, **h2, &m))
+ assert_equal([h, kw], c.instance_exec(h, &m))
+ assert_equal([h2, kw], c.instance_exec(h2, &m))
+ assert_equal([h3, kw], c.instance_exec(h3, &m))
+ end
+
+ def test_instance_exec_method_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.m(*args)
+ args
+ end
+ m = c.method(:m)
+ assert_equal([], c.instance_exec(**{}, &m))
+ assert_equal([], c.instance_exec(**kw, &m))
+ assert_equal([h], c.instance_exec(**h, &m))
+ assert_equal([h], c.instance_exec(a: 1, &m))
+ assert_equal([h2], c.instance_exec(**h2, &m))
+ assert_equal([h3], c.instance_exec(**h3, &m))
+ assert_equal([h3], c.instance_exec(a: 1, **h2, &m))
+
+ c.singleton_class.remove_method(:m)
+ def c.m
+ end
+ m = c.method(:m)
+ assert_nil(c.instance_exec(**{}, &m))
+ assert_nil(c.instance_exec(**kw, &m))
+ assert_raise(ArgumentError) { c.instance_exec(**h, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h2, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h3, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(args)
+ args
+ end
+ m = c.method(:m)
+ assert_raise(ArgumentError) { c.instance_exec(**{}, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**kw, &m) }
+ assert_equal(kw, c.instance_exec(kw, **kw, &m))
+ assert_equal(h, c.instance_exec(**h, &m))
+ assert_equal(h, c.instance_exec(a: 1, &m))
+ assert_equal(h2, c.instance_exec(**h2, &m))
+ assert_equal(h3, c.instance_exec(**h3, &m))
+ assert_equal(h3, c.instance_exec(a: 1, **h2, &m))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(**args)
+ args
+ end
+ m = c.method(:m)
+ assert_equal(kw, c.instance_exec(**{}, &m))
+ assert_equal(kw, c.instance_exec(**kw, &m))
+ assert_equal(h, c.instance_exec(**h, &m))
+ assert_equal(h, c.instance_exec(a: 1, &m))
+ assert_equal(h2, c.instance_exec(**h2, &m))
+ assert_equal(h3, c.instance_exec(**h3, &m))
+ assert_equal(h3, c.instance_exec(a: 1, **h2, &m))
+ assert_raise(ArgumentError) { c.instance_exec(h, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(h2, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(h3, &m) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg, **args)
+ [arg, args]
+ end
+ m = c.method(:m)
+ assert_raise(ArgumentError) { c.instance_exec(**{}, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**kw, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h2, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h3, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) }
+ assert_equal([h, kw], c.instance_exec(h, &m))
+ assert_equal([h2, kw], c.instance_exec(h2, &m))
+ assert_equal([h3, kw], c.instance_exec(h3, &m))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg=1, **args)
+ [arg, args]
+ end
+ m = c.method(:m)
+ assert_equal([1, kw], c.instance_exec(**{}, &m))
+ assert_equal([1, kw], c.instance_exec(**kw, &m))
+ assert_equal([1, h], c.instance_exec(**h, &m))
+ assert_equal([1, h], c.instance_exec(a: 1, &m))
+ assert_equal([1, h2], c.instance_exec(**h2, &m))
+ assert_equal([1, h3], c.instance_exec(**h3, &m))
+ assert_equal([1, h3], c.instance_exec(a: 1, **h2, &m))
+ assert_equal([h, kw], c.instance_exec(h, &m))
+ assert_equal([h2, kw], c.instance_exec(h2, &m))
+ assert_equal([h3, kw], c.instance_exec(h3, &m))
+ end
+
+ def test_instance_exec_define_method_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ c.define_singleton_method(:m) do |*args|
+ args
+ end
+ m = c.method(:m)
+ assert_equal([], c.instance_exec(**{}, &m))
+ assert_equal([], c.instance_exec(**kw, &m))
+ assert_equal([h], c.instance_exec(**h, &m))
+ assert_equal([h], c.instance_exec(a: 1, &m))
+ assert_equal([h2], c.instance_exec(**h2, &m))
+ assert_equal([h3], c.instance_exec(**h3, &m))
+ assert_equal([h3], c.instance_exec(a: 1, **h2, &m))
+
+ c.singleton_class.remove_method(:m)
+ c.define_singleton_method(:m) do
+ end
+ m = c.method(:m)
+ assert_nil(c.instance_exec(**{}, &m))
+ assert_nil(c.instance_exec(**kw, &m))
+ assert_raise(ArgumentError) { c.instance_exec(**h, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h2, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h3, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) }
+
+ c.singleton_class.remove_method(:m)
+ c.define_singleton_method(:m) do |args|
+ args
+ end
+ m = c.method(:m)
+ assert_raise(ArgumentError) { c.instance_exec(**{}, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**kw, &m) }
+ assert_equal(kw, c.instance_exec(kw, **kw, &m))
+ assert_equal(h, c.instance_exec(**h, &m))
+ assert_equal(h, c.instance_exec(a: 1, &m))
+ assert_equal(h2, c.instance_exec(**h2, &m))
+ assert_equal(h3, c.instance_exec(**h3, &m))
+ assert_equal(h3, c.instance_exec(a: 1, **h2, &m))
+
+ c.singleton_class.remove_method(:m)
+ c.define_singleton_method(:m) do |**args|
+ args
+ end
+ m = c.method(:m)
+ assert_equal(kw, c.instance_exec(**{}, &m))
+ assert_equal(kw, c.instance_exec(**kw, &m))
+ assert_equal(h, c.instance_exec(**h, &m))
+ assert_equal(h, c.instance_exec(a: 1, &m))
+ assert_equal(h2, c.instance_exec(**h2, &m))
+ assert_equal(h3, c.instance_exec(**h3, &m))
+ assert_equal(h3, c.instance_exec(a: 1, **h2, &m))
+ assert_raise(ArgumentError) { c.instance_exec(h, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(h2, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(h3, &m) }
+
+ c.singleton_class.remove_method(:m)
+ c.define_singleton_method(:m) do |arg, **args|
+ [arg, args]
+ end
+ m = c.method(:m)
+ assert_raise(ArgumentError) { c.instance_exec(**{}, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**kw, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h2, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(**h3, &m) }
+ assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) }
+ assert_equal([h, kw], c.instance_exec(h, &m))
+ assert_equal([h2, kw], c.instance_exec(h2, &m))
+ assert_equal([h3, kw], c.instance_exec(h3, &m))
+
+ c.singleton_class.remove_method(:m)
+ c.define_singleton_method(:m) do |arg=1, **args|
+ [arg, args]
+ end
+ m = c.method(:m)
+ assert_equal([1, kw], c.instance_exec(**{}, &m))
+ assert_equal([1, kw], c.instance_exec(**kw, &m))
+ assert_equal([1, h], c.instance_exec(**h, &m))
+ assert_equal([1, h], c.instance_exec(a: 1, &m))
+ assert_equal([1, h2], c.instance_exec(**h2, &m))
+ assert_equal([1, h3], c.instance_exec(**h3, &m))
+ assert_equal([1, h3], c.instance_exec(a: 1, **h2, &m))
+ assert_equal([h, kw], c.instance_exec(h, &m))
+ assert_equal([h2, kw], c.instance_exec(h2, &m))
+ assert_equal([h3, kw], c.instance_exec(h3, &m))
+ end
+
+ def test_instance_exec_sym_proc_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ def c.m(*args)
+ args
+ end
+ assert_equal([], c.instance_exec(c, **{}, &:m))
+ assert_equal([], c.instance_exec(c, **kw, &:m))
+ assert_equal([h], c.instance_exec(c, **h, &:m))
+ assert_equal([h], c.instance_exec(c, a: 1, &:m))
+ assert_equal([h2], c.instance_exec(c, **h2, &:m))
+ assert_equal([h3], c.instance_exec(c, **h3, &:m))
+ assert_equal([h3], c.instance_exec(c, a: 1, **h2, &:m))
+
+ c.singleton_class.remove_method(:m)
+ def c.m
+ end
+ assert_nil(c.instance_exec(c, **{}, &:m))
+ assert_nil(c.instance_exec(c, **kw, &:m))
+ assert_raise(ArgumentError) { c.instance_exec(c, **h, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, a: 1, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, **h2, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, **h3, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, a: 1, **h2, &:m) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(args)
+ args
+ end
+ assert_raise(ArgumentError) { c.instance_exec(c, **{}, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, **kw, &:m) }
+ assert_equal(kw, c.instance_exec(c, kw, **kw, &:m))
+ assert_equal(h, c.instance_exec(c, **h, &:m))
+ assert_equal(h, c.instance_exec(c, a: 1, &:m))
+ assert_equal(h2, c.instance_exec(c, **h2, &:m))
+ assert_equal(h3, c.instance_exec(c, **h3, &:m))
+ assert_equal(h3, c.instance_exec(c, a: 1, **h2, &:m))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(**args)
+ args
+ end
+ assert_equal(kw, c.instance_exec(c, **{}, &:m))
+ assert_equal(kw, c.instance_exec(c, **kw, &:m))
+ assert_equal(h, c.instance_exec(c, **h, &:m))
+ assert_equal(h, c.instance_exec(c, a: 1, &:m))
+ assert_equal(h2, c.instance_exec(c, **h2, &:m))
+ assert_equal(h3, c.instance_exec(c, **h3, &:m))
+ assert_equal(h3, c.instance_exec(c, a: 1, **h2, &:m))
+ assert_raise(ArgumentError) { c.instance_exec(c, h, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, h2, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, h3, &:m) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { c.instance_exec(c, **{}, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, **kw, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, **h, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, a: 1, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, **h2, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, **h3, &:m) }
+ assert_raise(ArgumentError) { c.instance_exec(c, a: 1, **h2, &:m) }
+ assert_equal([h, kw], c.instance_exec(c, h, &:m))
+ assert_equal([h2, kw], c.instance_exec(c, h2, &:m))
+ assert_equal([h3, kw], c.instance_exec(c, h3, &:m))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], c.instance_exec(c, **{}, &:m))
+ assert_equal([1, kw], c.instance_exec(c, **kw, &:m))
+ assert_equal([1, h], c.instance_exec(c, **h, &:m))
+ assert_equal([1, h], c.instance_exec(c, a: 1, &:m))
+ assert_equal([1, h2], c.instance_exec(c, **h2, &:m))
+ assert_equal([1, h3], c.instance_exec(c, **h3, &:m))
+ assert_equal([1, h3], c.instance_exec(c, a: 1, **h2, &:m))
+ assert_equal([h, kw], c.instance_exec(c, h, &:m))
+ assert_equal([h2, kw], c.instance_exec(c, h2, &:m))
+ assert_equal([h3, kw], c.instance_exec(c, h3, &:m))
+ end
+
+ def test_rb_yield_block_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = Object.new
+ c.extend Bug::Iter::Yield
+ class << c
+ alias m yield_block
+ end
+ def c.c(*args)
+ args
+ end
+ assert_equal([], c.m(:c, **{}))
+ assert_equal([], c.m(:c, **kw))
+ assert_equal([h], c.m(:c, **h))
+ assert_equal([h], c.m(:c, a: 1))
+ assert_equal([h2], c.m(:c, **h2))
+ assert_equal([h3], c.m(:c, **h3))
+ assert_equal([h3], c.m(:c, a: 1, **h2))
+
+ c.singleton_class.remove_method(:c)
+ def c.c; end
+ assert_nil(c.m(:c, **{}))
+ assert_nil(c.m(:c, **kw))
+ assert_raise(ArgumentError) { c.m(:c, **h) }
+ assert_raise(ArgumentError) { c.m(:c, a: 1) }
+ assert_raise(ArgumentError) { c.m(:c, **h2) }
+ assert_raise(ArgumentError) { c.m(:c, **h3) }
+ assert_raise(ArgumentError) { c.m(:c, a: 1, **h2) }
+
+ c.singleton_class.remove_method(:c)
+ def c.c(args)
+ args
+ end
+ assert_raise(ArgumentError) { c.m(:c, **{}) }
+ assert_raise(ArgumentError) { c.m(:c, **kw) }
+ assert_equal(kw, c.m(:c, kw, **kw))
+ assert_equal(h, c.m(:c, **h))
+ assert_equal(h, c.m(:c, a: 1))
+ assert_equal(h2, c.m(:c, **h2))
+ assert_equal(h3, c.m(:c, **h3))
+ assert_equal(h3, c.m(:c, a: 1, **h2))
+
+ c.singleton_class.remove_method(:c)
+ def c.c(**args)
+ [args, yield(**args)]
+ end
+ m = ->(**args){ args }
+ assert_equal([kw, kw], c.m(:c, **{}, &m))
+ assert_equal([kw, kw], c.m(:c, **kw, &m))
+ assert_equal([h, h], c.m(:c, **h, &m))
+ assert_equal([h, h], c.m(:c, a: 1, &m))
+ assert_equal([h2, h2], c.m(:c, **h2, &m))
+ assert_equal([h3, h3], c.m(:c, **h3, &m))
+ assert_equal([h3, h3], c.m(:c, a: 1, **h2, &m))
+ assert_raise(ArgumentError) { c.m(:c, h, &m) }
+ assert_raise(ArgumentError) { c.m(:c, h2, &m) }
+ assert_raise(ArgumentError) { c.m(:c, h3, &m) }
+
+ c.singleton_class.remove_method(:c)
+ def c.c(arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { c.m(:c, **{}, &m) }
+ assert_raise(ArgumentError) { c.m(:c, **kw, &m) }
+ assert_raise(ArgumentError) { c.m(:c, **h, &m) }
+ assert_raise(ArgumentError) { c.m(:c, a: 1, &m) }
+ assert_raise(ArgumentError) { c.m(:c, **h2, &m) }
+ assert_raise(ArgumentError) { c.m(:c, **h3, &m) }
+ assert_raise(ArgumentError) { c.m(:c, a: 1, **h2, &m) }
+ assert_equal([h, kw], c.m(:c, h))
+ assert_equal([h2, kw], c.m(:c, h2))
+ assert_equal([h3, kw], c.m(:c, h3))
+
+ c.singleton_class.remove_method(:c)
+ def c.c(arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], c.m(:c, **{}))
+ assert_equal([1, kw], c.m(:c, **kw))
+ assert_equal([1, h], c.m(:c, **h))
+ assert_equal([1, h], c.m(:c, a: 1))
+ assert_equal([1, h2], c.m(:c, **h2))
+ assert_equal([1, h3], c.m(:c, **h3))
+ assert_equal([1, h3], c.m(:c, a: 1, **h2))
+ assert_equal([h, kw], c.m(:c, h))
+ assert_equal([h2, kw], c.m(:c, h2))
+ assert_equal([h3, kw], c.m(:c, h3))
+ end
def p1
Proc.new do |str: "foo", num: 424242|
@@ -254,8 +3699,8 @@ class TestKeywordArguments < Test::Unit::TestCase
[:keyrest, :kw], [:block, :b]], p6.parameters)
end
- def m1(*args)
- yield(*args)
+ def m1(*args, **options)
+ yield(*args, **options)
end
def test_block
@@ -273,15 +3718,56 @@ class TestKeywordArguments < Test::Unit::TestCase
def test_rest_keyrest
bug7665 = '[ruby-core:51278]'
bug8463 = '[ruby-core:55203] [Bug #8463]'
- expect = [*%w[foo bar], {zzz: 42}]
- assert_equal(expect, rest_keyrest(*expect), bug7665)
+ a = [*%w[foo bar], {zzz: 42}]
+ splat_expect = a + [{}]
+ nonsplat_expect = [a, {}]
+ assert_equal(splat_expect, rest_keyrest(*a), bug7665)
+ assert_equal(nonsplat_expect, rest_keyrest(a), bug7665)
+
pr = proc {|*args, **opt| next *args, opt}
- assert_equal(expect, pr.call(*expect), bug7665)
- assert_equal(expect, pr.call(expect), bug8463)
+ assert_equal(splat_expect, pr.call(*a), bug7665)
+ assert_equal(nonsplat_expect, pr.call(a), bug8463)
+
pr = proc {|a, *b, **opt| next a, *b, opt}
- assert_equal(expect, pr.call(expect), bug8463)
+ assert_equal(splat_expect, pr.call(a), bug8463)
+
pr = proc {|a, **opt| next a, opt}
- assert_equal(expect.values_at(0, -1), pr.call(expect), bug8463)
+ assert_equal([splat_expect, {}], pr.call(splat_expect), bug8463)
+ end
+
+ def req_plus_keyword(x, **h)
+ [x, h]
+ end
+
+ def opt_plus_keyword(x=1, **h)
+ [x, h]
+ end
+
+ def splat_plus_keyword(*a, **h)
+ [a, h]
+ end
+
+ def test_keyword_no_split
+ assert_raise(ArgumentError) { req_plus_keyword(:a=>1) }
+ assert_raise(ArgumentError) { req_plus_keyword("a"=>1) }
+ assert_raise(ArgumentError) { req_plus_keyword("a"=>1, :a=>1) }
+ assert_equal([{:a=>1}, {}], req_plus_keyword({:a=>1}))
+ assert_equal([{"a"=>1}, {}], req_plus_keyword({"a"=>1}))
+ assert_equal([{"a"=>1, :a=>1}, {}], req_plus_keyword({"a"=>1, :a=>1}))
+
+ assert_equal([1, {:a=>1}], opt_plus_keyword(:a=>1))
+ assert_equal([1, {"a"=>1}], opt_plus_keyword("a"=>1))
+ assert_equal([1, {"a"=>1, :a=>1}], opt_plus_keyword("a"=>1, :a=>1))
+ assert_equal([{:a=>1}, {}], opt_plus_keyword({:a=>1}))
+ assert_equal([{"a"=>1}, {}], opt_plus_keyword({"a"=>1}))
+ assert_equal([{"a"=>1, :a=>1}, {}], opt_plus_keyword({"a"=>1, :a=>1}))
+
+ assert_equal([[], {:a=>1}], splat_plus_keyword(:a=>1))
+ assert_equal([[], {"a"=>1}], splat_plus_keyword("a"=>1))
+ assert_equal([[], {"a"=>1, :a=>1}], splat_plus_keyword("a"=>1, :a=>1))
+ assert_equal([[{:a=>1}], {}], splat_plus_keyword({:a=>1}))
+ assert_equal([[{"a"=>1}], {}], splat_plus_keyword({"a"=>1}))
+ assert_equal([[{"a"=>1, :a=>1}], {}], splat_plus_keyword({"a"=>1, :a=>1}))
end
def test_bare_kwrest
@@ -418,6 +3904,25 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal([42, {:bar=>"x"}], b.new.foo(42), bug8236)
end
+ def test_super_with_keyword_kwrest
+ base = Class.new do
+ def foo(**h)
+ h
+ end
+ end
+ a = Class.new(base) do
+ attr_reader :h
+ def foo(a:, b:, **h)
+ @h = h
+ super
+ end
+ end
+
+ o = a.new
+ assert_equal({a: 1, b: 2, c: 3}, o.foo(a: 1, b: 2, c: 3))
+ assert_equal({c: 3}, o.h)
+ end
+
def test_zsuper_only_named_kwrest
bug8416 = '[ruby-core:55033] [Bug #8416]'
base = Class.new do
@@ -426,11 +3931,15 @@ class TestKeywordArguments < Test::Unit::TestCase
end
end
a = Class.new(base) do
+ attr_reader :h
def foo(**h)
+ @h = h
super
end
end
- assert_equal({:bar=>"x"}, a.new.foo(bar: "x"), bug8416)
+ o = a.new
+ assert_equal({:bar=>"x"}, o.foo(bar: "x"), bug8416)
+ assert_equal({:bar=>"x"}, o.h)
end
def test_zsuper_only_anonymous_kwrest
@@ -460,13 +3969,12 @@ class TestKeywordArguments < Test::Unit::TestCase
end
def test_precedence_of_keyword_arguments_with_post_argument
- bug8993 = '[ruby-core:57706] [Bug #8993]'
a = Class.new do
def foo(a, b, c=1, *d, e, f:2, **g)
[a, b, c, d, e, f, g]
end
end
- assert_equal([1, 2, 1, [], {:f=>5}, 2, {}], a.new.foo(1, 2, f:5), bug8993)
+ assert_raise(ArgumentError) { a.new.foo(1, 2, f:5) }
end
def test_splat_keyword_nondestructive
@@ -493,33 +4001,49 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal({a: 1, b: 2}, m1(**o, **o2) {|x| break x}, bug9898)
end
- def test_implicit_hash_conversion
- bug10016 = '[ruby-core:63593] [Bug #10016]'
-
+ def test_no_implicit_hash_conversion
o = Object.new
def o.to_hash() { k: 9 } end
assert_equal([1, 42, [], o, :key, {}, nil], f9(1, o))
- assert_equal([1, 9], m1(1, o) {|a, k: 0| break [a, k]}, bug10016)
- assert_equal([1, 9], m1(1, o, &->(a, k: 0) {break [a, k]}), bug10016)
+ assert_equal([1, 0], m1(1, o) {|a, k: 0| break [a, k]})
+ assert_raise(ArgumentError) { m1(1, o, &->(a, k: 0) {break [a, k]}) }
end
def test_splat_hash
m = Object.new
def m.f() :ok; end
+ def m.f1(a) a; end
def m.f2(a = nil) a; end
+ def m.f3(**a) a; end
+ def m.f4(*a) a; end
o = {a: 1}
- assert_raise_with_message(ArgumentError, /unknown keyword: a/) {
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given 1, expected 0\)/) {
m.f(**o)
}
o = {}
assert_equal(:ok, m.f(**o), '[ruby-core:68124] [Bug #10856]')
a = []
assert_equal(:ok, m.f(*a, **o), '[ruby-core:83638] [Bug #10856]')
+ assert_equal(:OK, m.f1(*a, :OK, **o), '[ruby-core:91825] [Bug #10856]')
+ assert_equal({}, m.f1(*a, o), '[ruby-core:91825] [Bug #10856]')
o = {a: 42}
- assert_equal({a: 42}, m.f2(**o), '[ruby-core:82280] [Bug #13791]')
+ assert_warning('', 'splat to mandatory') do
+ assert_equal({a: 42}, m.f1(**o))
+ end
+ assert_warning('') do
+ assert_equal({a: 42}, m.f2(**o), '[ruby-core:82280] [Bug #13791]')
+ end
+ assert_warning('', 'splat to kwrest') do
+ assert_equal({a: 42}, m.f3(**o))
+ end
+ assert_warning('', 'splat to rest') do
+ assert_equal([{a: 42}], m.f4(**o))
+ end
- assert_equal({a: 42}, m.f2("a".to_sym => 42), '[ruby-core:82291] [Bug #13793]')
+ assert_warning('') do
+ assert_equal({a: 42}, m.f2("a".to_sym => 42), '[ruby-core:82291] [Bug #13793]')
+ end
o = {}
a = [:ok]
@@ -535,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
@@ -545,6 +4069,20 @@ class TestKeywordArguments < Test::Unit::TestCase
}, bug8964
end
+ def test_large_kwsplat_to_method_taking_kw_and_kwsplat
+ assert_separately(['-'], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ n = 100000
+ x = Fiber.new do
+ h = {kw: 2}
+ n.times{|i| h[i.to_s.to_sym] = i}
+ def self.f(kw: 1, **kws) kws.size end
+ f(**h)
+ end.resume
+ assert_equal(n, x)
+ end;
+ end
+
def test_dynamic_symbol_keyword
bug10266 = '[ruby-dev:48564] [Bug #10266]'
assert_separately(['-', bug10266], "#{<<~"begin;"}\n#{<<~'end;'}")
@@ -565,14 +4103,14 @@ class TestKeywordArguments < Test::Unit::TestCase
bar(k1: 1)
end
end
- assert_raise_with_message(ArgumentError, /unknown keyword: k1/, bug10413) {
+ assert_raise_with_message(ArgumentError, /unknown keyword: :k1/, bug10413) {
o.foo {raise "unreachable"}
}
end
def test_unknown_keyword
bug13004 = '[ruby-dev:49893] [Bug #13004]'
- assert_raise_with_message(ArgumentError, /unknown keyword: invalid-argument/, bug13004) {
+ assert_raise_with_message(ArgumentError, /unknown keyword: :"invalid-argument"/, bug13004) {
[].sample(random: nil, "invalid-argument": nil)
}
end
@@ -601,7 +4139,7 @@ class TestKeywordArguments < Test::Unit::TestCase
def test_nonsymbol_key
result = m(["a" => 10]) { |a = nil, **b| [a, b] }
- assert_equal([{"a" => 10}, {}], result)
+ assert_equal([[{"a" => 10}], {}], result)
end
def method_for_test_to_hash_call_during_setup_complex_parameters k1:, k2:, **rest_kw
@@ -679,7 +4217,381 @@ class TestKeywordArguments < Test::Unit::TestCase
end
end
+ def many_kwargs(a0: '', a1: '', a2: '', a3: '', a4: '', a5: '', a6: '', a7: '',
+ b0: '', b1: '', b2: '', b3: '', b4: '', b5: '', b6: '', b7: '',
+ c0: '', c1: '', c2: '', c3: '', c4: '', c5: '', c6: '', c7: '',
+ d0: '', d1: '', d2: '', d3: '', d4: '', d5: '', d6: '', d7: '',
+ e0: '')
+ [a0, a1, a2, a3, a4, a5, a6, a7,
+ b0, b1, b2, b3, b4, b5, b6, b7,
+ c0, c1, c2, c3, c4, c5, c6, c7,
+ d0, d1, d2, d3, d4, d5, d6, d7,
+ e0]
+ end
+
+ def test_many_kwargs
+ i = 0
+ assert_equal(:ok, many_kwargs(a0: :ok)[i], "#{i}: a0"); i+=1
+ assert_equal(:ok, many_kwargs(a1: :ok)[i], "#{i}: a1"); i+=1
+ assert_equal(:ok, many_kwargs(a2: :ok)[i], "#{i}: a2"); i+=1
+ assert_equal(:ok, many_kwargs(a3: :ok)[i], "#{i}: a3"); i+=1
+ assert_equal(:ok, many_kwargs(a4: :ok)[i], "#{i}: a4"); i+=1
+ assert_equal(:ok, many_kwargs(a5: :ok)[i], "#{i}: a5"); i+=1
+ assert_equal(:ok, many_kwargs(a6: :ok)[i], "#{i}: a6"); i+=1
+ assert_equal(:ok, many_kwargs(a7: :ok)[i], "#{i}: a7"); i+=1
+
+ assert_equal(:ok, many_kwargs(b0: :ok)[i], "#{i}: b0"); i+=1
+ assert_equal(:ok, many_kwargs(b1: :ok)[i], "#{i}: b1"); i+=1
+ assert_equal(:ok, many_kwargs(b2: :ok)[i], "#{i}: b2"); i+=1
+ assert_equal(:ok, many_kwargs(b3: :ok)[i], "#{i}: b3"); i+=1
+ assert_equal(:ok, many_kwargs(b4: :ok)[i], "#{i}: b4"); i+=1
+ assert_equal(:ok, many_kwargs(b5: :ok)[i], "#{i}: b5"); i+=1
+ assert_equal(:ok, many_kwargs(b6: :ok)[i], "#{i}: b6"); i+=1
+ assert_equal(:ok, many_kwargs(b7: :ok)[i], "#{i}: b7"); i+=1
+
+ assert_equal(:ok, many_kwargs(c0: :ok)[i], "#{i}: c0"); i+=1
+ assert_equal(:ok, many_kwargs(c1: :ok)[i], "#{i}: c1"); i+=1
+ assert_equal(:ok, many_kwargs(c2: :ok)[i], "#{i}: c2"); i+=1
+ assert_equal(:ok, many_kwargs(c3: :ok)[i], "#{i}: c3"); i+=1
+ assert_equal(:ok, many_kwargs(c4: :ok)[i], "#{i}: c4"); i+=1
+ assert_equal(:ok, many_kwargs(c5: :ok)[i], "#{i}: c5"); i+=1
+ assert_equal(:ok, many_kwargs(c6: :ok)[i], "#{i}: c6"); i+=1
+ assert_equal(:ok, many_kwargs(c7: :ok)[i], "#{i}: c7"); i+=1
+
+ assert_equal(:ok, many_kwargs(d0: :ok)[i], "#{i}: d0"); i+=1
+ assert_equal(:ok, many_kwargs(d1: :ok)[i], "#{i}: d1"); i+=1
+ assert_equal(:ok, many_kwargs(d2: :ok)[i], "#{i}: d2"); i+=1
+ assert_equal(:ok, many_kwargs(d3: :ok)[i], "#{i}: d3"); i+=1
+ assert_equal(:ok, many_kwargs(d4: :ok)[i], "#{i}: d4"); i+=1
+ assert_equal(:ok, many_kwargs(d5: :ok)[i], "#{i}: d5"); i+=1
+ assert_equal(:ok, many_kwargs(d6: :ok)[i], "#{i}: d6"); i+=1
+ assert_equal(:ok, many_kwargs(d7: :ok)[i], "#{i}: d7"); i+=1
+
+ assert_equal(:ok, many_kwargs(e0: :ok)[i], "#{i}: e0"); i+=1
+ end
+
def test_splat_empty_hash_with_block_passing
assert_valid_syntax("bug15087(**{}, &nil)")
end
+
+ def test_do_not_use_newarraykwsplat
+ assert_equal([42, "foo", 424242], f2(*[], 42, **{}))
+ a = [1, 2, 3]
+ assert_equal([[1,2,3,4,5,6], "foo", 424242, {:k=>:k}], f7(*a, 4,5,6, k: :k))
+ end
+end
+
+class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase
+ class C
+ def call(*args, **kw)
+ yield(self, *args, **kw)
+ end
+ end
+ using(Module.new do
+ refine C do
+ def m(*args, **kw)
+ super
+ end
+ end
+ end)
+
+ def test_sym_proc_refine_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = C.new
+ def c.m(*args)
+ args
+ end
+ assert_equal([], c.call(**{}, &:m))
+ assert_equal([], c.call(**kw, &:m))
+ assert_equal([h], c.call(**h, &:m))
+ assert_equal([h], c.call(h, **{}, &:m))
+ assert_equal([h], c.call(a: 1, &:m))
+ assert_equal([h2], c.call(**h2, &:m))
+ assert_equal([h3], c.call(**h3, &:m))
+ assert_equal([h3], c.call(a: 1, **h2, &:m))
+
+ c.singleton_class.remove_method(:m)
+ def c.m; end
+ assert_nil(c.call(**{}, &:m))
+ assert_nil(c.call(**kw, &:m))
+ assert_raise(ArgumentError) { c.call(**h, &:m) }
+ assert_raise(ArgumentError) { c.call(a: 1, &:m) }
+ assert_raise(ArgumentError) { c.call(**h2, &:m) }
+ assert_raise(ArgumentError) { c.call(**h3, &:m) }
+ assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(args)
+ args
+ end
+ assert_raise(ArgumentError) { c.call(**{}, &:m) }
+ assert_raise(ArgumentError) { c.call(**kw, &:m) }
+ assert_equal(h, c.call(**h, &:m))
+ assert_equal(h, c.call(a: 1, &:m))
+ assert_equal(h2, c.call(**h2, &:m))
+ assert_equal(h3, c.call(**h3, &:m))
+ assert_equal(h3, c.call(a: 1, **h2, &:m))
+
+ c.singleton_class.remove_method(:m)
+ def c.m(**args)
+ args
+ end
+ assert_equal(kw, c.call(**{}, &:m))
+ assert_equal(kw, c.call(**kw, &:m))
+ assert_equal(h, c.call(**h, &:m))
+ assert_equal(h, c.call(a: 1, &:m))
+ assert_equal(h2, c.call(**h2, &:m))
+ assert_equal(h3, c.call(**h3, &:m))
+ assert_equal(h3, c.call(a: 1, **h2, &:m))
+ assert_raise(ArgumentError) { c.call(h, &:m) }
+ assert_raise(ArgumentError) { c.call(h2, &:m) }
+ assert_raise(ArgumentError) { c.call(h3, &:m) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { c.call(**{}, &:m) }
+ assert_raise(ArgumentError) { c.call(**kw, &:m) }
+ assert_raise(ArgumentError) { c.call(**h, &:m) }
+ assert_raise(ArgumentError) { c.call(a: 1, &:m) }
+ assert_raise(ArgumentError) { c.call(**h2, &:m) }
+ assert_raise(ArgumentError) { c.call(**h3, &:m) }
+ assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m) }
+
+ c.singleton_class.remove_method(:m)
+ def c.m(arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], c.call(**{}, &:m))
+ assert_equal([1, kw], c.call(**kw, &:m))
+ assert_equal([1, h], c.call(**h, &:m))
+ assert_equal([1, h], c.call(a: 1, &:m))
+ assert_equal([1, h2], c.call(**h2, &:m))
+ assert_equal([1, h3], c.call(**h3, &:m))
+ assert_equal([1, h3], c.call(a: 1, **h2, &:m))
+ end
+
+ def test_sym_proc_refine_super_method_missing_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = C.new
+ def c.method_missing(_, *args)
+ args
+ end
+ assert_equal([], c.call(**{}, &:m))
+ assert_equal([], c.call(**kw, &:m))
+ assert_equal([h], c.call(**h, &:m))
+ assert_equal([h], c.call(h, **{}, &:m))
+ assert_equal([h], c.call(a: 1, &:m))
+ assert_equal([h2], c.call(**h2, &:m))
+ assert_equal([h3], c.call(**h3, &:m))
+ assert_equal([h3], c.call(a: 1, **h2, &:m))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_) end
+ assert_nil(c.call(**{}, &:m))
+ assert_nil(c.call(**kw, &:m))
+ assert_raise(ArgumentError) { c.call(**h, &:m) }
+ assert_raise(ArgumentError) { c.call(a: 1, &:m) }
+ assert_raise(ArgumentError) { c.call(**h2, &:m) }
+ assert_raise(ArgumentError) { c.call(**h3, &:m) }
+ assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, args)
+ args
+ end
+ assert_raise(ArgumentError) { c.call(**{}, &:m) }
+ assert_raise(ArgumentError) { c.call(**kw, &:m) }
+ assert_equal(h, c.call(**h, &:m))
+ assert_equal(h, c.call(a: 1, &:m))
+ assert_equal(h2, c.call(**h2, &:m))
+ assert_equal(h3, c.call(**h3, &:m))
+ assert_equal(h3, c.call(a: 1, **h2, &:m))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, **args)
+ args
+ end
+ assert_equal(kw, c.call(**{}, &:m))
+ assert_equal(kw, c.call(**kw, &:m))
+ assert_equal(h, c.call(**h, &:m))
+ assert_equal(h, c.call(a: 1, &:m))
+ assert_equal(h2, c.call(**h2, &:m))
+ assert_equal(h3, c.call(**h3, &:m))
+ assert_equal(h3, c.call(a: 1, **h2, &:m))
+ assert_raise(ArgumentError) { c.call(h, &:m2) }
+ assert_raise(ArgumentError) { c.call(h2, &:m2) }
+ assert_raise(ArgumentError) { c.call(h3, &:m2) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { c.call(**{}, &:m2) }
+ assert_raise(ArgumentError) { c.call(**kw, &:m2) }
+ assert_raise(ArgumentError) { c.call(**h, &:m2) }
+ assert_raise(ArgumentError) { c.call(a: 1, &:m2) }
+ assert_raise(ArgumentError) { c.call(**h2, &:m2) }
+ assert_raise(ArgumentError) { c.call(**h3, &:m2) }
+ assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m2) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], c.call(**{}, &:m))
+ assert_equal([1, kw], c.call(**kw, &:m))
+ assert_equal([1, h], c.call(**h, &:m))
+ assert_equal([1, h], c.call(a: 1, &:m))
+ assert_equal([1, h2], c.call(**h2, &:m))
+ assert_equal([1, h3], c.call(**h3, &:m))
+ assert_equal([1, h3], c.call(a: 1, **h2, &:m))
+ end
+
+ def test_sym_proc_refine_method_missing_kwsplat
+ kw = {}
+ h = {:a=>1}
+ h2 = {'a'=>1}
+ h3 = {'a'=>1, :a=>1}
+
+ c = C.new
+ def c.method_missing(_, *args)
+ args
+ end
+ assert_equal([], c.call(**{}, &:m2))
+ assert_equal([], c.call(**kw, &:m2))
+ assert_equal([h], c.call(**h, &:m2))
+ assert_equal([h], c.call(h, **{}, &:m2))
+ assert_equal([h], c.call(a: 1, &:m2))
+ assert_equal([h2], c.call(**h2, &:m2))
+ assert_equal([h3], c.call(**h3, &:m2))
+ assert_equal([h3], c.call(a: 1, **h2, &:m2))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_) end
+ assert_nil(c.call(**{}, &:m2))
+ assert_nil(c.call(**kw, &:m2))
+ assert_raise(ArgumentError) { c.call(**h, &:m2) }
+ assert_raise(ArgumentError) { c.call(a: 1, &:m2) }
+ assert_raise(ArgumentError) { c.call(**h2, &:m2) }
+ assert_raise(ArgumentError) { c.call(**h3, &:m2) }
+ assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m2) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, args)
+ args
+ end
+ assert_raise(ArgumentError) { c.call(**{}, &:m2) }
+ assert_raise(ArgumentError) { c.call(**kw, &:m2) }
+ assert_equal(h, c.call(**h, &:m2))
+ assert_equal(h, c.call(a: 1, &:m2))
+ assert_equal(h2, c.call(**h2, &:m2))
+ assert_equal(h3, c.call(**h3, &:m2))
+ assert_equal(h3, c.call(a: 1, **h2, &:m2))
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, **args)
+ args
+ end
+ assert_equal(kw, c.call(**{}, &:m2))
+ assert_equal(kw, c.call(**kw, &:m2))
+ assert_equal(h, c.call(**h, &:m2))
+ assert_equal(h, c.call(a: 1, &:m2))
+ assert_equal(h2, c.call(**h2, &:m2))
+ assert_equal(h3, c.call(**h3, &:m2))
+ assert_equal(h3, c.call(a: 1, **h2, &:m2))
+ assert_raise(ArgumentError) { c.call(h, &:m2) }
+ assert_raise(ArgumentError) { c.call(h2, &:m2) }
+ assert_raise(ArgumentError) { c.call(h3, &:m2) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, arg, **args)
+ [arg, args]
+ end
+ assert_raise(ArgumentError) { c.call(**{}, &:m2) }
+ assert_raise(ArgumentError) { c.call(**kw, &:m2) }
+ assert_raise(ArgumentError) { c.call(**h, &:m2) }
+ assert_raise(ArgumentError) { c.call(a: 1, &:m2) }
+ assert_raise(ArgumentError) { c.call(**h2, &:m2) }
+ assert_raise(ArgumentError) { c.call(**h3, &:m2) }
+ assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m2) }
+
+ c.singleton_class.remove_method(:method_missing)
+ def c.method_missing(_, arg=1, **args)
+ [arg, args]
+ end
+ assert_equal([1, kw], c.call(**{}, &:m2))
+ assert_equal([1, kw], c.call(**kw, &:m2))
+ assert_equal([1, h], c.call(**h, &:m2))
+ assert_equal([1, h], c.call(a: 1, &:m2))
+ assert_equal([1, h2], c.call(**h2, &:m2))
+ assert_equal([1, h3], c.call(**h3, &:m2))
+ assert_equal([1, h3], c.call(a: 1, **h2, &:m2))
+ end
+
+ def test_protected_kwarg
+ mock = Class.new do
+ def foo
+ bar('x', y: 'z')
+ end
+ protected
+ def bar(x, y)
+ nil
+ end
+ end
+
+ assert_nothing_raised do
+ mock.new.foo
+ end
+ end
+
+ def test_splat_fixnum
+ bug16603 = '[ruby-core:97047] [Bug #16603]'
+ assert_raise(TypeError, bug16603) { p(**42) }
+ assert_raise(TypeError, bug16603) { p(k:1, **42) }
+ end
+
+ def test_value_omission
+ f = ->(**kwargs) { kwargs }
+ x = 1
+ y = 2
+ assert_equal({x: 1, y: 2}, f.call(x:, y:))
+ assert_equal({x: 1, y: 2, z: 3}, f.call(x:, y:, z: 3))
+ assert_equal({one: 1, two: 2}, f.call(one:, two:))
+ end
+
+ def m_bug20570(*a, **nil)
+ a
+ end
+
+ def test_splat_arg_with_prohibited_keyword
+ assert_equal([], m_bug20570(*[]))
+ assert_equal([1], m_bug20570(*[1]))
+ assert_equal([1, 2], m_bug20570(*[1, 2]))
+ h = nil
+ assert_equal([], m_bug20570(*[], **h))
+ assert_equal([1], m_bug20570(*[1], **h))
+ assert_equal([1, 2], m_bug20570(*[1, 2], **h))
+
+ assert_equal([], m_bug20570(*[], **nil))
+ assert_equal([1], m_bug20570(*[1], **nil))
+ assert_equal([1, 2], m_bug20570(*[1, 2], **nil))
+ end
+
+ private def one
+ 1
+ end
+
+ private def two
+ 2
+ end
end
diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb
index 3ac2e4cb98..c1858a36dd 100644
--- a/test/ruby/test_lambda.rb
+++ b/test/ruby/test_lambda.rb
@@ -74,6 +74,109 @@ class TestLambdaParameters < Test::Unit::TestCase
assert_raise(ArgumentError, bug9605) {proc(&plus).call [1,2]}
end
+ def test_proc_inside_lambda_inside_method_return_inside_lambda_inside_method
+ def self.a
+ -> do
+ p = Proc.new{return :a}
+ p.call
+ end.call
+ end
+ assert_equal(:a, a)
+
+ def self.b
+ lambda do
+ p = Proc.new{return :b}
+ p.call
+ end.call
+ end
+ assert_equal(:b, b)
+ end
+
+ def test_proc_inside_lambda_inside_method_return_inside_lambda_outside_method
+ def self.a
+ -> do
+ p = Proc.new{return :a}
+ p.call
+ end
+ end
+ assert_equal(:a, a.call)
+
+ def self.b
+ lambda do
+ p = Proc.new{return :b}
+ p.call
+ end
+ end
+ assert_equal(:b, b.call)
+ end
+
+ def test_proc_inside_lambda_inside_method_return_outside_lambda_inside_method
+ def self.a
+ -> do
+ Proc.new{return :a}
+ end.call.call
+ end
+ assert_raise(LocalJumpError) {a}
+
+ def self.b
+ lambda do
+ Proc.new{return :b}
+ end.call.call
+ end
+ assert_raise(LocalJumpError) {b}
+ end
+
+ def test_proc_inside_lambda_inside_method_return_outside_lambda_outside_method
+ def self.a
+ -> do
+ Proc.new{return :a}
+ end
+ end
+ assert_raise(LocalJumpError) {a.call.call}
+
+ def self.b
+ lambda do
+ Proc.new{return :b}
+ end
+ end
+ assert_raise(LocalJumpError) {b.call.call}
+ end
+
+ def test_proc_inside_lambda2_inside_method_return_outside_lambda1_inside_method
+ def self.a
+ -> do
+ -> do
+ Proc.new{return :a}
+ end.call.call
+ end.call
+ end
+ assert_raise(LocalJumpError) {a}
+
+ def self.b
+ lambda do
+ lambda do
+ Proc.new{return :a}
+ end.call.call
+ end.call
+ end
+ assert_raise(LocalJumpError) {b}
+ end
+
+ def test_proc_inside_lambda_toplevel
+ assert_ruby_status [], <<~RUBY
+ lambda{
+ $g = proc{ return :pr }
+ }.call
+ begin
+ $g.call
+ rescue LocalJumpError
+ # OK!
+ else
+ raise
+ end
+ RUBY
+ end
+
def test_instance_exec
bug12568 = '[ruby-core:76300] [Bug #12568]'
assert_nothing_raised(ArgumentError, bug12568) do
@@ -157,27 +260,69 @@ class TestLambdaParameters < Test::Unit::TestCase
assert_equal(42, return_in_callee(42), feature8693)
end
+ def break_in_current(val)
+ 1.tap(&->(*) {break 0})
+ val
+ end
+
+ def break_in_callee(val)
+ yield_block(&->(*) {break 0})
+ val
+ end
+
+ def test_break
+ assert_equal(42, break_in_current(42))
+ assert_equal(42, break_in_callee(42))
+ end
+
def test_do_lambda_source_location
- exp_lineno = __LINE__ + 3
+ exp = [__LINE__ + 1, 10, __LINE__ + 5, 7]
lmd = ->(x,
y,
z) do
#
end
- file, lineno = lmd.source_location
+ file, *loc = lmd.source_location
assert_match(/^#{ Regexp.quote(__FILE__) }$/, file)
- assert_equal(exp_lineno, lineno, "must be at the beginning of the block")
+ assert_equal(exp, loc)
end
def test_brace_lambda_source_location
- exp_lineno = __LINE__ + 3
+ exp = [__LINE__ + 1, 10, __LINE__ + 5, 5]
lmd = ->(x,
y,
z) {
#
}
- file, lineno = lmd.source_location
+ file, *loc = lmd.source_location
assert_match(/^#{ Regexp.quote(__FILE__) }$/, file)
- assert_equal(exp_lineno, lineno, "must be at the beginning of the block")
+ assert_equal(exp, loc)
+ end
+
+ def test_not_orphan_return
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b.call end; def m2(); m1(&-> { return 42 }) end }.m2)
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b end; def m2(); m1(&-> { return 42 }).call end }.m2)
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b end; def m2(); m1(&-> { return 42 }) end }.m2.call)
+ end
+
+ def test_not_orphan_break
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b.call end; def m2(); m1(&-> { break 42 }) end }.m2)
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b end; def m2(); m1(&-> { break 42 }).call end }.m2)
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b end; def m2(); m1(&-> { break 42 }) end }.m2.call)
+ end
+
+ def test_not_orphan_next
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b.call end; def m2(); m1(&-> { next 42 }) end }.m2)
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b end; def m2(); m1(&-> { next 42 }).call end }.m2)
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b end; def m2(); m1(&-> { next 42 }) end }.m2.call)
end
end
diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb
index 03371c912a..4dddbab50c 100644
--- a/test/ruby/test_lazy_enumerator.rb
+++ b/test/ruby/test_lazy_enumerator.rb
@@ -17,9 +17,9 @@ class TestLazyEnumerator < Test::Unit::TestCase
@enum.each do |v|
@current = v
if v.is_a? Enumerable
- yield *v
+ yield(*v)
else
- yield v
+ yield(v)
end
end
end
@@ -160,6 +160,10 @@ class TestLazyEnumerator < Test::Unit::TestCase
assert_equal([{?a=>97}, {?b=>98}, {?c=>99}], [?a, ?b, ?c].lazy.flat_map {|x| {x=>x.ord}}.force)
end
+ def test_flat_map_take
+ assert_equal([1,2]*3, [[1,2]].cycle.lazy.take(3).flat_map {|x| x}.to_a)
+ end
+
def test_reject
a = Step.new(1..6)
assert_equal(4, a.reject {|x| x < 4}.first)
@@ -278,6 +282,11 @@ class TestLazyEnumerator < Test::Unit::TestCase
assert_equal(3, a.current)
end
+ def test_zip_map_lambda_bug_19569
+ ary = [1, 2, 3].to_enum.lazy.zip([:a, :b, :c]).map(&:last).to_a
+ assert_equal([:a, :b, :c], ary)
+ end
+
def test_take
a = Step.new(1..10)
assert_equal(1, a.take(5).first)
@@ -291,6 +300,26 @@ class TestLazyEnumerator < Test::Unit::TestCase
assert_equal(nil, a.current)
end
+ def test_take_0_bug_18971
+ def (bomb = Object.new.extend(Enumerable)).each
+ raise
+ end
+ [2..10, bomb].each do |e|
+ assert_equal([], e.lazy.take(0).map(&:itself).to_a)
+ assert_equal([], e.lazy.take(0).select(&:even?).to_a)
+ assert_equal([], e.lazy.take(0).select(&:odd?).to_a)
+ assert_equal([], e.lazy.take(0).reject(&:even?).to_a)
+ assert_equal([], e.lazy.take(0).reject(&:odd?).to_a)
+ assert_equal([], e.lazy.take(0).take(1).to_a)
+ assert_equal([], e.lazy.take(0).take(0).take(1).to_a)
+ assert_equal([], e.lazy.take(0).drop(0).to_a)
+ assert_equal([], e.lazy.take(0).find_all {|_| true}.to_a)
+ assert_equal([], e.lazy.take(0).zip((12..20)).to_a)
+ assert_equal([], e.lazy.take(0).uniq.to_a)
+ assert_equal([], e.lazy.take(0).sort.to_a)
+ end
+ end
+
def test_take_bad_arg
a = Step.new(1..10)
assert_raise(ArgumentError) { a.lazy.take(-1) }
@@ -452,6 +481,18 @@ class TestLazyEnumerator < Test::Unit::TestCase
EOS
end
+ def test_lazy_eager
+ lazy = [1, 2, 3].lazy.map { |x| x * 2 }
+ enum = lazy.eager
+ assert_equal Enumerator, enum.class
+ assert_equal 3, enum.size
+ assert_equal [1, 2, 3], enum.map { |x| x / 2 }
+ end
+
+ def test_lazy_zip_map_yield_arity_bug_20623
+ assert_equal([[1, 2]], [1].lazy.zip([2].lazy).map { |x| x }.force)
+ end
+
def test_lazy_to_enum
lazy = [1, 2, 3].lazy
def lazy.foo(*args)
@@ -466,6 +507,54 @@ EOS
assert_equal [1, 2, 3], lazy.to_enum.to_a
end
+ def test_lazy_to_enum_lazy_methods
+ a = [1, 2, 3].to_enum
+ pr = proc{|x| [x, x * 2]}
+ selector = proc{|x| x*2 if x % 2 == 0}
+
+ [
+ [:with_index, nil],
+ [:with_index, 10, nil],
+ [:with_index, 10, pr],
+ [:map, nil],
+ [:map, pr],
+ [:collect, nil],
+ [:flat_map, nil],
+ [:flat_map, pr],
+ [:collect_concat, nil],
+ [:select, nil],
+ [:select, selector],
+ [:find_all, nil],
+ [:filter, nil],
+ [:filter_map, selector],
+ [:filter_map, nil],
+ [:reject, selector],
+ [:grep, selector, nil],
+ [:grep, selector, pr],
+ [:grep_v, selector, nil],
+ [:grep_v, selector, pr],
+ [:zip, a, nil],
+ [:take, 3, nil],
+ [:take_while, nil],
+ [:take_while, selector],
+ [:drop, 1, nil],
+ [:drop_while, nil],
+ [:drop_while, selector],
+ [:uniq, nil],
+ [:uniq, proc{|x| x.odd?}],
+ ].each do |args|
+ block = args.pop
+ assert_equal [1, 2, 3].to_enum.to_enum(*args).first(2).to_a, [1, 2, 3].to_enum.lazy.to_enum(*args).first(2).to_a
+ assert_equal (0..50).to_enum.to_enum(*args).first(2).to_a, (0..50000).to_enum.lazy.to_enum(*args).first(2).to_a
+ if block
+ assert_equal [1, 2, 3, 4].to_enum.to_enum(*args).map(&block).first(2).to_a, [1, 2, 3, 4].to_enum.lazy.to_enum(*args).map(&block).first(2).to_a
+ unless args.first == :take_while || args.first == :drop_while
+ assert_equal (0..50).to_enum.to_enum(*args).map(&block).first(2).to_a, (0..50000).to_enum.lazy.to_enum(*args).map(&block).first(2).to_a
+ end
+ end
+ end
+ end
+
def test_size
lazy = [1, 2, 3].lazy
assert_equal 3, lazy.size
@@ -578,4 +667,52 @@ EOS
assert_equal([1, 2, 3, 4, 5, 10], u.first(6))
assert_equal([1, 2, 3, 4, 5, 10], u.first(6))
end
+
+ def test_filter_map
+ e = (1..Float::INFINITY).lazy.filter_map do |x|
+ raise "too big" if x > 10000
+ (x**2) % 10 if x.even?
+ end
+ assert_equal([4, 6, 6, 4, 0, 4], e.first(6))
+ assert_equal([4, 6, 6, 4, 0, 4], e.first(6))
+ end
+
+ def test_with_index
+ feature7877 = '[ruby-dev:47025] [Feature #7877]'
+ leibniz = ->(n) {
+ (0..Float::INFINITY).lazy.with_index.map {|i, j|
+ raise IndexError, "limit exceeded (#{n})" unless j < n
+ ((-1) ** j) / (2*i+1).to_f
+ }.take(n).reduce(:+)
+ }
+ assert_nothing_raised(IndexError, feature7877) {
+ assert_in_epsilon(Math::PI/4, leibniz[1000])
+ }
+
+ a = []
+ ary = (0..Float::INFINITY).lazy.with_index(2) {|i, j| a << [i-1, j] }.take(2).to_a
+ assert_equal([[-1, 2], [0, 3]], a)
+ assert_equal([0, 1], ary)
+
+ a = []
+ ary = (0..Float::INFINITY).lazy.with_index(2, &->(i,j) { a << [i-1, j] }).take(2).to_a
+ assert_equal([[-1, 2], [0, 3]], a)
+ assert_equal([0, 1], ary)
+
+ ary = (0..Float::INFINITY).lazy.with_index(2).map {|i, j| [i-1, j] }.take(2).to_a
+ assert_equal([[-1, 2], [0, 3]], ary)
+
+ ary = (0..Float::INFINITY).lazy.with_index(2).map(&->(i, j) { [i-1, j] }).take(2).to_a
+ assert_equal([[-1, 2], [0, 3]], ary)
+
+ ary = (0..Float::INFINITY).lazy.with_index(2).take(2).to_a
+ assert_equal([[0, 2], [1, 3]], ary)
+
+ ary = (0..Float::INFINITY).lazy.with_index.take(2).to_a
+ assert_equal([[0, 0], [1, 1]], ary)
+ end
+
+ def test_with_index_size
+ assert_equal(3, Enumerator::Lazy.new([1, 2, 3], 3){|y, v| y << v}.with_index.size)
+ end
end
diff --git a/test/ruby/test_literal.rb b/test/ruby/test_literal.rb
index cf1a2babd7..cff888d4b3 100644
--- a/test/ruby/test_literal.rb
+++ b/test/ruby/test_literal.rb
@@ -26,7 +26,7 @@ class TestRubyLiteral < Test::Unit::TestCase
assert_equal '5', 0b101.inspect
assert_instance_of Integer, 0b101
assert_raise(SyntaxError) { eval("0b") }
- assert_equal '123456789012345678901234567890', 123456789012345678901234567890.inspect
+ assert_equal '123456789012345678901234567890', 123456789012345678901234567890.to_s
assert_instance_of Integer, 123456789012345678901234567890
assert_instance_of Float, 1.3
assert_equal '2', eval("0x00+2").inspect
@@ -39,12 +39,15 @@ class TestRubyLiteral < Test::Unit::TestCase
end
def test_string
+ verbose_bak, $VERBOSE = $VERBOSE, nil # prevent syntax warnings
+
assert_instance_of String, ?a
assert_equal "a", ?a
assert_instance_of String, ?A
assert_equal "A", ?A
assert_instance_of String, ?\n
assert_equal "\n", ?\n
+ assert_equal " ", ?\s
assert_equal " ", ?\ # space
assert_equal '', ''
assert_equal 'string', 'string'
@@ -93,6 +96,15 @@ class TestRubyLiteral < Test::Unit::TestCase
assert_equal "ab", eval("?a 'b'")
assert_equal "a\nb", eval("<<A 'b'\na\nA")
+
+ assert_raise(SyntaxError) {eval('"\C-' "\u3042" '"')}
+ assert_raise(SyntaxError) {eval('"\C-\\' "\u3042" '"')}
+ assert_raise(SyntaxError) {eval('"\M-' "\u3042" '"')}
+ assert_raise(SyntaxError) {eval('"\M-\\' "\u3042" '"')}
+
+ assert_equal "\x09 \xC9 \x89", eval('"\C-\111 \M-\111 \M-\C-\111"')
+ ensure
+ $VERBOSE = verbose_bak
end
def test_dstring
@@ -141,6 +153,8 @@ class TestRubyLiteral < Test::Unit::TestCase
end
def test_frozen_string
+ default = eval("'test'").frozen?
+
all_assertions do |a|
a.for("false with indicator") do
str = eval("# -*- frozen-string-literal: false -*-\n""'foo'")
@@ -160,34 +174,56 @@ class TestRubyLiteral < Test::Unit::TestCase
end
a.for("false with preceding garbage") do
str = eval("# x frozen-string-literal: false\n""'foo'")
- assert_not_predicate(str, :frozen?)
+ assert_equal(default, str.frozen?)
end
a.for("true with preceding garbage") do
str = eval("# x frozen-string-literal: true\n""'foo'")
- assert_not_predicate(str, :frozen?)
+ assert_equal(default, str.frozen?)
end
a.for("false with succeeding garbage") do
str = eval("# frozen-string-literal: false x\n""'foo'")
- assert_not_predicate(str, :frozen?)
+ assert_equal(default, str.frozen?)
end
a.for("true with succeeding garbage") do
str = eval("# frozen-string-literal: true x\n""'foo'")
- assert_not_predicate(str, :frozen?)
+ assert_equal(default, str.frozen?)
end
end
end
+ def test_frozen_string_in_array_literal
+ list = eval("# frozen-string-literal: true\n""['foo', 'bar']")
+ assert_equal 2, list.length
+ list.each { |str| assert_predicate str, :frozen? }
+ end
+
+ def test_string_in_hash_literal
+ hash = eval("# frozen-string-literal: false\n""{foo: 'foo'}")
+ assert_not_predicate(hash[:foo], :frozen?)
+ end
+
if defined?(RubyVM::InstructionSequence.compile_option) and
RubyVM::InstructionSequence.compile_option.key?(:debug_frozen_string_literal)
def test_debug_frozen_string
- src = 'n = 1; "foo#{n ? "-#{n}" : ""}"'; f = "test.rb"; n = 1
+ src = '_="foo-1"'; f = "test.rb"; n = 1
opt = {frozen_string_literal: true, debug_frozen_string_literal: true}
- str = RubyVM::InstructionSequence.compile(src, f, f, n, opt).eval
+ str = RubyVM::InstructionSequence.compile(src, f, f, n, **opt).eval
assert_equal("foo-1", str)
assert_predicate(str, :frozen?)
assert_raise_with_message(FrozenError, /created at #{Regexp.quote(f)}:#{n}/) {
str << "x"
- }
+ } unless ENV['RUBY_ISEQ_DUMP_DEBUG']
+ end
+
+ def test_debug_frozen_string_in_array_literal
+ src = '["foo"]'; f = "test.rb"; n = 1
+ opt = {frozen_string_literal: true, debug_frozen_string_literal: true}
+ ary = RubyVM::InstructionSequence.compile(src, f, f, n, **opt).eval
+ assert_equal("foo", ary.first)
+ assert_predicate(ary.first, :frozen?)
+ assert_raise_with_message(FrozenError, /created at #{Regexp.quote(f)}:#{n}/) {
+ ary.first << "x"
+ } unless ENV['RUBY_ISEQ_DUMP_DEBUG']
end
end
@@ -216,8 +252,9 @@ class TestRubyLiteral < Test::Unit::TestCase
def test_dregexp
assert_instance_of Regexp, /re#{'ge'}xp/
assert_equal(/regexp/, /re#{'ge'}xp/)
- bug3903 = '[ruby-core:32682]'
- assert_raise(SyntaxError, bug3903) {eval('/[#{"\x80"}]/')}
+
+ # [ruby-core:32682]
+ eval('/[#{"\x80"}]/')
end
def test_array
@@ -266,6 +303,24 @@ class TestRubyLiteral < Test::Unit::TestCase
assert_equal "literal", h["string"]
end
+ def test_hash_literal_frozen
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ def frozen_hash_literal_arg
+ {0=>1,1=>4,2=>17}
+ end
+
+ ObjectSpace.each_object(Hash) do |a|
+ if a.class == Hash and !a.default_proc and a.size == 3 &&
+ a[0] == 1 && a[1] == 4 && a[2] == 17
+ # should not be found.
+ raise
+ end
+ end
+ assert_not_include frozen_hash_literal_arg, 3
+ end;
+ end
+
def test_big_array_and_hash_literal
assert_normal_exit %q{GC.disable=true; x = nil; raise if eval("[#{(1..1_000_000).map{'x'}.join(", ")}]").size != 1_000_000}, "", timeout: 300, child_env: %[--disable-gems]
assert_normal_exit %q{GC.disable=true; x = nil; raise if eval("[#{(1..1_000_000).to_a.join(", ")}]").size != 1_000_000}, "", timeout: 300, child_env: %[--disable-gems]
@@ -425,17 +480,47 @@ class TestRubyLiteral < Test::Unit::TestCase
def test_hash_duplicated_key
h = EnvUtil.suppress_warning do
- eval <<~end
+ eval "#{<<-"begin;"}\n#{<<-'end;'}"
+ begin;
# This is a syntax that renders warning at very early stage.
# eval used to delay warning, to be suppressible by EnvUtil.
{"a" => 100, "b" => 200, "a" => 300, "a" => 400}
- end
+ end;
end
assert_equal(2, h.size)
assert_equal(400, h['a'])
assert_equal(200, h['b'])
assert_nil(h['c'])
assert_equal(nil, h.key('300'))
+
+ a = []
+ h = EnvUtil.suppress_warning do
+ eval <<~end
+ # This is a syntax that renders warning at very early stage.
+ # eval used to delay warning, to be suppressible by EnvUtil.
+ {"a" => a.push(100).last, "b" => a.push(200).last, "a" => a.push(300).last, "a" => a.push(400).last}
+ end
+ end
+ assert_equal({'a' => 400, 'b' => 200}, h)
+ assert_equal([100, 200, 300, 400], a)
+
+ assert_all_assertions_foreach(
+ "duplicated literal key",
+ ':foo',
+ '"a"',
+ '1000',
+ '1.0',
+ '1_000_000_000_000_000_000_000',
+ '1.0r',
+ '1.0i',
+ '1.72723e-77',
+ '//',
+ '__LINE__',
+ '__FILE__',
+ '__ENCODING__',
+ ) do |key|
+ assert_warning(/key #{Regexp.quote(eval(key).inspect)} is duplicated/) { eval("{#{key} => :bar, #{key} => :foo}") }
+ end
end
def test_hash_frozen_key_id
@@ -452,6 +537,30 @@ class TestRubyLiteral < Test::Unit::TestCase
assert_equal(100, h['a'])
end
+ FOO = "foo"
+
+ def test_hash_value_omission
+ x = 1
+ y = 2
+ assert_equal({x: 1, y: 2}, {x:, y:})
+ assert_equal({x: 1, y: 2, z: 3}, {x:, y:, z: 3})
+ assert_equal({one: 1, two: 2}, {one:, two:})
+ b = binding
+ b.local_variable_set(:if, "if")
+ b.local_variable_set(:self, "self")
+ assert_equal({FOO: "foo", if: "if", self: "self"},
+ eval('{FOO:, if:, self:}', b))
+ assert_syntax_error('{"#{x}":}', /'\}'/)
+ end
+
+ private def one
+ 1
+ end
+
+ private def two
+ 2
+ end
+
def test_range
assert_instance_of Range, (1..2)
assert_equal(1..2, 1..2)
@@ -493,6 +602,8 @@ class TestRubyLiteral < Test::Unit::TestCase
end
def test_integer
+ verbose_bak, $VERBOSE = $VERBOSE, nil # prevent syntax warnings
+
head = ['', '0x', '0o', '0b', '0d', '-', '+']
chars = ['0', '1', '_', '9', 'f']
head.each {|h|
@@ -516,18 +627,20 @@ class TestRubyLiteral < Test::Unit::TestCase
}
}
bug2407 = '[ruby-dev:39798]'
- head.each {|h|
- if /^0/ =~ h
- begin
- eval("#{h}_")
- rescue SyntaxError => e
- assert_match(/numeric literal without digits\Z/, e.message, bug2407)
- end
+ head.grep_v(/^0/) do |s|
+ head.grep(/^0/) do |h|
+ h = "#{s}#{h}_"
+ assert_syntax_error(h, /numeric literal without digits\Z/, "#{bug2407}: #{h.inspect}")
end
- }
+ end
+
+ ensure
+ $VERBOSE = verbose_bak
end
def test_float
+ verbose_bak, $VERBOSE = $VERBOSE, nil # prevent syntax warnings
+
head = ['', '-', '+']
chars = ['0', '1', '_', '9', 'f', '.']
head.each {|h|
@@ -546,15 +659,32 @@ class TestRubyLiteral < Test::Unit::TestCase
end
begin
r2 = eval(s)
- rescue NameError, SyntaxError
+ rescue ArgumentError
+ # Debug log for a random failure: ArgumentError: SyntaxError#path changed
+ $stderr.puts "TestRubyLiteral#test_float failed: %p" % s
+ raise
+ rescue SyntaxError => e
+ r2 = :err
+ rescue NameError
r2 = :err
end
r2 = :err if Range === r2
- assert_equal(r1, r2, "Float(#{s.inspect}) != eval(#{s.inspect})")
+ s = s.inspect
+ mesg = "Float(#{s}) != eval(#{s})"
+ mesg << ":" << e.message if e
+ assert_equal(r1, r2, mesg)
}
}
}
assert_equal(100.0, 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100e100)
+
+ ensure
+ $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
diff --git a/test/ruby/test_m17n.rb b/test/ruby/test_m17n.rb
index 9c7df4cb03..9f7a3c7f4b 100644
--- a/test/ruby/test_m17n.rb
+++ b/test/ruby/test_m17n.rb
@@ -69,15 +69,6 @@ class TestM17N < Test::Unit::TestCase
assert_regexp_fixed_encoding(r)
end
- def assert_regexp_usascii_literal(r, enc, ex = nil)
- code = "# -*- encoding: US-ASCII -*-\n#{r}.encoding"
- if ex
- assert_raise(ex) { eval(code) }
- else
- assert_equal(enc, eval(code))
- end
- end
-
def encdump(str)
d = str.dump
if /\.force_encoding\("[A-Za-z0-9.:_+-]*"\)\z/ =~ d
@@ -195,69 +186,49 @@ 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
- end
-
- STR_WITHOUT_BOM = "\u3042".freeze
- STR_WITH_BOM = "\uFEFF\u3042".freeze
- bug8940 = '[ruby-core:59757] [Bug #8940]'
- bug9415 = '[ruby-dev:47895] [Bug #9415]'
- %w/UTF-16 UTF-32/.each do |enc|
- %w/BE LE/.each do |endian|
- bom = "\uFEFF".encode("#{enc}#{endian}").force_encoding(enc)
-
- define_method("test_utf_16_32_inspect(#{enc}#{endian})") do
- s = STR_WITHOUT_BOM.encode(enc + endian)
- # When a UTF-16/32 string doesn't have a BOM,
- # inspect as a dummy encoding string.
- assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect,
- s.dup.force_encoding(enc).inspect)
- assert_normal_exit("#{bom.b.dump}.force_encoding('#{enc}').inspect", bug8940)
- end
- define_method("test_utf_16_32_codepoints(#{enc}#{endian})") do
- assert_equal([0xFEFF], bom.codepoints, bug9415)
+ 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
- define_method("test_utf_16_32_ord(#{enc}#{endian})") do
- assert_equal(0xFEFF, bom.ord, bug9415)
- end
+ def test_utf_dummy_are_like_regular_dummy_encodings
+ [Encoding::UTF_16, Encoding::UTF_32].each do |enc|
+ s = "\u3042".encode("UTF-32BE")
+ assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect, s.dup.force_encoding(enc).inspect)
+ s = "\x00\x00\xFE\xFF"
+ assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect, s.dup.force_encoding(enc).inspect)
- define_method("test_utf_16_32_inspect(#{enc}#{endian}-BOM)") do
- s = STR_WITH_BOM.encode(enc + endian)
- # When a UTF-16/32 string has a BOM,
- # inspect as a particular encoding string.
- assert_equal(s.inspect,
- s.dup.force_encoding(enc).inspect)
- end
+ assert_equal [0, 0, 254, 255], "\x00\x00\xFE\xFF".force_encoding(enc).codepoints
+ assert_equal 0, "\x00\x00\xFE\xFF".force_encoding(enc).ord
+ assert_equal 255, "\xFF\xFE\x00\x00".force_encoding(enc).ord
end
end
@@ -277,55 +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
@@ -365,7 +324,10 @@ class TestM17N < Test::Unit::TestCase
"\u3042".encode("UTF-16LE"),
"\u3042".encode("UTF-16BE"),
].each do |str|
- assert_equal(str, eval(str.dump), "[ruby-dev:33142]")
+ dump = str.dump
+ assert_equal(str, eval(dump), "[ruby-dev:33142]")
+ assert_equal(str, dump.undump)
+ assert_equal(str, eval("# frozen-string-literal: true\n#{dump}"), '[Bug #14687]')
end
end
@@ -612,6 +574,8 @@ class TestM17N < Test::Unit::TestCase
r1 = Regexp.new('\xa1'.force_encoding("ascii-8bit"))
r2 = eval('/\xa1#{r1}/'.force_encoding('ascii-8bit'))
assert_equal(Encoding::ASCII_8BIT, r2.encoding)
+
+ [r1, r2]
end
def test_regexp_named_class
@@ -883,10 +847,22 @@ class TestM17N < Test::Unit::TestCase
assert_raise(Encoding::CompatibilityError) {
"%s%s" % [s("\xc2\xa1"), e("\xc2\xa1")]
}
+
+ assert_equal("\u3042".encode('Windows-31J'), "%c" % "\u3042\u3044".encode('Windows-31J'))
end
def test_sprintf_p
Encoding.list.each do |e|
+ unless e.ascii_compatible?
+ format = e.dummy? ? "%p".force_encoding(e) : "%p".encode(e)
+ assert_raise(Encoding::CompatibilityError) do
+ sprintf(format, nil)
+ end
+ assert_raise(Encoding::CompatibilityError) do
+ format % nil
+ end
+ next
+ end
format = "%p".force_encoding(e)
['', 'a', "\xC2\xA1", "\x00"].each do |s|
s.force_encoding(e)
@@ -1091,7 +1067,23 @@ class TestM17N < Test::Unit::TestCase
assert_nil(e("\xa1\xa2\xa3\xa4").index(e("\xa3")))
assert_nil(e("\xa1\xa2\xa3\xa4").rindex(e("\xa3")))
s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4")
- assert_raise(Encoding::CompatibilityError){s.rindex(a("\xb1\xa3"))}
+
+ a_with_e = /EUC-JP and BINARY \(ASCII-8BIT\)/
+ assert_raise_with_message(Encoding::CompatibilityError, a_with_e) do
+ s.index(a("\xb1\xa3"))
+ end
+ assert_raise_with_message(Encoding::CompatibilityError, a_with_e) do
+ s.rindex(a("\xb1\xa3"))
+ end
+
+ a_with_e = /BINARY \(ASCII-8BIT\) regexp with EUC-JP string/
+ assert_raise_with_message(Encoding::CompatibilityError, a_with_e) do
+ s.index(Regexp.new(a("\xb1\xa3")))
+ end
+ assert_raise_with_message(Encoding::CompatibilityError, a_with_e) do
+ s.rindex(Regexp.new(a("\xb1\xa3")))
+ end
+
bug11488 = '[ruby-core:70592] [Bug #11488]'
each_encoding("abcdef", "def") do |str, substr|
assert_equal(3, str.index(substr), bug11488)
@@ -1319,10 +1311,14 @@ class TestM17N < Test::Unit::TestCase
end
def test_env
- locale_encoding = Encoding.find("locale")
+ if RUBY_PLATFORM =~ /bccwin|mswin|mingw/
+ env_encoding = Encoding::UTF_8
+ else
+ env_encoding = Encoding.find("locale")
+ end
ENV.each {|k, v|
- assert_equal(locale_encoding, k.encoding, k)
- assert_equal(locale_encoding, v.encoding, v)
+ assert_equal(env_encoding, k.encoding, proc {"key(#{k.encoding})=#{k.dump}"})
+ assert_equal(env_encoding, v.encoding, proc {"key(#{k.encoding})=#{k.dump}\n" "value(#{v.encoding})=#{v.dump}"})
}
end
@@ -1417,31 +1413,42 @@ class TestM17N < Test::Unit::TestCase
end
def test_regexp_usascii
- assert_regexp_usascii_literal('//', Encoding::US_ASCII)
- assert_regexp_usascii_literal('/#{ }/', Encoding::US_ASCII)
- assert_regexp_usascii_literal('/#{"a"}/', Encoding::US_ASCII)
- assert_regexp_usascii_literal('/#{%q"\x80"}/', Encoding::ASCII_8BIT)
- assert_regexp_usascii_literal('/#{"\x80"}/', nil, SyntaxError)
-
- assert_regexp_usascii_literal('/a/', Encoding::US_ASCII)
- assert_regexp_usascii_literal('/a#{ }/', Encoding::US_ASCII)
- assert_regexp_usascii_literal('/a#{"a"}/', Encoding::US_ASCII)
- assert_regexp_usascii_literal('/a#{%q"\x80"}/', Encoding::ASCII_8BIT)
- assert_regexp_usascii_literal('/a#{"\x80"}/', nil, SyntaxError)
-
- assert_regexp_usascii_literal('/\x80/', Encoding::ASCII_8BIT)
- assert_regexp_usascii_literal('/\x80#{ }/', Encoding::ASCII_8BIT)
- assert_regexp_usascii_literal('/\x80#{"a"}/', Encoding::ASCII_8BIT)
- assert_regexp_usascii_literal('/\x80#{%q"\x80"}/', Encoding::ASCII_8BIT)
- assert_regexp_usascii_literal('/\x80#{"\x80"}/', nil, SyntaxError)
-
- assert_regexp_usascii_literal('/\u1234/', Encoding::UTF_8)
- assert_regexp_usascii_literal('/\u1234#{ }/', Encoding::UTF_8)
- assert_regexp_usascii_literal('/\u1234#{"a"}/', Encoding::UTF_8)
- assert_regexp_usascii_literal('/\u1234#{%q"\x80"}/', nil, SyntaxError)
- assert_regexp_usascii_literal('/\u1234#{"\x80"}/', nil, SyntaxError)
- assert_regexp_usascii_literal('/\u1234\x80/', nil, SyntaxError)
- assert_regexp_usascii_literal('/\u1234#{ }\x80/', nil, RegexpError)
+ tests = [
+ [__LINE__, '//', Encoding::US_ASCII],
+ [__LINE__, '/#{ }/', Encoding::US_ASCII],
+ [__LINE__, '/#{"a"}/', Encoding::US_ASCII],
+ [__LINE__, '/#{%q"\x80"}/', Encoding::US_ASCII],
+ [__LINE__, '/#{"\x80"}/', Encoding::ASCII_8BIT],
+
+ [__LINE__, '/a/', Encoding::US_ASCII],
+ [__LINE__, '/a#{ }/', Encoding::US_ASCII],
+ [__LINE__, '/a#{"a"}/', Encoding::US_ASCII],
+ [__LINE__, '/a#{%q"\x80"}/', Encoding::ASCII_8BIT],
+ [__LINE__, '/a#{"\x80"}/', Encoding::ASCII_8BIT],
+
+ [__LINE__, '/\x80/', Encoding::ASCII_8BIT],
+ [__LINE__, '/\x80#{ }/', Encoding::ASCII_8BIT],
+ [__LINE__, '/\x80#{"a"}/', Encoding::ASCII_8BIT],
+ [__LINE__, '/\x80#{%q"\x80"}/', Encoding::ASCII_8BIT],
+ [__LINE__, '/\x80#{"\x80"}/', Encoding::ASCII_8BIT],
+
+ [__LINE__, '/\u1234/', Encoding::UTF_8],
+ [__LINE__, '/\u1234#{ }/', Encoding::UTF_8],
+ [__LINE__, '/\u1234#{"a"}/', Encoding::UTF_8],
+
+ [__LINE__, '/\u1234#{%q"\x80"}/', nil, SyntaxError],
+ [__LINE__, '/\u1234#{"\x80"}/', nil, SyntaxError],
+ [__LINE__, '/\u1234\x80/', nil, SyntaxError],
+ [__LINE__, '/\u1234#{ }\x80/', nil, RegexpError],
+ ]
+ all_assertions_foreach(nil, *tests) do |line, r, enc, ex|
+ code = "# -*- encoding: US-ASCII -*-\n#{r}.encoding"
+ if ex
+ assert_raise(ex) {eval(code, nil, __FILE__, line-1)}
+ else
+ assert_equal(enc, eval(code, nil, __FILE__, line-1))
+ end
+ end
end
def test_gbk
@@ -1528,6 +1535,17 @@ class TestM17N < Test::Unit::TestCase
}
end
+ def test_setbyte_range
+ s = u("\xE3\x81\x82\xE3\x81\x84")
+ assert_nothing_raised { s.setbyte(0, -1) }
+ assert_nothing_raised { s.setbyte(0, 0x00) }
+ assert_nothing_raised { s.setbyte(0, 0x7F) }
+ assert_nothing_raised { s.setbyte(0, 0x80) }
+ assert_nothing_raised { s.setbyte(0, 0xff) }
+ assert_nothing_raised { s.setbyte(0, 0x100) }
+ assert_nothing_raised { s.setbyte(0, 0x4f7574206f6620636861722072616e6765) }
+ end
+
def test_compatible
assert_nil Encoding.compatible?("",0)
assert_equal(Encoding::UTF_8, Encoding.compatible?(u(""), ua("abc")))
@@ -1566,8 +1584,6 @@ class TestM17N < Test::Unit::TestCase
s = "\u3042"
assert_equal(a("\xE3\x81\x82"), s.b)
assert_equal(Encoding::ASCII_8BIT, s.b.encoding)
- s.taint
- assert_predicate(s.b, :tainted?)
s = "abc".b
assert_predicate(s.b, :ascii_only?)
end
@@ -1576,23 +1592,31 @@ class TestM17N < Test::Unit::TestCase
str = "foo"
assert_equal(str, str.scrub)
assert_not_same(str, str.scrub)
- assert_predicate(str.dup.taint.scrub, :tainted?)
str = "\u3042\u3044"
assert_equal(str, str.scrub)
assert_not_same(str, str.scrub)
- assert_predicate(str.dup.taint.scrub, :tainted?)
str.force_encoding(Encoding::ISO_2022_JP) # dummy encoding
assert_equal(str, str.scrub)
assert_not_same(str, str.scrub)
assert_nothing_raised(ArgumentError) {str.scrub(nil)}
- assert_predicate(str.dup.taint.scrub, :tainted?)
+ end
+
+ def test_scrub_modification_inside_block
+ str = ("abc\u3042".b << "\xE3\x80".b).force_encoding('UTF-8')
+ assert_raise(RuntimeError) {str.scrub{|_| str << "1234567890"; "?" }}
+
+ str = "\x00\xD8\x42\x30".force_encoding(Encoding::UTF_16LE)
+ assert_raise(RuntimeError) do
+ str.scrub do |_|
+ str << "1\x002\x00".force_encoding('UTF-16LE')
+ "?\x00".force_encoding('UTF-16LE')
+ end
+ end
end
def test_scrub_replace_default
assert_equal("\uFFFD\uFFFD\uFFFD", u("\x80\x80\x80").scrub)
assert_equal("\uFFFDA", u("\xF4\x80\x80A").scrub)
- assert_predicate(u("\x80\x80\x80").taint.scrub, :tainted?)
- assert_predicate(u("\xF4\x80\x80A").taint.scrub, :tainted?)
# examples in Unicode 6.1.0 D93b
assert_equal("\x41\uFFFD\uFFFD\x41\uFFFD\x41",
@@ -1607,14 +1631,8 @@ class TestM17N < Test::Unit::TestCase
def test_scrub_replace_argument
assert_equal("foo", u("foo").scrub("\u3013"))
- assert_predicate(u("foo").taint.scrub("\u3013"), :tainted?)
- assert_not_predicate(u("foo").scrub("\u3013".taint), :tainted?)
assert_equal("\u3042\u3044", u("\xE3\x81\x82\xE3\x81\x84").scrub("\u3013"))
- assert_predicate(u("\xE3\x81\x82\xE3\x81\x84").taint.scrub("\u3013"), :tainted?)
- assert_not_predicate(u("\xE3\x81\x82\xE3\x81\x84").scrub("\u3013".taint), :tainted?)
assert_equal("\u3042\u3013", u("\xE3\x81\x82\xE3\x81").scrub("\u3013"))
- assert_predicate(u("\xE3\x81\x82\xE3\x81").taint.scrub("\u3013"), :tainted?)
- assert_predicate(u("\xE3\x81\x82\xE3\x81").scrub("\u3013".taint), :tainted?)
assert_raise(Encoding::CompatibilityError){ u("\xE3\x81\x82\xE3\x81").scrub(e("\xA4\xA2")) }
assert_raise(TypeError){ u("\xE3\x81\x82\xE3\x81").scrub(1) }
assert_raise(ArgumentError){ u("\xE3\x81\x82\xE3\x81\x82\xE3\x81").scrub(u("\x81")) }
@@ -1623,8 +1641,6 @@ class TestM17N < Test::Unit::TestCase
def test_scrub_replace_block
assert_equal("\u3042<e381>", u("\xE3\x81\x82\xE3\x81").scrub{|x|'<'+x.unpack('H*')[0]+'>'})
- assert_predicate(u("\xE3\x81\x82\xE3\x81").taint.scrub{|x|'<'+x.unpack('H*')[0]+'>'}, :tainted?)
- assert_predicate(u("\xE3\x81\x82\xE3\x81").scrub{|x|('<'+x.unpack('H*')[0]+'>').taint}, :tainted?)
assert_raise(Encoding::CompatibilityError){ u("\xE3\x81\x82\xE3\x81").scrub{e("\xA4\xA2")} }
assert_raise(TypeError){ u("\xE3\x81\x82\xE3\x81").scrub{1} }
assert_raise(ArgumentError){ u("\xE3\x81\x82\xE3\x81\x82\xE3\x81").scrub{u("\x81")} }
@@ -1693,6 +1709,23 @@ class TestM17N < Test::Unit::TestCase
assert_equal(e("[\"\xB4\xC1\xBB\xFA\"]"), s, bug11787)
end
+ def test_encoding_names_of_default_internal
+ # [Bug #20595] [Bug #20598]
+ [
+ "default_internal.names",
+ "name_list",
+ "aliases.keys"
+ ].each do |method|
+ assert_separately(%w(-W0), <<~RUBY)
+ exp_name = "int" + "ernal"
+ Encoding.default_internal = Encoding::ASCII_8BIT
+ name = Encoding.#{method}.find { |x| x == exp_name }
+ Encoding.default_internal = nil
+ assert_equal exp_name, name, "Encoding.#{method} [Bug #20595] [Bug #20598]"
+ RUBY
+ end
+ end
+
def test_greek_capital_gap
bug12204 = '[ruby-core:74478] [Bug #12204] GREEK CAPITAL RHO and SIGMA'
assert_equal("\u03A3", "\u03A1".succ, bug12204)
diff --git a/test/ruby/test_m17n_comb.rb b/test/ruby/test_m17n_comb.rb
index 99c162a92f..e48a1948be 100644
--- a/test/ruby/test_m17n_comb.rb
+++ b/test/ruby/test_m17n_comb.rb
@@ -593,6 +593,21 @@ class TestM17NComb < Test::Unit::TestCase
}
end
+ def test_str_casecmp?
+ strings = STRINGS.dup
+ strings.push(
+ # prevent wrong single byte optimization
+ "\xC0".force_encoding("ISO-8859-1"),
+ "\xE0".force_encoding("ISO-8859-1"),
+ )
+ combination(strings, strings) {|s1, s2|
+ #puts "#{encdump(s1)}.casecmp(#{encdump(s2)})"
+ next unless s1.valid_encoding? && s2.valid_encoding? && Encoding.compatible?(s1, s2)
+ r = s1.casecmp?(s2)
+ assert_equal(s1.downcase(:fold) == s2.downcase(:fold), r)
+ }
+ end
+
def test_str_center
combination(STRINGS, [0,1,2,3,10]) {|s1, width|
t = s1.center(width)
@@ -744,11 +759,21 @@ class TestM17NComb < Test::Unit::TestCase
}
end
+ def crypt_supports_des_crypt?
+ /openbsd/ !~ RUBY_PLATFORM
+ end
+
# glibc 2.16 or later denies salt contained other than [0-9A-Za-z./] #7312
# we use this check to test strict and non-strict behavior separately #11045
strict_crypt = if defined? Etc::CS_GNU_LIBC_VERSION
- glibcver = Etc.confstr(Etc::CS_GNU_LIBC_VERSION).scan(/\d+/).map(&:to_i)
- (glibcver <=> [2, 16]) >= 0
+ begin
+ confstr = Etc.confstr(Etc::CS_GNU_LIBC_VERSION)
+ rescue Errno::EINVAL
+ false
+ else
+ glibcver = confstr.scan(/\d+/).map(&:to_i)
+ (glibcver <=> [2, 16]) >= 0
+ end
end
def test_str_crypt
@@ -760,7 +785,7 @@ class TestM17NComb < Test::Unit::TestCase
}
end
- if !strict_crypt
+ if !strict_crypt && /openbsd/ !~ RUBY_PLATFORM
def test_str_crypt_nonstrict
combination(STRINGS, STRINGS) {|str, salt|
# only test input other than [0-9A-Za-z./] to confirm non-strict behavior
@@ -772,9 +797,14 @@ class TestM17NComb < Test::Unit::TestCase
end
private def confirm_crypt_result(str, salt)
- if b(salt).length < 2
- assert_raise(ArgumentError) { str.crypt(salt) }
- return
+ if crypt_supports_des_crypt?
+ if b(salt).length < 2
+ assert_raise(ArgumentError) { str.crypt(salt) }
+ return
+ end
+ else
+ return if b(salt).length < 2
+ salt = "$2a$04$0WVaz0pV3jzfZ5G5tpmH#{salt}"
end
t = str.crypt(salt)
assert_equal(b(str).crypt(b(salt)), t, "#{encdump(str)}.crypt(#{encdump(salt)})")
diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb
index 0565a1c04f..eb66994801 100644
--- a/test/ruby/test_marshal.rb
+++ b/test/ruby/test_marshal.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: false
require 'test/unit'
-require 'tempfile'
require_relative 'marshaltestlib'
class TestMarshal < Test::Unit::TestCase
@@ -8,7 +7,6 @@ class TestMarshal < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
end
def teardown
@@ -34,7 +32,7 @@ class TestMarshal < Test::Unit::TestCase
end
def test_marshal
- a = [1, 2, 3, [4,5,"foo"], {1=>"bar"}, 2.5, fact(30)]
+ a = [1, 2, 3, 2**32, 2**64, [4,5,"foo"], {1=>"bar"}, 2.5, fact(30)]
assert_equal a, Marshal.load(Marshal.dump(a))
[[1,2,3,4], [81, 2, 118, 3146]].each { |w,x,y,z|
@@ -48,6 +46,26 @@ class TestMarshal < Test::Unit::TestCase
}
end
+ def test_marshal_integers
+ a = []
+ [-2, -1, 0, 1, 2].each do |i|
+ 0.upto(65).map do |exp|
+ a << 2**exp + i
+ end
+ end
+ assert_equal a, Marshal.load(Marshal.dump(a))
+
+ a = [2**32, []]*2
+ assert_equal a, Marshal.load(Marshal.dump(a))
+
+ a = [2**32, 2**32, []]*2
+ assert_equal a, Marshal.load(Marshal.dump(a))
+ end
+
+ def test_marshal_small_bignum_backref
+ assert_equal [2**32, 2**32], Marshal.load("\x04\b[\al+\b\x00\x00\x00\x00\x01\x00@\x06")
+ end
+
StrClone = String.clone
def test_marshal_cloned_class
assert_instance_of(StrClone, Marshal.load(Marshal.dump(StrClone.new("abc"))))
@@ -59,6 +77,8 @@ class TestMarshal < Test::Unit::TestCase
TestMarshal.instance_eval { remove_const :StructOrNot }
TestMarshal.const_set :StructOrNot, Class.new
assert_raise(TypeError, "[ruby-dev:31709]") { Marshal.load(s) }
+ ensure
+ TestMarshal.instance_eval { remove_const :StructOrNot }
end
def test_struct_invalid_members
@@ -67,6 +87,16 @@ class TestMarshal < Test::Unit::TestCase
Marshal.load("\004\bIc&TestMarshal::StructInvalidMembers\006:\020__members__\"\bfoo")
TestMarshal::StructInvalidMembers.members
}
+ ensure
+ TestMarshal.instance_eval { remove_const :StructInvalidMembers }
+ end
+
+ def test_load_range_as_struct
+ assert_raise(TypeError, 'GH-6832') do
+ # Can be obtained with:
+ # $ ruby -e 'Range = Struct.new(:a, :b, :c); p Marshal.dump(Range.new(nil, nil, nil))'
+ Marshal.load("\x04\bS:\nRange\b:\x06a0:\x06b0:\x06c0")
+ end
end
class C
@@ -157,20 +187,29 @@ class TestMarshal < Test::Unit::TestCase
end
def test_change_class_name
+ self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3)
eval("class C3; def _dump(s); 'foo'; end; end")
m = Marshal.dump(C3.new)
assert_raise(TypeError) { Marshal.load(m) }
+ self.class.__send__(:remove_const, :C3)
eval("C3 = nil")
assert_raise(TypeError) { Marshal.load(m) }
+ ensure
+ self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3)
end
def test_change_struct
+ self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3)
eval("C3 = Struct.new(:foo, :bar)")
m = Marshal.dump(C3.new("FOO", "BAR"))
+ self.class.__send__(:remove_const, :C3)
eval("C3 = Struct.new(:foo)")
assert_raise(TypeError) { Marshal.load(m) }
+ self.class.__send__(:remove_const, :C3)
eval("C3 = Struct.new(:foo, :baz)")
assert_raise(TypeError) { Marshal.load(m) }
+ ensure
+ self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3)
end
class C4
@@ -189,57 +228,6 @@ class TestMarshal < Test::Unit::TestCase
end
end
- def test_taint
- x = Object.new
- x.taint
- s = Marshal.dump(x)
- assert_equal(true, s.tainted?)
- y = Marshal.load(s)
- assert_equal(true, y.tainted?)
- end
-
- def test_taint_each_object
- x = Object.new
- obj = [[x]]
-
- # clean object causes crean stream
- assert_equal(false, obj.tainted?)
- assert_equal(false, obj.first.tainted?)
- assert_equal(false, obj.first.first.tainted?)
- s = Marshal.dump(obj)
- assert_equal(false, s.tainted?)
-
- # tainted object causes tainted stream
- x.taint
- assert_equal(false, obj.tainted?)
- assert_equal(false, obj.first.tainted?)
- assert_equal(true, obj.first.first.tainted?)
- t = Marshal.dump(obj)
- assert_equal(true, t.tainted?)
-
- # clean stream causes clean objects
- assert_equal(false, s.tainted?)
- y = Marshal.load(s)
- assert_equal(false, y.tainted?)
- assert_equal(false, y.first.tainted?)
- assert_equal(false, y.first.first.tainted?)
-
- # tainted stream causes tainted objects
- assert_equal(true, t.tainted?)
- y = Marshal.load(t)
- assert_equal(true, y.tainted?)
- assert_equal(true, y.first.tainted?)
- assert_equal(true, y.first.first.tainted?)
-
- # same tests by different senario
- s.taint
- assert_equal(true, s.tainted?)
- y = Marshal.load(s)
- assert_equal(true, y.tainted?)
- assert_equal(true, y.first.tainted?)
- assert_equal(true, y.first.first.tainted?)
- end
-
def test_symbol2
[:ruby, :"\u{7d05}\u{7389}"].each do |sym|
assert_equal(sym, Marshal.load(Marshal.dump(sym)), '[ruby-core:24788]')
@@ -280,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]')
@@ -313,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"))
@@ -325,11 +323,10 @@ class TestMarshal < Test::Unit::TestCase
assert_equal(c, Marshal.load(Marshal.dump(c)), bug2109)
assert_nothing_raised(ArgumentError, '[ruby-dev:40386]') do
- re = Tempfile.create("marshal_regexp") do |f|
- f.binmode.write("\x04\bI/\x00\x00\x06:\rencoding\"\rUS-ASCII")
- f.rewind
- re2 = Marshal.load(f)
- re2
+ re = IO.pipe do |r, w|
+ w.write("\x04\bI/\x00\x00\x06:\rencoding\"\rUS-ASCII")
+ # Marshal.load would not overread and block
+ Marshal.load(r)
end
assert_equal(//, re)
end
@@ -472,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")}
@@ -499,16 +520,6 @@ class TestMarshal < Test::Unit::TestCase
module TestModule
end
- def test_marshal_load_should_not_taint_classes
- bug7325 = '[ruby-core:49198]'
- for c in [TestClass, TestModule]
- assert_not_predicate(c, :tainted?)
- c2 = Marshal.load(Marshal.dump(c).taint)
- assert_same(c, c2)
- assert_not_predicate(c, :tainted?, bug7325)
- end
- end
-
class Bug7627 < Struct.new(:bar)
attr_accessor :foo
@@ -593,17 +604,23 @@ class TestMarshal < Test::Unit::TestCase
def test_class_ivar
assert_raise(TypeError) {Marshal.load("\x04\x08Ic\x1bTestMarshal::TestClass\x06:\x0e@ivar_bug\"\x08bug")}
assert_raise(TypeError) {Marshal.load("\x04\x08IM\x1bTestMarshal::TestClass\x06:\x0e@ivar_bug\"\x08bug")}
- assert_not_operator(TestClass, :instance_variable_defined?, :@bug)
+ assert_not_operator(TestClass, :instance_variable_defined?, :@ivar_bug)
+
+ assert_raise(TypeError) {Marshal.load("\x04\x08[\x07c\x1bTestMarshal::TestClassI@\x06\x06:\x0e@ivar_bug\"\x08bug")}
+ assert_not_operator(TestClass, :instance_variable_defined?, :@ivar_bug)
end
def test_module_ivar
assert_raise(TypeError) {Marshal.load("\x04\x08Im\x1cTestMarshal::TestModule\x06:\x0e@ivar_bug\"\x08bug")}
assert_raise(TypeError) {Marshal.load("\x04\x08IM\x1cTestMarshal::TestModule\x06:\x0e@ivar_bug\"\x08bug")}
- assert_not_operator(TestModule, :instance_variable_defined?, :@bug)
+ assert_not_operator(TestModule, :instance_variable_defined?, :@ivar_bug)
+
+ assert_raise(TypeError) {Marshal.load("\x04\x08[\x07m\x1cTestMarshal::TestModuleI@\x06\x06:\x0e@ivar_bug\"\x08bug")}
+ assert_not_operator(TestModule, :instance_variable_defined?, :@ivar_bug)
end
class TestForRespondToFalse
- def respond_to?(a)
+ def respond_to?(a, priv = false)
false
end
end
@@ -620,15 +637,6 @@ class TestMarshal < Test::Unit::TestCase
assert_equal(Marshal.dump(bare), Marshal.dump(packed))
end
- def test_untainted_numeric
- bug8945 = '[ruby-core:57346] [Bug #8945] Numerics never be tainted'
- b = RbConfig::LIMITS['FIXNUM_MAX'] + 1
- tainted = [0, 1.0, 1.72723e-77, b].select do |x|
- Marshal.load(Marshal.dump(x).taint).tainted?
- end
- assert_empty(tainted.map {|x| [x, x.class]}, bug8945)
- end
-
class Bug9523
attr_reader :cc
def marshal_dump
@@ -640,7 +648,9 @@ class TestMarshal < Test::Unit::TestCase
end
def test_continuation
- require "continuation"
+ EnvUtil.suppress_warning {require "continuation"}
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
c = Bug9523.new
assert_raise_with_message(RuntimeError, /Marshal\.dump reentered at marshal_dump/) do
Marshal.dump(c)
@@ -666,7 +676,8 @@ class TestMarshal < Test::Unit::TestCase
end
def test_unloadable_data
- c = eval("class Unloadable\u{23F0 23F3}<Time;;self;end")
+ name = "Unloadable\u{23F0 23F3}"
+ c = eval("class #{name} < Time;;self;end")
c.class_eval {
alias _dump_data _dump
undef _dump
@@ -675,10 +686,16 @@ class TestMarshal < Test::Unit::TestCase
assert_raise_with_message(TypeError, /Unloadable\u{23F0 23F3}/) {
Marshal.load(d)
}
+
+ ensure
+ self.class.class_eval do
+ remove_const name
+ end if c
end
def test_unloadable_userdef
- c = eval("class Userdef\u{23F0 23F3}<Time;self;end")
+ name = "Userdef\u{23F0 23F3}"
+ c = eval("class #{name} < Time;self;end")
class << c
undef _load
end
@@ -686,6 +703,19 @@ class TestMarshal < Test::Unit::TestCase
assert_raise_with_message(TypeError, /Userdef\u{23F0 23F3}/) {
Marshal.load(d)
}
+
+ ensure
+ self.class.class_eval do
+ remove_const name
+ end if c
+ end
+
+ def test_recursive_userdef
+ t = Time.utc(0)
+ t.instance_eval {@v = t}
+ assert_raise_with_message(RuntimeError, /recursive\b.*\b_dump/) do
+ Marshal.dump(t)
+ end
end
def test_unloadable_usrmarshal
@@ -701,15 +731,16 @@ class TestMarshal < Test::Unit::TestCase
def test_no_internal_ids
opt = %w[--disable=gems]
- args = [opt, 'Marshal.dump("",STDOUT)', true, true, encoding: Encoding::ASCII_8BIT]
- out, err, status = EnvUtil.invoke_ruby(*args)
+ args = [opt, 'Marshal.dump("",STDOUT)', true, true]
+ kw = {encoding: Encoding::ASCII_8BIT}
+ out, err, status = EnvUtil.invoke_ruby(*args, **kw)
assert_empty(err)
assert_predicate(status, :success?)
expected = out
opt << "--enable=frozen-string-literal"
opt << "--debug=frozen-string-literal"
- out, err, status = EnvUtil.invoke_ruby(*args)
+ out, err, status = EnvUtil.invoke_ruby(*args, **kw)
assert_empty(err)
assert_predicate(status, :success?)
assert_equal(expected, out)
@@ -721,6 +752,23 @@ class TestMarshal < Test::Unit::TestCase
assert_equal(['X', 'X'], Marshal.load(Marshal.dump(obj), ->(v) { v == str ? v.upcase : v }))
end
+ def test_marshal_proc_string_encoding
+ string = "foo"
+ payload = Marshal.dump(string)
+ Marshal.load(payload, ->(v) {
+ if v.is_a?(String)
+ assert_equal(string, v)
+ assert_equal(string.encoding, v.encoding)
+ end
+ v
+ })
+ end
+
+ def test_marshal_proc_freeze
+ object = { foo: [42, "bar"] }
+ assert_equal object, Marshal.load(Marshal.dump(object), :freeze.to_proc)
+ end
+
def test_marshal_load_extended_class_crash
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
@@ -795,7 +843,11 @@ class TestMarshal < Test::Unit::TestCase
end
def marshal_dump
- self.foo.baz = :problem
+ if self.foo.baz
+ self.foo.remove_instance_variable(:@baz)
+ else
+ self.foo.baz = :problem
+ end
{foo: self.foo}
end
@@ -811,4 +863,144 @@ class TestMarshal < Test::Unit::TestCase
Marshal.dump(obj)
end
end
+
+ def test_marshal_dump_removing_instance_variable
+ obj = Bug15968.new
+ obj.baz = :Bug15968
+ assert_raise_with_message(RuntimeError, /instance variable removed/) do
+ Marshal.dump(obj)
+ end
+ end
+
+ ruby2_keywords def ruby2_keywords_hash(*a)
+ a.last
+ end
+
+ def ruby2_keywords_test(key: 1)
+ key
+ end
+
+ def test_marshal_with_ruby2_keywords_hash
+ flagged_hash = ruby2_keywords_hash(key: 42)
+ data = Marshal.dump(flagged_hash)
+ hash = Marshal.load(data)
+ assert_equal(42, ruby2_keywords_test(*[hash]))
+
+ hash2 = Marshal.load(data.sub(/\x06K(?=T\z)/, "\x08KEY"))
+ assert_raise(ArgumentError, /\(given 1, expected 0\)/) {
+ ruby2_keywords_test(*[hash2])
+ }
+ hash2 = Marshal.load(data.sub(/:\x06K(?=T\z)/, "I\\&\x06:\x0dencoding\"\x0aUTF-7"))
+ assert_raise(ArgumentError, /\(given 1, expected 0\)/) {
+ ruby2_keywords_test(*[hash2])
+ }
+ end
+
+ def test_invalid_byte_sequence_symbol
+ data = Marshal.dump(:K)
+ data = data.sub(/:\x06K/, "I\\&\x06:\x0dencoding\"\x0dUTF-16LE")
+ assert_raise(ArgumentError, /UTF-16LE: "\\x4B"/) {
+ Marshal.load(data)
+ }
+ end
+
+ def exception_test
+ raise
+ end
+
+ def test_marshal_exception
+ begin
+ exception_test
+ rescue => e
+ e2 = Marshal.load(Marshal.dump(e))
+ assert_equal(e.message, e2.message)
+ assert_equal(e.backtrace, e2.backtrace)
+ assert_nil(e2.backtrace_locations) # temporal
+ end
+ end
+
+ def nameerror_test
+ unknown_method
+ end
+
+ def test_marshal_nameerror
+ begin
+ nameerror_test
+ rescue NameError => e
+ e2 = Marshal.load(Marshal.dump(e))
+ assert_equal(e.message.lines.first.chomp, e2.message.lines.first)
+ assert_equal(e.name, e2.name)
+ assert_equal(e.backtrace, e2.backtrace)
+ assert_nil(e2.backtrace_locations) # temporal
+ end
+ end
+
+ class TestMarshalFreezeProc < Test::Unit::TestCase
+ include MarshalTestLib
+
+ def encode(o)
+ Marshal.dump(o)
+ end
+
+ def decode(s)
+ Marshal.load(s, :freeze.to_proc)
+ end
+ end
+
+ def _test_hash_compared_by_identity(h)
+ h.compare_by_identity
+ h["a" + "0"] = 1
+ h["a" + "0"] = 2
+ h = Marshal.load(Marshal.dump(h))
+ assert_predicate(h, :compare_by_identity?)
+ a = h.to_a
+ assert_equal([["a0", 1], ["a0", 2]], a.sort)
+ assert_not_same(a[1][0], a[0][0])
+ end
+
+ def test_hash_compared_by_identity
+ _test_hash_compared_by_identity(Hash.new)
+ end
+
+ def test_hash_default_compared_by_identity
+ _test_hash_compared_by_identity(Hash.new(true))
+ end
+
+ class TestMarshalFreeze < Test::Unit::TestCase
+ include MarshalTestLib
+
+ def encode(o)
+ Marshal.dump(o)
+ end
+
+ def decode(s)
+ Marshal.load(s, freeze: true)
+ end
+
+ def test_return_objects_are_frozen
+ source = ["foo", {}, /foo/, 1..2]
+ objects = decode(encode(source))
+ assert_equal source, objects
+ assert_predicate objects, :frozen?
+ objects.each do |obj|
+ assert_predicate obj, :frozen?
+ end
+ end
+
+ def test_proc_returned_object_are_not_frozen
+ source = ["foo", {}, /foo/, 1..2]
+ objects = Marshal.load(encode(source), ->(o) { o.dup }, freeze: true)
+ assert_equal source, objects
+ refute_predicate objects, :frozen?
+ objects.each do |obj|
+ refute_predicate obj, :frozen?
+ end
+ end
+
+ def test_modules_and_classes_are_not_frozen
+ _objects = Marshal.load(encode([Object, Kernel]), freeze: true)
+ refute_predicate Object, :frozen?
+ refute_predicate Kernel, :frozen?
+ end
+ end
end
diff --git a/test/ruby/test_math.rb b/test/ruby/test_math.rb
index f226287442..e134600cc4 100644
--- a/test/ruby/test_math.rb
+++ b/test/ruby/test_math.rb
@@ -5,6 +5,7 @@ class TestMath < Test::Unit::TestCase
def assert_infinity(a, *rest)
rest = ["not infinity: #{a.inspect}"] if rest.empty?
assert_predicate(a, :infinite?, *rest)
+ assert_predicate(a, :positive?, *rest)
end
def assert_nan(a, *rest)
@@ -73,9 +74,9 @@ class TestMath < Test::Unit::TestCase
check(1 * Math::PI / 4, Math.acos( 1.0 / Math.sqrt(2)))
check(2 * Math::PI / 4, Math.acos( 0.0))
check(4 * Math::PI / 4, Math.acos(-1.0))
- assert_raise(Math::DomainError) { Math.acos(+1.0 + Float::EPSILON) }
- assert_raise(Math::DomainError) { Math.acos(-1.0 - Float::EPSILON) }
- assert_raise(Math::DomainError) { Math.acos(2.0) }
+ assert_raise_with_message(Math::DomainError, /\bacos\b/) { Math.acos(+1.0 + Float::EPSILON) }
+ assert_raise_with_message(Math::DomainError, /\bacos\b/) { Math.acos(-1.0 - Float::EPSILON) }
+ assert_raise_with_message(Math::DomainError, /\bacos\b/) { Math.acos(2.0) }
end
def test_asin
@@ -83,9 +84,9 @@ class TestMath < Test::Unit::TestCase
check( 1 * Math::PI / 4, Math.asin( 1.0 / Math.sqrt(2)))
check( 2 * Math::PI / 4, Math.asin( 1.0))
check(-2 * Math::PI / 4, Math.asin(-1.0))
- assert_raise(Math::DomainError) { Math.asin(+1.0 + Float::EPSILON) }
- assert_raise(Math::DomainError) { Math.asin(-1.0 - Float::EPSILON) }
- assert_raise(Math::DomainError) { Math.asin(2.0) }
+ assert_raise_with_message(Math::DomainError, /\basin\b/) { Math.asin(+1.0 + Float::EPSILON) }
+ assert_raise_with_message(Math::DomainError, /\basin\b/) { Math.asin(-1.0 - Float::EPSILON) }
+ assert_raise_with_message(Math::DomainError, /\basin\b/) { Math.asin(2.0) }
end
def test_atan
@@ -119,8 +120,8 @@ class TestMath < Test::Unit::TestCase
check(0, Math.acosh(1))
check(1, Math.acosh((Math::E ** 1 + Math::E ** -1) / 2))
check(2, Math.acosh((Math::E ** 2 + Math::E ** -2) / 2))
- assert_raise(Math::DomainError) { Math.acosh(1.0 - Float::EPSILON) }
- assert_raise(Math::DomainError) { Math.acosh(0) }
+ assert_raise_with_message(Math::DomainError, /\bacosh\b/) { Math.acosh(1.0 - Float::EPSILON) }
+ assert_raise_with_message(Math::DomainError, /\bacosh\b/) { Math.acosh(0) }
end
def test_asinh
@@ -135,8 +136,8 @@ class TestMath < Test::Unit::TestCase
check(2, Math.atanh(Math.sinh(2) / Math.cosh(2)))
assert_nothing_raised { assert_infinity(Math.atanh(1)) }
assert_nothing_raised { assert_infinity(-Math.atanh(-1)) }
- assert_raise(Math::DomainError) { Math.atanh(+1.0 + Float::EPSILON) }
- assert_raise(Math::DomainError) { Math.atanh(-1.0 - Float::EPSILON) }
+ assert_raise_with_message(Math::DomainError, /\batanh\b/) { Math.atanh(+1.0 + Float::EPSILON) }
+ assert_raise_with_message(Math::DomainError, /\batanh\b/) { Math.atanh(-1.0 - Float::EPSILON) }
end
def test_exp
@@ -146,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))
@@ -157,10 +165,17 @@ class TestMath < Test::Unit::TestCase
assert_nothing_raised { assert_infinity(Math.log(1.0/0)) }
assert_nothing_raised { assert_infinity(-Math.log(+0.0)) }
assert_nothing_raised { assert_infinity(-Math.log(-0.0)) }
- assert_raise(Math::DomainError) { Math.log(-1.0) }
+ assert_raise_with_message(Math::DomainError, /\blog\b/) { Math.log(-1.0) }
+ assert_raise_with_message(Math::DomainError, /\blog\b/) { Math.log(-Float::EPSILON) }
assert_raise(TypeError) { Math.log(1,nil) }
- assert_raise(Math::DomainError, '[ruby-core:62309] [ruby-Bug #9797]') { Math.log(1.0, -1.0) }
+ assert_raise_with_message(Math::DomainError, /\blog\b/, '[ruby-core:62309] [ruby-Bug #9797]') { Math.log(1.0, -1.0) }
+ assert_raise_with_message(Math::DomainError, /\blog\b/) { Math.log(1.0, -Float::EPSILON) }
assert_nothing_raised { assert_nan(Math.log(0.0, 0.0)) }
+ assert_nothing_raised { assert_nan(Math.log(Float::NAN)) }
+ assert_nothing_raised { assert_nan(Math.log(1.0, Float::NAN)) }
+ assert_nothing_raised { assert_infinity(-Math.log(0)) }
+ assert_nothing_raised { assert_infinity(-Math.log(0, 2)) }
+ check(307.95368556425274, Math.log(2**1023, 10))
end
def test_log2
@@ -172,7 +187,10 @@ class TestMath < Test::Unit::TestCase
assert_nothing_raised { assert_infinity(Math.log2(1.0/0)) }
assert_nothing_raised { assert_infinity(-Math.log2(+0.0)) }
assert_nothing_raised { assert_infinity(-Math.log2(-0.0)) }
- assert_raise(Math::DomainError) { Math.log2(-1.0) }
+ assert_raise_with_message(Math::DomainError, /\blog2\b/) { Math.log2(-1.0) }
+ assert_raise_with_message(Math::DomainError, /\blog2\b/) { Math.log2(-Float::EPSILON) }
+ assert_nothing_raised { assert_nan(Math.log2(Float::NAN)) }
+ assert_nothing_raised { assert_infinity(-Math.log2(0)) }
end
def test_log10
@@ -184,7 +202,23 @@ class TestMath < Test::Unit::TestCase
assert_nothing_raised { assert_infinity(Math.log10(1.0/0)) }
assert_nothing_raised { assert_infinity(-Math.log10(+0.0)) }
assert_nothing_raised { assert_infinity(-Math.log10(-0.0)) }
- assert_raise(Math::DomainError) { Math.log10(-1.0) }
+ assert_raise_with_message(Math::DomainError, /\blog10\b/) { Math.log10(-1.0) }
+ assert_raise_with_message(Math::DomainError, /\blog10\b/) { Math.log10(-Float::EPSILON) }
+ assert_nothing_raised { assert_nan(Math.log10(Float::NAN)) }
+ 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
@@ -193,7 +227,9 @@ class TestMath < Test::Unit::TestCase
check(2, Math.sqrt(4))
assert_nothing_raised { assert_infinity(Math.sqrt(1.0/0)) }
assert_equal("0.0", Math.sqrt(-0.0).to_s) # insure it is +0.0, not -0.0
- assert_raise(Math::DomainError) { Math.sqrt(-1.0) }
+ assert_raise_with_message(Math::DomainError, /\bsqrt\b/) { Math.sqrt(-1.0) }
+ assert_raise_with_message(Math::DomainError, /\bsqrt\b/) { Math.sqrt(-Float::EPSILON) }
+ assert_nothing_raised { assert_nan(Math.sqrt(Float::NAN)) }
end
def test_cbrt
@@ -201,7 +237,11 @@ class TestMath < Test::Unit::TestCase
check(-2, Math.cbrt(-8))
check(3, Math.cbrt(27))
check(-0.1, Math.cbrt(-0.001))
+ check(0.0, Math.cbrt(0.0))
assert_nothing_raised { assert_infinity(Math.cbrt(1.0/0)) }
+ assert_operator(Math.cbrt(1.0 - Float::EPSILON), :<=, 1.0)
+ assert_nothing_raised { assert_nan(Math.sqrt(Float::NAN)) }
+ assert_nothing_raised { assert_nan(Math.cbrt(Float::NAN)) }
end
def test_frexp
@@ -210,6 +250,7 @@ class TestMath < Test::Unit::TestCase
assert_float_and_int([0.5, 1], Math.frexp(1.0))
assert_float_and_int([0.5, 2], Math.frexp(2.0))
assert_float_and_int([0.75, 2], Math.frexp(3.0))
+ assert_nan(Math.frexp(Float::NAN)[0])
end
def test_ldexp
@@ -227,11 +268,13 @@ class TestMath < Test::Unit::TestCase
def test_erf
check(0, Math.erf(0))
check(1, Math.erf(1.0 / 0.0))
+ assert_nan(Math.erf(Float::NAN))
end
def test_erfc
check(1, Math.erfc(0))
check(0, Math.erfc(1.0 / 0.0))
+ assert_nan(Math.erfc(Float::NAN))
end
def test_gamma
@@ -256,11 +299,12 @@ class TestMath < Test::Unit::TestCase
assert_infinity(Math.gamma(i-1), "Math.gamma(#{i-1}) should be INF")
end
- assert_raise(Math::DomainError) { Math.gamma(-Float::INFINITY) }
+ assert_raise_with_message(Math::DomainError, /\bgamma\b/) { Math.gamma(-Float::INFINITY) }
+ assert_raise_with_message(Math::DomainError, /\bgamma\b/) { Math.gamma(-1.0) }
x = Math.gamma(-0.0)
mesg = "Math.gamma(-0.0) should be -INF"
- assert_infinity(x, mesg)
- assert_predicate(x, :negative?, mesg)
+ assert_infinity(-x, mesg)
+ assert_nan(Math.gamma(Float::NAN))
end
def test_lgamma
@@ -276,12 +320,23 @@ class TestMath < Test::Unit::TestCase
assert_float_and_int([Math.log(15 * sqrt_pi / 8), 1], Math.lgamma(3.5))
assert_float_and_int([Math.log(6), 1], Math.lgamma(4))
- assert_raise(Math::DomainError) { Math.lgamma(-Float::INFINITY) }
+ 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_predicate(x, :positive?, mesg)
assert_equal(-1, sign, mesg)
+
+ x, = Math.lgamma(-1)
+ assert_infinity(x, "Math.lgamma(-1) should be +INF")
+
+ x, = Math.lgamma(Float::NAN)
+ assert_nan(x)
end
def test_fixnum_to_f
diff --git a/test/ruby/test_memory_view.rb b/test/ruby/test_memory_view.rb
new file mode 100644
index 0000000000..d0122ddd59
--- /dev/null
+++ b/test/ruby/test_memory_view.rb
@@ -0,0 +1,341 @@
+require "-test-/memory_view"
+require "rbconfig/sizeof"
+
+class TestMemoryView < Test::Unit::TestCase
+ NATIVE_ENDIAN = MemoryViewTestUtils::NATIVE_ENDIAN
+ LITTLE_ENDIAN = :little_endian
+ BIG_ENDIAN = :big_endian
+
+ %I(SHORT INT INT16 INT32 INT64 INTPTR LONG LONG_LONG FLOAT DOUBLE).each do |type|
+ name = :"#{type}_ALIGNMENT"
+ const_set(name, MemoryViewTestUtils.const_get(name))
+ end
+
+ def test_rb_memory_view_register_duplicated
+ assert_warning(/Duplicated registration of memory view to/) do
+ MemoryViewTestUtils.register(MemoryViewTestUtils::ExportableString)
+ end
+ end
+
+ def test_rb_memory_view_register_nonclass
+ assert_raise(TypeError) do
+ MemoryViewTestUtils.register(Object.new)
+ end
+ end
+
+ def sizeof(type)
+ RbConfig::SIZEOF[type.to_s]
+ end
+
+ def test_rb_memory_view_item_size_from_format
+ [
+ [nil, 1], ['c', 1], ['C', 1],
+ ['n', 2], ['v', 2],
+ ['l', 4], ['L', 4], ['N', 4], ['V', 4], ['f', 4], ['e', 4], ['g', 4],
+ ['q', 8], ['Q', 8], ['d', 8], ['E', 8], ['G', 8],
+ ['s', sizeof(:short)], ['S', sizeof(:short)], ['s!', sizeof(:short)], ['S!', sizeof(:short)],
+ ['i', sizeof(:int)], ['I', sizeof(:int)], ['i!', sizeof(:int)], ['I!', sizeof(:int)],
+ ['l!', sizeof(:long)], ['L!', sizeof(:long)],
+ ['q!', sizeof('long long')], ['Q!', sizeof('long long')],
+ ['j', sizeof(:intptr_t)], ['J', sizeof(:intptr_t)],
+ ].each do |format, expected|
+ actual, err = MemoryViewTestUtils.item_size_from_format(format)
+ assert_nil(err)
+ assert_equal(expected, actual, "rb_memory_view_item_size_from_format(#{format || 'NULL'}) == #{expected}")
+ end
+ end
+
+ def test_rb_memory_view_item_size_from_format_composed
+ actual, = MemoryViewTestUtils.item_size_from_format("ccc")
+ assert_equal(3, actual)
+
+ actual, = MemoryViewTestUtils.item_size_from_format("c3")
+ assert_equal(3, actual)
+
+ actual, = MemoryViewTestUtils.item_size_from_format("fd")
+ assert_equal(12, actual)
+
+ actual, = MemoryViewTestUtils.item_size_from_format("fx2d")
+ assert_equal(14, actual)
+ end
+
+ def test_rb_memory_view_item_size_from_format_with_spaces
+ # spaces should be ignored
+ actual, = MemoryViewTestUtils.item_size_from_format("f x2 d")
+ assert_equal(14, actual)
+ end
+
+ def test_rb_memory_view_item_size_from_format_error
+ assert_equal([-1, "a"], MemoryViewTestUtils.item_size_from_format("ccca"))
+ assert_equal([-1, "a"], MemoryViewTestUtils.item_size_from_format("ccc4a"))
+ end
+
+ def test_rb_memory_view_parse_item_format
+ total_size, members, err = MemoryViewTestUtils.parse_item_format("ccc2f3x2d4q!<")
+ assert_equal(58, total_size)
+ assert_nil(err)
+ assert_equal([
+ {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 0, size: 1, repeat: 1},
+ {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 1, size: 1, repeat: 1},
+ {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 2, size: 1, repeat: 2},
+ {format: 'f', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 4, size: 4, repeat: 3},
+ {format: 'd', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 18, size: 8, repeat: 4},
+ {format: 'q', native_size_p: true, endianness: :little_endian, offset: 50, size: sizeof('long long'), repeat: 1}
+ ],
+ members)
+ end
+
+ def test_rb_memory_view_parse_item_format_with_alignment_signle
+ [
+ ["c", false, NATIVE_ENDIAN, 1, 1, 1],
+ ["C", false, NATIVE_ENDIAN, 1, 1, 1],
+ ["s", false, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1],
+ ["S", false, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1],
+ ["s!", true, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1],
+ ["S!", true, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1],
+ ["n", false, :big_endian, INT16_ALIGNMENT, sizeof(:int16_t), 1],
+ ["v", false, :little_endian, INT16_ALIGNMENT, sizeof(:int16_t), 1],
+ ["i", false, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1],
+ ["I", false, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1],
+ ["i!", true, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1],
+ ["I!", true, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1],
+ ["l", false, NATIVE_ENDIAN, INT32_ALIGNMENT, sizeof(:int32_t), 1],
+ ["L", false, NATIVE_ENDIAN, INT32_ALIGNMENT, sizeof(:int32_t), 1],
+ ["l!", true, NATIVE_ENDIAN, LONG_ALIGNMENT, sizeof(:long), 1],
+ ["L!", true, NATIVE_ENDIAN, LONG_ALIGNMENT, sizeof(:long), 1],
+ ["N", false, :big_endian, INT32_ALIGNMENT, sizeof(:int32_t), 1],
+ ["V", false, :little_endian, INT32_ALIGNMENT, sizeof(:int32_t), 1],
+ ["f", false, NATIVE_ENDIAN, FLOAT_ALIGNMENT, sizeof(:float), 1],
+ ["e", false, :little_endian, FLOAT_ALIGNMENT, sizeof(:float), 1],
+ ["g", false, :big_endian, FLOAT_ALIGNMENT, sizeof(:float), 1],
+ ["q", false, NATIVE_ENDIAN, INT64_ALIGNMENT, sizeof(:int64_t), 1],
+ ["Q", false, NATIVE_ENDIAN, INT64_ALIGNMENT, sizeof(:int64_t), 1],
+ ["q!", true, NATIVE_ENDIAN, LONG_LONG_ALIGNMENT, sizeof("long long"), 1],
+ ["Q!", true, NATIVE_ENDIAN, LONG_LONG_ALIGNMENT, sizeof("long long"), 1],
+ ["d", false, NATIVE_ENDIAN, DOUBLE_ALIGNMENT, sizeof(:double), 1],
+ ["E", false, :little_endian, DOUBLE_ALIGNMENT, sizeof(:double), 1],
+ ["G", false, :big_endian, DOUBLE_ALIGNMENT, sizeof(:double), 1],
+ ["j", false, NATIVE_ENDIAN, INTPTR_ALIGNMENT, sizeof(:intptr_t), 1],
+ ["J", false, NATIVE_ENDIAN, INTPTR_ALIGNMENT, sizeof(:intptr_t), 1],
+ ].each do |type, native_size_p, endianness, alignment, size, repeat, total_size|
+ total_size, members, err = MemoryViewTestUtils.parse_item_format("|c#{type}")
+ assert_nil(err)
+
+ padding_size = alignment - 1
+ expected_total_size = 1 + padding_size + size
+ assert_equal(expected_total_size, total_size)
+
+ expected_result = [
+ {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 0, size: 1, repeat: 1},
+ {format: type[0], native_size_p: native_size_p, endianness: endianness, offset: alignment, size: size, repeat: repeat},
+ ]
+ assert_equal(expected_result, members)
+ end
+ end
+
+ def alignment_padding(total_size, alignment)
+ res = total_size % alignment
+ if res > 0
+ alignment - res
+ else
+ 0
+ end
+ end
+
+ def test_rb_memory_view_parse_item_format_with_alignment_total_size_with_tail_padding
+ total_size, _members, err = MemoryViewTestUtils.parse_item_format("|lqc")
+ assert_nil(err)
+
+ expected_total_size = sizeof(:int32_t)
+ expected_total_size += alignment_padding(expected_total_size, INT32_ALIGNMENT)
+ expected_total_size += sizeof(:int64_t)
+ expected_total_size += alignment_padding(expected_total_size, INT64_ALIGNMENT)
+ expected_total_size += 1
+ expected_total_size += alignment_padding(expected_total_size, INT64_ALIGNMENT)
+ assert_equal(expected_total_size, total_size)
+ end
+
+ def test_rb_memory_view_parse_item_format_with_alignment_compound
+ total_size, members, err = MemoryViewTestUtils.parse_item_format("|ccc2f3x2d4cq!<")
+ assert_nil(err)
+
+ expected_total_size = 1 + 1 + 1*2
+ expected_total_size += alignment_padding(expected_total_size, FLOAT_ALIGNMENT)
+ expected_total_size += sizeof(:float)*3 + 1*2
+ expected_total_size += alignment_padding(expected_total_size, DOUBLE_ALIGNMENT)
+ expected_total_size += sizeof(:double)*4 + 1
+ expected_total_size += alignment_padding(expected_total_size, LONG_LONG_ALIGNMENT)
+ expected_total_size += sizeof("long long")
+ assert_equal(expected_total_size, total_size)
+
+ expected_result = [
+ {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 0, size: 1, repeat: 1},
+ {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 1, size: 1, repeat: 1},
+ {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 2, size: 1, repeat: 2},
+ ]
+ offset = 4
+
+ res = offset % FLOAT_ALIGNMENT
+ offset += FLOAT_ALIGNMENT - res if res > 0
+ expected_result << {format: 'f', native_size_p: false, endianness: NATIVE_ENDIAN, offset: offset, size: 4, repeat: 3}
+ offset += 12
+
+ offset += 2 # 2x
+
+ res = offset % DOUBLE_ALIGNMENT
+ offset += DOUBLE_ALIGNMENT - res if res > 0
+ expected_result << {format: 'd', native_size_p: false, endianness: NATIVE_ENDIAN, offset: offset, size: 8, repeat: 4}
+ offset += 32
+
+ expected_result << {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: offset, size: 1, repeat: 1}
+ offset += 1
+
+ res = offset % LONG_LONG_ALIGNMENT
+ offset += LONG_LONG_ALIGNMENT - res if res > 0
+ expected_result << {format: 'q', native_size_p: true, endianness: :little_endian, offset: offset, size: 8, repeat: 1}
+
+ assert_equal(expected_result, members)
+ end
+
+ def test_rb_memory_view_extract_item_members
+ m = MemoryViewTestUtils
+ assert_equal(1, m.extract_item_members([1].pack("c"), "c"))
+ assert_equal([1, 2], m.extract_item_members([1, 2].pack("ii"), "ii"))
+ assert_equal([1, 2, 3], m.extract_item_members([1, 2, 3].pack("cls"), "cls"))
+ end
+
+ def test_rb_memory_view_extract_item_members_endianness
+ m = MemoryViewTestUtils
+ assert_equal([0x0102, 0x0304], m.extract_item_members([1, 2, 3, 4].pack("c*"), "S>2"))
+ assert_equal([0x0102, 0x0304], m.extract_item_members([1, 2, 3, 4].pack("c*"), "n2"))
+ assert_equal([0x0201, 0x0403], m.extract_item_members([1, 2, 3, 4].pack("c*"), "S<2"))
+ assert_equal([0x0201, 0x0403], m.extract_item_members([1, 2, 3, 4].pack("c*"), "v2"))
+ assert_equal(0x01020304, m.extract_item_members([1, 2, 3, 4].pack("c*"), "L>"))
+ assert_equal(0x01020304, m.extract_item_members([1, 2, 3, 4].pack("c*"), "N"))
+ assert_equal(0x04030201, m.extract_item_members([1, 2, 3, 4].pack("c*"), "L<"))
+ assert_equal(0x04030201, m.extract_item_members([1, 2, 3, 4].pack("c*"), "V"))
+ assert_equal(0x0102030405060708, m.extract_item_members([1, 2, 3, 4, 5, 6, 7, 8].pack("c*"), "Q>"))
+ assert_equal(0x0807060504030201, m.extract_item_members([1, 2, 3, 4, 5, 6, 7, 8].pack("c*"), "Q<"))
+ end
+
+ def test_rb_memory_view_extract_item_members_float
+ m = MemoryViewTestUtils
+ packed = [1.23].pack("f")
+ assert_equal(packed.unpack("f")[0], m.extract_item_members(packed, "f"))
+ end
+
+ def test_rb_memory_view_extract_item_members_float_endianness
+ m = MemoryViewTestUtils
+ hi, lo = [1.23].pack("f").unpack("L")[0].divmod(0x10000)
+ packed = [lo, hi].pack("S*")
+ assert_equal(packed.unpack("e")[0], m.extract_item_members(packed, "e"))
+ packed = [hi, lo].pack("S*")
+ assert_equal(packed.unpack("g")[0], m.extract_item_members(packed, "g"))
+ end
+
+ def test_rb_memory_view_extract_item_members_doble
+ m = MemoryViewTestUtils
+ packed = [1.23].pack("d")
+ assert_equal(1.23, m.extract_item_members(packed, "d"))
+ end
+
+ def test_rb_memory_view_extract_item_members_doble_endianness
+ m = MemoryViewTestUtils
+ hi, lo = [1.23].pack("d").unpack("Q")[0].divmod(0x10000)
+ packed = [lo, hi].pack("L*")
+ assert_equal(packed.unpack("E")[0], m.extract_item_members(packed, "E"))
+ packed = [hi, lo].pack("L*")
+ assert_equal(packed.unpack("G")[0], m.extract_item_members(packed, "G"))
+ end
+
+ def test_rb_memory_view_available_p
+ es = MemoryViewTestUtils::ExportableString.new("ruby")
+ assert_equal(true, MemoryViewTestUtils.available?(es))
+ es = MemoryViewTestUtils::ExportableString.new(nil)
+ assert_equal(false, MemoryViewTestUtils.available?(es))
+ end
+
+ def test_ref_count_with_exported_object
+ es = MemoryViewTestUtils::ExportableString.new("ruby")
+ assert_equal(1, MemoryViewTestUtils.ref_count_while_exporting(es, 1))
+ assert_equal(2, MemoryViewTestUtils.ref_count_while_exporting(es, 2))
+ assert_equal(10, MemoryViewTestUtils.ref_count_while_exporting(es, 10))
+ assert_nil(MemoryViewTestUtils.ref_count_while_exporting(es, 0))
+ end
+
+ def test_rb_memory_view_init_as_byte_array
+ # ExportableString's memory view is initialized by rb_memory_view_init_as_byte_array
+ es = MemoryViewTestUtils::ExportableString.new("ruby")
+ memory_view_info = MemoryViewTestUtils.get_memory_view_info(es)
+ assert_equal({
+ obj: es,
+ byte_size: 4,
+ readonly: true,
+ format: nil,
+ item_size: 1,
+ ndim: 1,
+ shape: nil,
+ strides: nil,
+ sub_offsets: nil
+ },
+ memory_view_info)
+ end
+
+ def test_rb_memory_view_get_with_memory_view_unavailable_object
+ es = MemoryViewTestUtils::ExportableString.new(nil)
+ memory_view_info = MemoryViewTestUtils.get_memory_view_info(es)
+ assert_nil(memory_view_info)
+ end
+
+ def test_rb_memory_view_fill_contiguous_strides
+ row_major_strides = MemoryViewTestUtils.fill_contiguous_strides(3, 8, [2, 3, 4], true)
+ assert_equal([96, 32, 8],
+ row_major_strides)
+
+ column_major_strides = MemoryViewTestUtils.fill_contiguous_strides(3, 8, [2, 3, 4], false)
+ assert_equal([8, 16, 48],
+ column_major_strides)
+ end
+
+ def test_rb_memory_view_get_item_pointer_single_member
+ buf = [ 1, 2, 3, 4,
+ 5, 6, 7, 8,
+ 9, 10, 11, 12 ].pack("l!*")
+ shape = [3, 4]
+ mv = MemoryViewTestUtils::MultiDimensionalView.new(buf, "l!", shape, nil)
+ assert_equal(1, mv[[0, 0]])
+ assert_equal(4, mv[[0, 3]])
+ assert_equal(6, mv[[1, 1]])
+ assert_equal(10, mv[[2, 1]])
+ end
+
+ def test_rb_memory_view_get_item_pointer_multiple_members
+ buf = [ 1, 2, 3, 4, 5, 6, 7, 8,
+ -1, -2, -3, -4, -5, -6, -7, -8].pack("s*")
+ shape = [2, 4]
+ strides = [4*sizeof(:short)*2, sizeof(:short)*2]
+ mv = MemoryViewTestUtils::MultiDimensionalView.new(buf, "ss", shape, strides)
+ assert_equal([1, 2], mv[[0, 0]])
+ assert_equal([5, 6], mv[[0, 2]])
+ assert_equal([-1, -2], mv[[1, 0]])
+ assert_equal([-7, -8], mv[[1, 3]])
+ end
+
+ def test_ractor
+ assert_in_out_err([], <<-"end;", ["[5, 6]", "[-7, -8]"], [])
+ require "-test-/memory_view"
+ require "rbconfig/sizeof"
+ $VERBOSE = nil
+ r = Ractor.new RbConfig::SIZEOF["short"] do |sizeof_short|
+ buf = [ 1, 2, 3, 4, 5, 6, 7, 8,
+ -1, -2, -3, -4, -5, -6, -7, -8].pack("s*")
+ shape = [2, 4]
+ strides = [4*sizeof_short*2, sizeof_short*2]
+ mv = MemoryViewTestUtils::MultiDimensionalView.new(buf, "ss", shape, strides)
+ p mv[[0, 2]]
+ mv[[1, 3]]
+ end
+ p r.value
+ end;
+ end
+end
diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb
index 77273dade5..8561f841a8 100644
--- a/test/ruby/test_method.rb
+++ b/test/ruby/test_method.rb
@@ -5,7 +5,6 @@ require 'test/unit'
class TestMethod < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
end
def teardown
@@ -22,6 +21,7 @@ class TestMethod < Test::Unit::TestCase
def mo5(a, *b, c) end
def mo6(a, *b, c, &d) end
def mo7(a, b = nil, *c, d, &e) end
+ def mo8(a, b = nil, *, d, &e) end
def ma1((a), &b) nil && a end
def mk1(**) end
def mk2(**o) nil && o end
@@ -30,6 +30,9 @@ class TestMethod < Test::Unit::TestCase
def mk5(a, b = nil, **o) nil && o end
def mk6(a, b = nil, c, **o) nil && o end
def mk7(a, b = nil, *c, d, **o) nil && o end
+ def mk8(a, b = nil, *c, d, e:, f: nil, **o) nil && o end
+ def mnk(**nil) end
+ def mf(...) end
class Base
def foo() :base end
@@ -100,6 +103,12 @@ class TestMethod < Test::Unit::TestCase
assert_raise(TypeError) do
um.bind(Base.new)
end
+
+ # cleanup
+ Derived.class_eval do
+ remove_method :foo
+ def foo() :derived; end
+ end
end
def test_callee
@@ -200,6 +209,27 @@ class TestMethod < Test::Unit::TestCase
assert_kind_of(String, o.method(:foo).hash.to_s)
end
+ def test_hash_does_not_change_after_compaction
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ # iseq backed method
+ assert_separately([], <<~RUBY)
+ def a; end
+
+ # Need this method here because otherwise the iseq may be on the C stack
+ # which would get pinned and not move during compaction
+ def get_hash
+ method(:a).hash
+ end
+
+ hash = get_hash
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_equal(hash, get_hash)
+ RUBY
+ end
+
def test_owner
c = Class.new do
def foo; end
@@ -254,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
@@ -285,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
@@ -309,6 +344,17 @@ class TestMethod < Test::Unit::TestCase
assert_equal(:foo, o.foo)
end
+ PUBLIC_SINGLETON_TEST = Object.new
+ class << PUBLIC_SINGLETON_TEST
+ private
+ PUBLIC_SINGLETON_TEST.define_singleton_method(:dsm){}
+ def PUBLIC_SINGLETON_TEST.def; end
+ end
+ def test_define_singleton_method_public
+ assert_nil(PUBLIC_SINGLETON_TEST.dsm)
+ assert_nil(PUBLIC_SINGLETON_TEST.def)
+ end
+
def test_define_singleton_method_no_proc
o = Object.new
assert_raise(ArgumentError) {
@@ -430,34 +476,60 @@ class TestMethod < Test::Unit::TestCase
assert_equal(:bar, m.clone.bar)
end
+ def test_clone_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ o = Object.new
+ def o.foo; :foo; end
+ m = o.method(:foo)
+ def m.bar; :bar; end
+ assert_equal(:foo, m.clone.call)
+ assert_equal(:bar, m.clone.bar)
+ end
+ end
+
def test_inspect
o = Object.new
- def o.foo; end
+ def o.foo; end; line_no = __LINE__
m = o.method(:foo)
- assert_equal("#<Method: #{ o.inspect }.foo>", m.inspect)
+ assert_equal("#<Method: #{ o.inspect }.foo() #{__FILE__}:#{line_no}>", m.inspect)
m = o.method(:foo)
- assert_equal("#<UnboundMethod: #{ class << o; self; end.inspect }#foo>", m.unbind.inspect)
+ assert_match("#<UnboundMethod: #{ class << o; self; end.inspect }#foo() #{__FILE__}:#{line_no}", m.unbind.inspect)
c = Class.new
- c.class_eval { def foo; end; }
+ c.class_eval { def foo; end; }; line_no = __LINE__
m = c.new.method(:foo)
- assert_equal("#<Method: #{ c.inspect }#foo>", m.inspect)
+ assert_equal("#<Method: #{ c.inspect }#foo() #{__FILE__}:#{line_no}>", m.inspect)
m = c.instance_method(:foo)
- assert_equal("#<UnboundMethod: #{ c.inspect }#foo>", m.inspect)
+ assert_equal("#<UnboundMethod: #{ c.inspect }#foo() #{__FILE__}:#{line_no}>", m.inspect)
c2 = Class.new(c)
c2.class_eval { private :foo }
m2 = c2.new.method(:foo)
- assert_equal("#<Method: #{ c2.inspect }(#{ c.inspect })#foo>", m2.inspect)
+ assert_equal("#<Method: #{ c2.inspect }(#{ c.inspect })#foo() #{__FILE__}:#{line_no}>", m2.inspect)
bug7806 = '[ruby-core:52048] [Bug #7806]'
c3 = Class.new(c)
c3.class_eval { alias bar foo }
m3 = c3.new.method(:bar)
- assert_equal("#<Method: #{c3.inspect}(#{c.inspect})#bar(foo)>", m3.inspect, bug7806)
+ assert_equal("#<Method: #{c3.inspect}(#{c.inspect})#bar(foo)() #{__FILE__}:#{line_no}>", m3.inspect, bug7806)
+
+ bug15608 = '[ruby-core:91570] [Bug #15608]'
+ c4 = Class.new(c)
+ c4.class_eval { alias bar foo }
+ o = c4.new
+ o.singleton_class
+ m4 = o.method(:bar)
+ assert_equal("#<Method: #{c4.inspect}(#{c.inspect})#bar(foo)() #{__FILE__}:#{line_no}>", m4.inspect, bug15608)
- m.taint
- assert_predicate(m.inspect, :tainted?, "inspect result should be infected")
+ bug17428 = '[ruby-core:101635] [Bug #17428]'
+ assert_equal("#<Method: #<Class:String>(Module)#prepend(*)>", String.method(:prepend).inspect, bug17428)
+
+ c5 = Class.new(String)
+ m = Module.new{def prepend; end; alias prep prepend}; line_no = __LINE__
+ c5.extend(m)
+ c6 = Class.new(c5)
+ assert_equal("#<Method: #<Class:#{c6.inspect}>(#{m.inspect})#prep(prepend)() #{__FILE__}:#{line_no}>", c6.method(:prep).inspect, bug17428)
end
def test_callee_top_level
@@ -495,6 +567,22 @@ class TestMethod < Test::Unit::TestCase
assert_include mmethods, :meth, 'normal methods are public by default'
end
+ def test_respond_to_missing_argument
+ obj = Struct.new(:mid).new
+ def obj.respond_to_missing?(id, *)
+ self.mid = id
+ true
+ end
+ assert_kind_of(Method, obj.method("bug15640"))
+ assert_kind_of(Symbol, obj.mid)
+ assert_equal("bug15640", obj.mid.to_s)
+
+ arg = Struct.new(:to_str).new("bug15640_2")
+ assert_kind_of(Method, obj.method(arg))
+ assert_kind_of(Symbol, obj.mid)
+ assert_equal("bug15640_2", obj.mid.to_s)
+ end
+
define_method(:pm0) {||}
define_method(:pm1) {|a|}
define_method(:pm2) {|a, b|}
@@ -513,6 +601,8 @@ class TestMethod < Test::Unit::TestCase
define_method(:pmk5) {|a, b = nil, **o|}
define_method(:pmk6) {|a, b = nil, c, **o|}
define_method(:pmk7) {|a, b = nil, *c, d, **o|}
+ define_method(:pmk8) {|a, b = nil, *c, d, e:, f: nil, **o|}
+ define_method(:pmnk) {|**nil|}
def test_bound_parameters
assert_equal([], method(:m0).parameters)
@@ -525,14 +615,19 @@ class TestMethod < Test::Unit::TestCase
assert_equal([[:req, :a], [:rest, :b], [:req, :c]], method(:mo5).parameters)
assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:mo6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:mo7).parameters)
+ assert_equal([[:req, :a], [:opt, :b], [:rest, :*], [:req, :d], [:block, :e]], method(:mo8).parameters)
assert_equal([[:req], [:block, :b]], method(:ma1).parameters)
- assert_equal([[:keyrest]], method(:mk1).parameters)
+ assert_equal([[:keyrest, :**]], method(:mk1).parameters)
assert_equal([[:keyrest, :o]], method(:mk2).parameters)
assert_equal([[:req, :a], [:keyrest, :o]], method(:mk3).parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], method(:mk4).parameters)
assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], method(:mk5).parameters)
assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], method(:mk6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:mk7).parameters)
+ assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], method(:mk8).parameters)
+ assert_equal([[:nokey]], method(:mnk).parameters)
+ # pending
+ assert_equal([[:rest, :*], [:keyrest, :**], [:block, :&]], method(:mf).parameters)
end
def test_unbound_parameters
@@ -546,14 +641,19 @@ class TestMethod < Test::Unit::TestCase
assert_equal([[:req, :a], [:rest, :b], [:req, :c]], self.class.instance_method(:mo5).parameters)
assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], self.class.instance_method(:mo6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:mo7).parameters)
+ assert_equal([[:req, :a], [:opt, :b], [:rest, :*], [:req, :d], [:block, :e]], self.class.instance_method(:mo8).parameters)
assert_equal([[:req], [:block, :b]], self.class.instance_method(:ma1).parameters)
- assert_equal([[:keyrest]], self.class.instance_method(:mk1).parameters)
+ assert_equal([[:keyrest, :**]], self.class.instance_method(:mk1).parameters)
assert_equal([[:keyrest, :o]], self.class.instance_method(:mk2).parameters)
assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:mk3).parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:mk4).parameters)
assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], self.class.instance_method(:mk5).parameters)
assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], self.class.instance_method(:mk6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:mk7).parameters)
+ assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], self.class.instance_method(:mk8).parameters)
+ assert_equal([[:nokey]], self.class.instance_method(:mnk).parameters)
+ # pending
+ assert_equal([[:rest, :*], [:keyrest, :**], [:block, :&]], self.class.instance_method(:mf).parameters)
end
def test_bmethod_bound_parameters
@@ -568,13 +668,15 @@ class TestMethod < Test::Unit::TestCase
assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:pmo6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:pmo7).parameters)
assert_equal([[:req], [:block, :b]], method(:pma1).parameters)
- assert_equal([[:keyrest]], method(:pmk1).parameters)
+ assert_equal([[:keyrest, :**]], method(:pmk1).parameters)
assert_equal([[:keyrest, :o]], method(:pmk2).parameters)
assert_equal([[:req, :a], [:keyrest, :o]], method(:pmk3).parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], method(:pmk4).parameters)
assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], method(:pmk5).parameters)
assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], method(:pmk6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:pmk7).parameters)
+ assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], method(:pmk8).parameters)
+ assert_equal([[:nokey]], method(:pmnk).parameters)
end
def test_bmethod_unbound_parameters
@@ -590,13 +692,15 @@ class TestMethod < Test::Unit::TestCase
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:pmo7).parameters)
assert_equal([[:req], [:block, :b]], self.class.instance_method(:pma1).parameters)
assert_equal([[:req], [:block, :b]], self.class.instance_method(:pma1).parameters)
- assert_equal([[:keyrest]], self.class.instance_method(:pmk1).parameters)
+ assert_equal([[:keyrest, :**]], self.class.instance_method(:pmk1).parameters)
assert_equal([[:keyrest, :o]], self.class.instance_method(:pmk2).parameters)
assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:pmk3).parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:pmk4).parameters)
assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], self.class.instance_method(:pmk5).parameters)
assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], self.class.instance_method(:pmk6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:pmk7).parameters)
+ assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], self.class.instance_method(:pmk8).parameters)
+ assert_equal([[:nokey]], self.class.instance_method(:pmnk).parameters)
end
def test_hidden_parameters
@@ -604,6 +708,56 @@ class TestMethod < Test::Unit::TestCase
assert_empty(method(:m).parameters.map{|_,n|n}.compact)
end
+ def test_method_parameters_inspect
+ assert_include(method(:m0).inspect, "()")
+ assert_include(method(:m1).inspect, "(a)")
+ assert_include(method(:m2).inspect, "(a, b)")
+ assert_include(method(:mo1).inspect, "(a=..., &b)")
+ assert_include(method(:mo2).inspect, "(a, b=...)")
+ assert_include(method(:mo3).inspect, "(*a)")
+ assert_include(method(:mo4).inspect, "(a, *b, &c)")
+ assert_include(method(:mo5).inspect, "(a, *b, c)")
+ assert_include(method(:mo6).inspect, "(a, *b, c, &d)")
+ assert_include(method(:mo7).inspect, "(a, b=..., *c, d, &e)")
+ assert_include(method(:mo8).inspect, "(a, b=..., *, d, &e)")
+ assert_include(method(:ma1).inspect, "(_, &b)")
+ assert_include(method(:mk1).inspect, "(**)")
+ assert_include(method(:mk2).inspect, "(**o)")
+ assert_include(method(:mk3).inspect, "(a, **o)")
+ assert_include(method(:mk4).inspect, "(a=..., **o)")
+ assert_include(method(:mk5).inspect, "(a, b=..., **o)")
+ assert_include(method(:mk6).inspect, "(a, b=..., c, **o)")
+ assert_include(method(:mk7).inspect, "(a, b=..., *c, d, **o)")
+ assert_include(method(:mk8).inspect, "(a, b=..., *c, d, e:, f: ..., **o)")
+ assert_include(method(:mnk).inspect, "(**nil)")
+ assert_include(method(:mf).inspect, "(...)")
+ end
+
+ def test_unbound_method_parameters_inspect
+ assert_include(self.class.instance_method(:m0).inspect, "()")
+ assert_include(self.class.instance_method(:m1).inspect, "(a)")
+ assert_include(self.class.instance_method(:m2).inspect, "(a, b)")
+ assert_include(self.class.instance_method(:mo1).inspect, "(a=..., &b)")
+ assert_include(self.class.instance_method(:mo2).inspect, "(a, b=...)")
+ assert_include(self.class.instance_method(:mo3).inspect, "(*a)")
+ assert_include(self.class.instance_method(:mo4).inspect, "(a, *b, &c)")
+ assert_include(self.class.instance_method(:mo5).inspect, "(a, *b, c)")
+ assert_include(self.class.instance_method(:mo6).inspect, "(a, *b, c, &d)")
+ assert_include(self.class.instance_method(:mo7).inspect, "(a, b=..., *c, d, &e)")
+ assert_include(self.class.instance_method(:mo8).inspect, "(a, b=..., *, d, &e)")
+ assert_include(self.class.instance_method(:ma1).inspect, "(_, &b)")
+ assert_include(self.class.instance_method(:mk1).inspect, "(**)")
+ assert_include(self.class.instance_method(:mk2).inspect, "(**o)")
+ assert_include(self.class.instance_method(:mk3).inspect, "(a, **o)")
+ assert_include(self.class.instance_method(:mk4).inspect, "(a=..., **o)")
+ assert_include(self.class.instance_method(:mk5).inspect, "(a, b=..., **o)")
+ assert_include(self.class.instance_method(:mk6).inspect, "(a, b=..., c, **o)")
+ assert_include(self.class.instance_method(:mk7).inspect, "(a, b=..., *c, d, **o)")
+ assert_include(self.class.instance_method(:mk8).inspect, "(a, b=..., *c, d, e:, f: ..., **o)")
+ assert_include(self.class.instance_method(:mnk).inspect, "(**nil)")
+ assert_include(self.class.instance_method(:mf).inspect, "(...)")
+ end
+
def test_public_method_with_zsuper_method
c = Class.new
c.class_eval do
@@ -651,9 +805,18 @@ class TestMethod < Test::Unit::TestCase
assert_nothing_raised { mv3 }
assert_nothing_raised { self.mv1 }
- assert_raise(NoMethodError) { self.mv2 }
+ assert_nothing_raised { self.mv2 }
+ assert_raise(NoMethodError) { (self).mv2 }
assert_nothing_raised { self.mv3 }
+ class << (obj = Object.new)
+ private def [](x) x end
+ def mv1(x) self[x] end
+ def mv2(x) (self)[x] end
+ end
+ assert_nothing_raised { obj.mv1(0) }
+ assert_raise(NoMethodError) { obj.mv2(0) }
+
v = Visibility.new
assert_equal('method', defined?(v.mv1))
@@ -706,7 +869,9 @@ class TestMethod < Test::Unit::TestCase
assert_instance_of String, __dir__
assert_equal(File.dirname(File.realpath(__FILE__)), __dir__)
bug8436 = '[ruby-core:55123] [Bug #8436]'
- assert_equal(__dir__, eval("__dir__", binding), bug8436)
+ file, line = *binding.source_location
+ file = File.realpath(file)
+ assert_equal(__dir__, eval("__dir__", binding, file, line), bug8436)
bug8662 = '[ruby-core:56099] [Bug #8662]'
assert_equal("arbitrary", eval("__dir__", binding, "arbitrary/file.rb"), bug8662)
assert_equal("arbitrary", Object.new.instance_eval("__dir__", "arbitrary/file.rb"), bug8662)
@@ -728,7 +893,7 @@ class TestMethod < Test::Unit::TestCase
assert_equal(c, c.instance_method(:foo).owner)
assert_equal(c, x.method(:foo).owner)
assert_equal(x.singleton_class, x.method(:bar).owner)
- assert_not_equal(x.method(:foo), x.method(:bar), bug7613)
+ assert_equal(x.method(:foo), x.method(:bar), bug7613)
assert_equal(c, x.method(:zot).owner, bug7993)
assert_equal(c, c.instance_method(:zot).owner, bug7993)
end
@@ -801,6 +966,38 @@ class TestMethod < Test::Unit::TestCase
assert_raise(NameError, bug14658) {o.singleton_method(:bar)}
end
+ def test_singleton_method_included_or_prepended_bug_20620
+ m = Module.new do
+ extend self
+ def foo = :foo
+ end
+ assert_equal(:foo, m.singleton_method(:foo).call)
+ assert_raise(NameError) {m.singleton_method(:puts)}
+
+ sc = Class.new do
+ def t = :t
+ end
+ c = Class.new(sc) do
+ singleton_class.prepend(Module.new do
+ def bar = :bar
+ end)
+ extend(Module.new do
+ def quux = :quux
+ end)
+ def self.baz = :baz
+ end
+ assert_equal(:bar, c.singleton_method(:bar).call)
+ assert_equal(:baz, c.singleton_method(:baz).call)
+ assert_equal(:quux, c.singleton_method(:quux).call)
+
+ assert_raise(NameError) {c.singleton_method(:t)}
+
+ c2 = Class.new(c)
+ assert_raise(NameError) {c2.singleton_method(:bar)}
+ assert_raise(NameError) {c2.singleton_method(:baz)}
+ assert_raise(NameError) {c2.singleton_method(:quux)}
+ end
+
Feature9783 = '[ruby-core:62212] [Feature #9783]'
def assert_curry_three_args(m)
@@ -907,7 +1104,37 @@ class TestMethod < Test::Unit::TestCase
assert_nil(m.super_method)
end
- def test_super_method_removed
+ def test_super_method_bind_unbind_clone
+ bug15629_m1 = Module.new do
+ def foo; end
+ end
+
+ bug15629_m2 = Module.new do
+ def foo; end
+ end
+
+ bug15629_c = Class.new do
+ include bug15629_m1
+ include bug15629_m2
+ end
+
+ o = bug15629_c.new
+ m = o.method(:foo)
+ sm = m.super_method
+ im = bug15629_c.instance_method(:foo)
+ sim = im.super_method
+
+ assert_equal(sm, m.clone.super_method)
+ assert_equal(sim, m.unbind.super_method)
+ assert_equal(sim, m.unbind.clone.super_method)
+ assert_equal(sim, im.clone.super_method)
+ assert_equal(sm, m.unbind.bind(o).super_method)
+ assert_equal(sm, m.unbind.clone.bind(o).super_method)
+ assert_equal(sm, im.bind(o).super_method)
+ assert_equal(sm, im.clone.bind(o).super_method)
+ end
+
+ def test_super_method_removed_public
c1 = Class.new {private def foo; end}
c2 = Class.new(c1) {public :foo}
c3 = Class.new(c2) {def foo; end}
@@ -917,20 +1144,35 @@ class TestMethod < Test::Unit::TestCase
assert_nil(m, Feature9781)
end
+ def test_super_method_removed_regular
+ c1 = Class.new { def foo; end }
+ c2 = Class.new(c1) { def foo; end }
+ assert_equal c1.instance_method(:foo), c2.instance_method(:foo).super_method
+ c1.remove_method :foo
+ assert_equal nil, c2.instance_method(:foo).super_method
+ end
+
def test_prepended_public_zsuper
- mod = EnvUtil.labeled_module("Mod") {private def foo; :ok end}
- mods = [mod]
+ mod = EnvUtil.labeled_module("Mod") {private def foo; [:ok] end}
obj = Object.new.extend(mod)
+
class << obj
public :foo
end
- 2.times do |i|
- mods.unshift(mod = EnvUtil.labeled_module("Mod#{i}") {def foo; end})
- obj.singleton_class.prepend(mod)
- end
+
+ mod1 = EnvUtil.labeled_module("Mod1") {def foo; [:mod1] + super end}
+ obj.singleton_class.prepend(mod1)
+
+ mod2 = EnvUtil.labeled_module("Mod2") {def foo; [:mod2] + super end}
+ obj.singleton_class.prepend(mod2)
+
m = obj.method(:foo)
- assert_equal(mods, mods.map {m.owner.tap {m = m.super_method}})
- assert_nil(m)
+ assert_equal mod2, m.owner
+ assert_equal mod1, m.super_method.owner
+ assert_equal obj.singleton_class, m.super_method.super_method.owner
+ assert_equal nil, m.super_method.super_method.super_method
+
+ assert_equal [:mod2, :mod1, :ok], obj.foo
end
def test_super_method_with_prepended_module
@@ -950,11 +1192,249 @@ class TestMethod < Test::Unit::TestCase
'[ruby-core:85231] [Bug #14421]'
end
+ def test_super_method_alias
+ c0 = Class.new do
+ def m1
+ [:C0_m1]
+ end
+ def m2
+ [:C0_m2]
+ end
+ end
+
+ c1 = Class.new(c0) do
+ def m1
+ [:C1_m1] + super
+ end
+ alias m2 m1
+ end
+
+ c2 = Class.new(c1) do
+ def m2
+ [:C2_m2] + super
+ end
+ end
+ o1 = c2.new
+ assert_equal([:C2_m2, :C1_m1, :C0_m1], o1.m2)
+
+ m = o1.method(:m2)
+ assert_equal([:C2_m2, :C1_m1, :C0_m1], m.call)
+
+ m = m.super_method
+ assert_equal([:C1_m1, :C0_m1], m.call)
+
+ m = m.super_method
+ assert_equal([:C0_m1], m.call)
+
+ assert_nil(m.super_method)
+ end
+
+ def test_super_method_alias_to_prepended_module
+ m = Module.new do
+ def m1
+ [:P_m1] + super
+ end
+
+ def m2
+ [:P_m2] + super
+ end
+ end
+
+ c0 = Class.new do
+ def m1
+ [:C0_m1]
+ end
+ end
+
+ c1 = Class.new(c0) do
+ def m1
+ [:C1_m1] + super
+ end
+ prepend m
+ alias m2 m1
+ end
+
+ o1 = c1.new
+ assert_equal([:P_m2, :P_m1, :C1_m1, :C0_m1], o1.m2)
+
+ m = o1.method(:m2)
+ assert_equal([:P_m2, :P_m1, :C1_m1, :C0_m1], m.call)
+
+ m = m.super_method
+ assert_equal([:P_m1, :C1_m1, :C0_m1], m.call)
+
+ m = m.super_method
+ assert_equal([:C1_m1, :C0_m1], m.call)
+
+ m = m.super_method
+ assert_equal([:C0_m1], m.call)
+
+ assert_nil(m.super_method)
+ end
+
+ # Bug 17780
+ def test_super_method_module_alias
+ m = Module.new do
+ def foo
+ end
+ alias :f :foo
+ end
+
+ method = m.instance_method(:f)
+ super_method = method.super_method
+ assert_nil(super_method)
+ end
+
+ # Bug 18435
+ def test_instance_methods_owner_consistency
+ a = Module.new { def method1; end }
+
+ b = Class.new do
+ include a
+ protected :method1
+ end
+
+ assert_equal [:method1], b.instance_methods(false)
+ assert_equal b, b.instance_method(:method1).owner
+ end
+
+ def test_zsuper_method_removed
+ a = EnvUtil.labeled_class('A') do
+ private
+ def foo(arg = nil)
+ 1
+ end
+ end
+ line = __LINE__ - 4
+
+ b = EnvUtil.labeled_class('B', a) do
+ public :foo
+ end
+
+ unbound = b.instance_method(:foo)
+
+ assert_equal unbound, b.public_instance_method(:foo)
+ assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
+ assert_equal [[:opt, :arg]], unbound.parameters
+
+ a.remove_method(:foo)
+
+ assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
+ assert_equal [[:opt, :arg]], unbound.parameters
+
+ obj = b.new
+ assert_equal 1, unbound.bind_call(obj)
+
+ assert_include b.instance_methods(false), :foo
+ link = 'https://github.com/ruby/ruby/pull/6467#issuecomment-1262159088'
+ assert_raise(NameError, link) { b.instance_method(:foo) }
+ # For #test_method_list below, otherwise we get the same error as just above
+ b.remove_method(:foo)
+ end
+
+ def test_zsuper_method_removed_higher_method
+ a0 = EnvUtil.labeled_class('A0') do
+ def foo(arg1 = nil, arg2 = nil)
+ 0
+ end
+ end
+ line0 = __LINE__ - 4
+ a0_foo = a0.instance_method(:foo)
+
+ a = EnvUtil.labeled_class('A', a0) do
+ private
+ def foo(arg = nil)
+ 1
+ end
+ end
+ line = __LINE__ - 4
+
+ b = EnvUtil.labeled_class('B', a) do
+ public :foo
+ end
+
+ unbound = b.instance_method(:foo)
+
+ assert_equal a0_foo, unbound.super_method
+
+ a.remove_method(:foo)
+
+ assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
+ assert_equal [[:opt, :arg]], unbound.parameters
+ assert_equal a0_foo, unbound.super_method
+
+ obj = b.new
+ assert_equal 1, unbound.bind_call(obj)
+
+ assert_include b.instance_methods(false), :foo
+ assert_equal "#<UnboundMethod: A0#foo(arg1=..., arg2=...) #{__FILE__}:#{line0}>", b.instance_method(:foo).inspect
+ end
+
+ def test_zsuper_method_redefined_bind_call
+ c0 = EnvUtil.labeled_class('C0') do
+ def foo
+ [:foo]
+ end
+ end
+
+ c1 = EnvUtil.labeled_class('C1', c0) do
+ def foo
+ super + [:bar]
+ end
+ end
+ m1 = c1.instance_method(:foo)
+
+ c2 = EnvUtil.labeled_class('C2', c1) do
+ private :foo
+ end
+
+ assert_equal [:foo], c2.private_instance_methods(false)
+ m2 = c2.instance_method(:foo)
+
+ c1.class_exec do
+ remove_method :foo
+ def foo
+ [:bar2]
+ end
+ end
+
+ m3 = c2.instance_method(:foo)
+ c = c2.new
+ assert_equal [:foo, :bar], m1.bind_call(c)
+ assert_equal c1, m1.owner
+ assert_equal [:foo, :bar], m2.bind_call(c)
+ assert_equal c2, m2.owner
+ assert_equal [:bar2], m3.bind_call(c)
+ assert_equal c2, m3.owner
+ end
+
+ # Bug #18751
+ def method_equality_visbility_alias
+ c = Class.new do
+ class << self
+ alias_method :n, :new
+ private :new
+ end
+ end
+
+ assert_equal c.method(:n), c.method(:new)
+
+ assert_not_equal c.method(:n), Class.method(:new)
+ assert_equal c.method(:n) == Class.instance_method(:new).bind(c)
+
+ assert_not_equal c.method(:new), Class.method(:new)
+ assert_equal c.method(:new), Class.instance_method(:new).bind(c)
+ end
+
def rest_parameter(*rest)
rest
end
def test_splat_long_array
+ if File.exist?('/etc/os-release') && File.read('/etc/os-release').include?('openSUSE Leap')
+ # For RubyCI's openSUSE machine http://rubyci.s3.amazonaws.com/opensuseleap/ruby-trunk/recent.html, which tends to die with NoMemoryError here.
+ omit 'do not exhaust memory on RubyCI openSUSE Leap machine'
+ end
n = 10_000_000
assert_equal n , rest_parameter(*(1..n)).size, '[Feature #10440]'
end
@@ -964,6 +1444,46 @@ class TestMethod < Test::Unit::TestCase
def foo
a = b = c = a = b = c = 12345
end
+
+ def binding_noarg
+ a = a = 12345
+ binding
+ end
+
+ def binding_one_arg(x)
+ a = a = 12345
+ binding
+ end
+
+ def binding_optargs(x, y=42)
+ a = a = 12345
+ binding
+ end
+
+ def binding_anyargs(*x)
+ a = a = 12345
+ binding
+ end
+
+ def binding_keywords(x: 42)
+ a = a = 12345
+ binding
+ end
+
+ def binding_anykeywords(**x)
+ a = a = 12345
+ binding
+ end
+
+ def binding_forwarding(...)
+ a = a = 12345
+ binding
+ end
+
+ def binding_forwarding1(x, ...)
+ a = a = 12345
+ binding
+ end
end
def test_to_proc_binding
@@ -982,17 +1502,82 @@ class TestMethod < Test::Unit::TestCase
assert_equal([:bar, :foo], b.local_variables.sort, bug11012)
end
- class MethodInMethodClass
- def m1
- def m2
+ def test_method_binding
+ c = C.new
+
+ b = c.binding_noarg
+ assert_equal(12345, b.local_variable_get(:a))
+
+ b = c.binding_one_arg(0)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(0, b.local_variable_get(:x))
+
+ b = c.binding_anyargs()
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal([], b.local_variable_get(:x))
+ b = c.binding_anyargs(0)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal([0], b.local_variable_get(:x))
+ b = c.binding_anyargs(0, 1)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal([0, 1], b.local_variable_get(:x))
+
+ b = c.binding_optargs(0)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(0, b.local_variable_get(:x))
+ assert_equal(42, b.local_variable_get(:y))
+ b = c.binding_optargs(0, 1)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(0, b.local_variable_get(:x))
+ assert_equal(1, b.local_variable_get(:y))
+
+ b = c.binding_keywords()
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(42, b.local_variable_get(:x))
+ b = c.binding_keywords(x: 102)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(102, b.local_variable_get(:x))
+
+ b = c.binding_anykeywords()
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal({}, b.local_variable_get(:x))
+ b = c.binding_anykeywords(foo: 999)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal({foo: 999}, b.local_variable_get(:x))
+
+ b = c.binding_forwarding()
+ assert_equal(12345, b.local_variable_get(:a))
+ b = c.binding_forwarding(0)
+ assert_equal(12345, b.local_variable_get(:a))
+ b = c.binding_forwarding(0, 1)
+ assert_equal(12345, b.local_variable_get(:a))
+ b = c.binding_forwarding(foo: 42)
+ assert_equal(12345, b.local_variable_get(:a))
+
+ b = c.binding_forwarding1(987)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(987, b.local_variable_get(:x))
+ b = c.binding_forwarding1(987, 654)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(987, b.local_variable_get(:x))
+ end
+
+ MethodInMethodClass_Setup = -> do
+ remove_const :MethodInMethodClass if defined? MethodInMethodClass
+
+ class MethodInMethodClass
+ def m1
+ def m2
+ end
+ self.class.send(:define_method, :m3){} # [Bug #11754]
end
-
- self.class.send(:define_method, :m3){} # [Bug #11754]
+ private
end
- private
end
def test_method_in_method_visibility_should_be_public
+ MethodInMethodClass_Setup.call
+
assert_equal([:m1].sort, MethodInMethodClass.public_instance_methods(false).sort)
assert_equal([].sort, MethodInMethodClass.private_instance_methods(false).sort)
@@ -1016,25 +1601,25 @@ class TestMethod < Test::Unit::TestCase
end
def test_argument_error_location
- body = <<-'END_OF_BODY'
- eval <<-'EOS'
- $line_lambda = __LINE__; $f = lambda do
- _x = 1
- end
- $line_method = __LINE__; def foo
- _x = 1
- end
- begin
- $f.call(1)
- rescue ArgumentError => e
- assert_equal "(eval):#{$line_lambda.to_s}:in `block in <main>'", e.backtrace.first
- end
- begin
- foo(1)
- rescue ArgumentError => e
- assert_equal "(eval):#{$line_method}:in `foo'", e.backtrace.first
- end
- EOS
+ body = <<~'END_OF_BODY'
+ eval <<~'EOS', nil, "main.rb"
+ $line_lambda = __LINE__; $f = lambda do
+ _x = 1
+ end
+ $line_method = __LINE__; def foo
+ _x = 1
+ end
+ begin
+ $f.call(1)
+ rescue ArgumentError => e
+ assert_equal "main.rb:#{$line_lambda}:in 'block in <main>'", e.backtrace.first
+ end
+ begin
+ foo(1)
+ rescue ArgumentError => e
+ assert_equal "main.rb:#{$line_method}:in 'Object#foo'", e.backtrace.first
+ end
+ EOS
END_OF_BODY
assert_separately [], body
@@ -1042,8 +1627,267 @@ class TestMethod < Test::Unit::TestCase
assert_separately [], "RubyVM::InstructionSequence.compile_option = {trace_instruction: false}\n" + body
end
+ def test_zsuper_private_override_instance_method
+ assert_separately([], <<-'end;', timeout: 30)
+ # Bug #16942 [ruby-core:98691]
+ module M
+ def x
+ end
+ end
+
+ module M2
+ prepend Module.new
+ include M
+ private :x
+ end
+
+ ::Object.prepend(M2)
+
+ m = Object.instance_method(:x)
+ assert_equal M2, m.owner
+ end;
+ end
+
+ def test_override_optimized_method_on_class_using_prepend
+ assert_separately([], <<-'end;', timeout: 30)
+ # Bug #17725 [ruby-core:102884]
+ $VERBOSE = nil
+ String.prepend(Module.new)
+ class String
+ def + other
+ 'blah blah'
+ end
+ end
+
+ assert_equal('blah blah', 'a' + 'b')
+ end;
+ end
+
def test_eqq
assert_operator(0.method(:<), :===, 5)
assert_not_operator(0.method(:<), :===, -5)
end
+
+ def test_compose_with_method
+ c = Class.new {
+ def f(x) x * 2 end
+ def g(x) x + 1 end
+ }
+ f = c.new.method(:f)
+ g = c.new.method(:g)
+
+ assert_equal(6, (f << g).call(2))
+ assert_equal(6, (g >> f).call(2))
+ end
+
+ def test_compose_with_proc
+ c = Class.new {
+ def f(x) x * 2 end
+ }
+ f = c.new.method(:f)
+ g = proc {|x| x + 1}
+
+ assert_equal(6, (f << g).call(2))
+ assert_equal(6, (g >> f).call(2))
+ end
+
+ def test_compose_with_callable
+ c = Class.new {
+ def f(x) x * 2 end
+ }
+ c2 = Class.new {
+ def call(x) x + 1 end
+ }
+ f = c.new.method(:f)
+ g = c2.new
+
+ assert_equal(6, (f << g).call(2))
+ assert_equal(5, (f >> g).call(2))
+ end
+
+ def test_compose_with_noncallable
+ c = Class.new {
+ def f(x) x * 2 end
+ }
+ f = c.new.method(:f)
+
+ assert_raise(TypeError) {
+ f << 5
+ }
+ assert_raise(TypeError) {
+ f >> 5
+ }
+ end
+
+ def test_umethod_bind_call
+ foo = Base.instance_method(:foo)
+ assert_equal(:base, foo.bind_call(Base.new))
+ assert_equal(:base, foo.bind_call(Derived.new))
+
+ plus = Integer.instance_method(:+)
+ assert_equal(3, plus.bind_call(1, 2))
+ end
+
+ def test_method_list
+ # chkbuild lists all methods.
+ # The following code emulate this listing.
+
+ # use_symbol = Object.instance_methods[0].is_a?(Symbol)
+ nummodule = nummethod = 0
+ mods = []
+ ObjectSpace.each_object(Module) {|m| mods << m if String === m.name }
+ mods = mods.sort_by {|m| m.name }
+ mods.each {|mod|
+ nummodule += 1
+ mc = mod.kind_of?(Class) ? "class" : "module"
+ puts_line = "#{mc} #{mod.name} #{(mod.ancestors - [mod]).inspect}"
+ puts_line = puts_line # prevent unused var warning
+ mod.singleton_methods(false).sort.each {|methname|
+ nummethod += 1
+ meth = mod.method(methname)
+ line = "#{mod.name}.#{methname} #{meth.arity}"
+ line << " not-implemented" if !mod.respond_to?(methname)
+ # puts line
+ }
+ ms = mod.instance_methods(false)
+ if true or use_symbol
+ ms << :initialize if mod.private_instance_methods(false).include? :initialize
+ else
+ ms << "initialize" if mod.private_instance_methods(false).include? "initialize"
+ end
+
+ ms.sort.each {|methname|
+ nummethod += 1
+ meth = mod.instance_method(methname)
+ line = "#{mod.name}\##{methname} #{meth.arity}"
+ line << " not-implemented" if /\(not-implemented\)/ =~ meth.inspect
+ # puts line
+ }
+ }
+ # puts "#{nummodule} modules, #{nummethod} methods"
+
+ assert_operator nummodule, :>, 0
+ assert_operator nummethod, :>, 0
+ end
+
+ def test_invalidating_CC_ASAN
+ assert_ruby_status(['-e', 'using Module.new'])
+ end
+
+ def test_kwarg_eval_memory_leak
+ assert_no_memory_leak([], "", <<~RUBY, rss: true, limit: 1.2)
+ obj = Object.new
+ def obj.test(**kwargs) = nil
+
+ 100_000.times do
+ eval("obj.test(foo: 123)")
+ end
+ RUBY
+ end
+
+ def test_super_with_splat
+ c = Class.new {
+ attr_reader :x
+
+ def initialize(*args)
+ @x, _ = args
+ end
+ }
+ b = Class.new(c) { def initialize(...) = super }
+ a = Class.new(b) { def initialize(*args) = super }
+ obj = a.new(1, 2, 3)
+ assert_equal 1, obj.x
+ end
+
+ def test_warn_unused_block
+ assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
+ def foo = nil
+ foo{} # warn
+ send(:foo){} # don't warn because it uses send
+ b = Proc.new{}
+ foo(&b) # warn
+ RUBY
+ errstr = err.join("\n")
+ assert_equal 2, err.size, errstr
+
+ assert_match(/-:2: warning/, errstr)
+ assert_match(/-:5: warning/, errstr)
+ end
+
+ assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
+ def foo = nil
+ 10.times{foo{}} # warn once
+ RUBY
+ assert_equal 1, err.size
+ end
+
+ assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
+ def foo = nil; b = nil
+ foo(&b) # no warning
+ 1.object_id{} # no warning because it is written in C
+
+ class C
+ def initialize
+ end
+ end
+ C.new{} # no warning
+
+ RUBY
+ assert_equal 0, err.size
+ end
+
+ assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
+ class C0
+ def f1 = nil
+ def f2 = nil
+ def f3 = nil
+ def f4 = nil
+ def f5 = nil
+ def f6 = nil
+ end
+
+ class C1 < C0
+ def f1 = super # zsuper / use
+ def f2 = super() # super / use
+ def f3(&_) = super(&_) # super / use
+ def f4 = super(&nil) # super / unuse
+ def f5 = super(){} # super / unuse
+ def f6 = super{} # zsuper / unuse
+ end
+
+ C1.new.f1{} # no warning
+ C1.new.f2{} # no warning
+ C1.new.f3{} # no warning
+ C1.new.f4{} # warning
+ C1.new.f5{} # warning
+ C1.new.f6{} # warning
+ RUBY
+ assert_equal 3, err.size, err.join("\n")
+ assert_match(/-:22: warning.+f4/, err.join)
+ assert_match(/-:23: warning.+f5/, err.join)
+ assert_match(/-:24: warning.+f6/, err.join)
+ end
+
+ assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
+ class C0
+ def f = yield
+ end
+
+ class C1 < C0
+ def f = nil
+ end
+
+ C1.new.f{} # do not warn on duck typing
+ RUBY
+ assert_equal 0, err.size, err.join("\n")
+ end
+
+ assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
+ def foo(*, &block) = block
+ def bar(buz, ...) = foo(buz, ...)
+ bar(:test) {} # do not warn because of forwarding
+ RUBY
+ assert_equal 0, err.size, err.join("\n")
+ end
+ end
end
diff --git a/test/ruby/test_method_cache.rb b/test/ruby/test_method_cache.rb
new file mode 100644
index 0000000000..2ed89e47bf
--- /dev/null
+++ b/test/ruby/test_method_cache.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+class TestMethodCache < Test::Unit::TestCase
+ def test_undef
+ # clear same
+ c0 = Class.new do
+ def foo; end
+ undef foo
+ end
+
+ assert_raise(NoMethodError) do
+ c0.new.foo
+ end
+
+ c0.class_eval do
+ def foo; :ok; end
+ end
+
+ assert_equal :ok, c0.new.foo
+ end
+
+ def test_undef_with_subclasses
+ # with subclasses
+ c0 = Class.new do
+ def foo; end
+ undef foo
+ end
+
+ _c1 = Class.new(c0)
+
+ assert_raise(NoMethodError) do
+ c0.new.foo
+ end
+
+ c0.class_eval do
+ def foo; :ok; end
+ end
+
+ assert_equal :ok, c0.new.foo
+ end
+
+ def test_undef_with_subclasses_complicated
+ c0 = Class.new{ def foo; end }
+ c1 = Class.new(c0){ undef foo }
+ c2 = Class.new(c1)
+ c3 = Class.new(c2)
+ _c4 = Class.new(c3)
+
+ assert_raise(NoMethodError) do
+ c3.new.foo
+ end
+
+ c2.class_eval do
+ def foo; :c2; end
+ end
+
+ assert_raise(NoMethodError) do
+ c1.new.foo
+ end
+
+ assert_equal :c2, c3.new.foo
+ end
+
+ def test_negative_cache_with_and_without_subclasses
+ c0 = Class.new{}
+ c1 = Class.new(c0){}
+ c0.new.foo rescue nil
+ c1.new.foo rescue nil
+ c1.module_eval{ def foo = :c1 }
+ c0.module_eval{ def foo = :c0 }
+
+ assert_equal :c0, c0.new.foo
+ end
+end
+
diff --git a/test/ruby/test_mixed_unicode_escapes.rb b/test/ruby/test_mixed_unicode_escapes.rb
index f0b4cc691f..a30b5c19f5 100644
--- a/test/ruby/test_mixed_unicode_escapes.rb
+++ b/test/ruby/test_mixed_unicode_escapes.rb
@@ -18,12 +18,12 @@ class TestMixedUnicodeEscape < Test::Unit::TestCase
assert_raise(SyntaxError) { eval %q("\u{1234}é")}
# also should not work for Regexp
- assert_raise(SyntaxError) { eval %q(/#{"\u1234"}#{"é"}/)}
assert_raise(RegexpError) { eval %q(/\u{1234}#{nil}é/)}
assert_raise(RegexpError) { eval %q(/é#{nil}\u1234/)}
# String interpolation turns into an expression and we get
# a different kind of error, but we still can't mix these
+ assert_raise(Encoding::CompatibilityError) { eval %q(/#{"\u1234"}#{"é"}/)}
assert_raise(Encoding::CompatibilityError) { eval %q("\u{1234}#{nil}é")}
assert_raise(Encoding::CompatibilityError) { eval %q("é#{nil}\u1234")}
end
diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb
index 076ea0901f..9ed6c1e321 100644
--- a/test/ruby/test_module.rb
+++ b/test/ruby/test_module.rb
@@ -9,29 +9,31 @@ 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
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
+ @deprecated = Warning[:deprecated]
+ Warning[:deprecated] = true
end
def teardown
$VERBOSE = @verbose
+ Warning[:deprecated] = @deprecated
end
def test_LT_0
@@ -85,8 +87,11 @@ class TestModule < Test::Unit::TestCase
private :user3
end
- module Other
- def other
+ OtherSetup = -> do
+ remove_const :Other if defined? ::TestModule::Other
+ module Other
+ def other
+ end
end
end
@@ -220,6 +225,8 @@ class TestModule < Test::Unit::TestCase
@@class_eval = 'b'
def test_class_eval
+ OtherSetup.call
+
Other.class_eval("CLASS_EVAL = 1")
assert_equal(1, Other::CLASS_EVAL)
assert_include(Other.constants, :CLASS_EVAL)
@@ -246,6 +253,14 @@ class TestModule < Test::Unit::TestCase
assert_operator(Math, :const_defined?, "PI")
assert_not_operator(Math, :const_defined?, :IP)
assert_not_operator(Math, :const_defined?, "IP")
+
+ # Test invalid symbol name
+ # [Bug #20245]
+ EnvUtil.under_gc_stress do
+ assert_raise(EncodingError) do
+ Math.const_defined?("\xC3")
+ end
+ end
end
def each_bad_constants(m, &b)
@@ -260,7 +275,7 @@ class TestModule < Test::Unit::TestCase
].each do |name, msg|
expected = "wrong constant name %s" % name
msg = "#{msg}#{': ' if msg}wrong constant name #{name.dump}"
- assert_raise_with_message(NameError, expected, "#{msg} to #{m}") do
+ assert_raise_with_message(NameError, Regexp.compile(Regexp.quote(expected)), "#{msg} to #{m}") do
yield name
end
end
@@ -294,11 +309,18 @@ class TestModule < Test::Unit::TestCase
end
def test_nested_get
- assert_equal Other, Object.const_get([self.class, Other].join('::'))
+ OtherSetup.call
+
+ assert_equal Other, Object.const_get([self.class, 'Other'].join('::'))
assert_equal User::USER, self.class.const_get([User, 'USER'].join('::'))
+ assert_raise(NameError) {
+ Object.const_get([self.class.name, 'String'].join('::'))
+ }
end
def test_nested_get_symbol
+ OtherSetup.call
+
const = [self.class, Other].join('::').to_sym
assert_raise(NameError) {Object.const_get(const)}
@@ -325,12 +347,17 @@ class TestModule < Test::Unit::TestCase
end
def test_nested_defined
+ OtherSetup.call
+
assert_send([Object, :const_defined?, [self.class.name, 'Other'].join('::')])
assert_send([self.class, :const_defined?, 'User::USER'])
assert_not_send([self.class, :const_defined?, 'User::Foo'])
+ assert_not_send([Object, :const_defined?, [self.class.name, 'String'].join('::')])
end
def test_nested_defined_symbol
+ OtherSetup.call
+
const = [self.class, Other].join('::').to_sym
assert_raise(NameError) {Object.const_defined?(const)}
@@ -338,6 +365,17 @@ class TestModule < Test::Unit::TestCase
assert_raise(NameError) {self.class.const_defined?(const)}
end
+ def test_nested_defined_inheritance
+ assert_send([Object, :const_defined?, [self.class.name, 'User', 'MIXIN'].join('::')])
+ assert_send([self.class, :const_defined?, 'User::MIXIN'])
+ assert_send([Object, :const_defined?, 'File::SEEK_SET'])
+
+ # const_defined? with `false`
+ assert_not_send([Object, :const_defined?, [self.class.name, 'User', 'MIXIN'].join('::'), false])
+ assert_not_send([self.class, :const_defined?, 'User::MIXIN', false])
+ assert_not_send([Object, :const_defined?, 'File::SEEK_SET', false])
+ end
+
def test_nested_defined_bad_class
assert_raise(TypeError) do
self.class.const_defined?('User::USER::Foo')
@@ -345,6 +383,8 @@ class TestModule < Test::Unit::TestCase
end
def test_const_set
+ OtherSetup.call
+
assert_not_operator(Other, :const_defined?, :KOALA)
Other.const_set(:KOALA, 99)
assert_operator(Other, :const_defined?, :KOALA)
@@ -372,19 +412,7 @@ class TestModule < Test::Unit::TestCase
assert_equal([:MIXIN, :USER], User.constants.sort)
end
- def test_self_initialize_copy
- bug9535 = '[ruby-dev:47989] [Bug #9535]'
- m = Module.new do
- def foo
- :ok
- end
- initialize_copy(self)
- end
- assert_equal(:ok, Object.new.extend(m).foo, bug9535)
- end
-
def test_initialize_copy_empty
- bug9813 = '[ruby-dev:48182] [Bug #9813]'
m = Module.new do
def x
end
@@ -394,15 +422,61 @@ class TestModule < Test::Unit::TestCase
assert_equal([:x], m.instance_methods)
assert_equal([:@x], m.instance_variables)
assert_equal([:X], m.constants)
- m.module_eval do
- initialize_copy(Module.new)
+
+ m = Class.new(Module) do
+ def initialize_copy(other)
+ # leave uninitialized
+ end
+ end.new.dup
+ c = Class.new
+ assert_operator(c.include(m), :<, m)
+ cp = Module.instance_method(:initialize_copy)
+ assert_raise(TypeError) do
+ cp.bind_call(m, Module.new)
+ end
+ end
+
+ class Bug18185 < Module
+ module InstanceMethods
+ end
+ attr_reader :ancestor_list
+ def initialize
+ @ancestor_list = ancestors
+ include InstanceMethods
+ end
+ class Foo
+ attr_reader :key
+ def initialize(key:)
+ @key = key
+ end
+ end
+ end
+
+ def test_module_subclass_initialize
+ mod = Bug18185.new
+ c = Class.new(Bug18185::Foo) do
+ include mod
end
- assert_empty(m.instance_methods, bug9813)
- assert_empty(m.instance_variables, bug9813)
- assert_empty(m.constants, bug9813)
+ anc = c.ancestors
+ assert_include(anc, mod)
+ assert_equal(1, anc.count(BasicObject), ->{anc.inspect})
+ b = c.new(key: 1)
+ assert_equal(1, b.key)
+ assert_not_include(mod.ancestor_list, BasicObject)
+ end
+
+ def test_module_collected_extended_object
+ m1 = labeled_module("m1")
+ m2 = labeled_module("m2")
+ Object.new.extend(m1)
+ GC.start
+ m1.include(m2)
+ assert_equal([m1, m2], m1.ancestors)
end
def test_dup
+ OtherSetup.call
+
bug6454 = '[ruby-core:45132]'
a = Module.new
@@ -444,6 +518,189 @@ class TestModule < Test::Unit::TestCase
assert_raise(ArgumentError) { Module.new { include } }
end
+ def test_include_before_initialize
+ m = Class.new(Module) do
+ def initialize(...)
+ include Enumerable
+ super
+ end
+ end.new
+ assert_operator(m, :<, Enumerable)
+ end
+
+ def test_prepend_self
+ m = Module.new
+ assert_equal([m], m.ancestors)
+ m.prepend(m) rescue nil
+ assert_equal([m], m.ancestors)
+ end
+
+ def test_bug17590
+ m = Module.new
+ c = Class.new
+ c.prepend(m)
+ c.include(m)
+ m.prepend(m) rescue nil
+ m2 = Module.new
+ m2.prepend(m)
+ c.include(m2)
+
+ assert_equal([m, c, m2] + Object.ancestors, c.ancestors)
+ end
+
+ def test_prepend_works_with_duped_classes
+ m = Module.new
+ a = Class.new do
+ def b; 2 end
+ prepend m
+ end
+ a2 = a.dup.new
+ a.class_eval do
+ alias _b b
+ def b; 1 end
+ end
+ assert_equal(2, a2.b)
+ end
+
+ def test_ancestry_of_duped_classes
+ m = Module.new
+ sc = Class.new
+ a = Class.new(sc) do
+ def b; 2 end
+ prepend m
+ end
+
+ a2 = a.dup.new
+
+ assert_kind_of Object, a2
+ assert_kind_of sc, a2
+ refute_kind_of a, a2
+ assert_kind_of m, a2
+
+ assert_kind_of Class, a2.class
+ assert_kind_of sc.singleton_class, a2.class
+ assert_same sc, a2.class.superclass
+ end
+
+ def test_gc_prepend_chain
+ assert_ruby_status([], <<-EOS)
+ 10000.times { |i|
+ m1 = Module.new do
+ def foo; end
+ end
+ m2 = Module.new do
+ prepend m1
+ def bar; end
+ end
+ m3 = Module.new do
+ def baz; end
+ prepend m2
+ end
+ Class.new do
+ prepend m3
+ end
+ }
+ GC.start
+ EOS
+ end
+
+ def test_refine_module_then_include
+ assert_separately([], "#{<<~"end;"}\n")
+ module M
+ end
+ class C
+ include M
+ end
+ module RefinementBug
+ refine M do
+ def refined_method
+ :rm
+ end
+ end
+ end
+ using RefinementBug
+
+ class A
+ include M
+ end
+
+ assert_equal(:rm, C.new.refined_method)
+ end;
+ end
+
+ def test_include_into_module_already_included
+ c = Class.new{def foo; [:c] end}
+ modules = lambda do ||
+ sub = Class.new(c){def foo; [:sc] + super end}
+ [
+ Module.new{def foo; [:m1] + super end},
+ Module.new{def foo; [:m2] + super end},
+ Module.new{def foo; [:m3] + super end},
+ sub,
+ sub.new
+ ]
+ end
+
+ m1, m2, m3, sc, o = modules.call
+ assert_equal([:sc, :c], o.foo)
+ sc.include m1
+ assert_equal([:sc, :m1, :c], o.foo)
+ m1.include m2
+ assert_equal([:sc, :m1, :m2, :c], o.foo)
+ m2.include m3
+ assert_equal([:sc, :m1, :m2, :m3, :c], o.foo)
+
+ m1, m2, m3, sc, o = modules.call
+ sc.prepend m1
+ assert_equal([:m1, :sc, :c], o.foo)
+ m1.include m2
+ assert_equal([:m1, :m2, :sc, :c], o.foo)
+ m2.include m3
+ assert_equal([:m1, :m2, :m3, :sc, :c], o.foo)
+
+ m1, m2, m3, sc, o = modules.call
+ sc.include m2
+ assert_equal([:sc, :m2, :c], o.foo)
+ sc.prepend m1
+ assert_equal([:m1, :sc, :m2, :c], o.foo)
+ m1.include m2
+ assert_equal([:m1, :sc, :m2, :c], o.foo)
+ m1.include m3
+ assert_equal([:m1, :m3, :sc, :m2, :c], o.foo)
+
+ m1, m2, m3, sc, o = modules.call
+ sc.include m3
+ sc.include m2
+ assert_equal([:sc, :m2, :m3, :c], o.foo)
+ sc.prepend m1
+ assert_equal([:m1, :sc, :m2, :m3, :c], o.foo)
+ m1.include m2
+ m1.include m3
+ assert_equal([:m1, :sc, :m2, :m3, :c], o.foo)
+
+ m1, m2, m3, sc, o = modules.call
+ assert_equal([:sc, :c], o.foo)
+ sc.prepend m1
+ assert_equal([:m1, :sc, :c], o.foo)
+ m1.prepend m2
+ assert_equal([:m2, :m1, :sc, :c], o.foo)
+ m2.prepend m3
+ assert_equal([:m3, :m2, :m1, :sc, :c], o.foo)
+ m1, m2, m3, sc, o = modules.call
+ sc.include m1
+ assert_equal([:sc, :m1, :c], o.foo)
+ sc.prepend m2
+ assert_equal([:m2, :sc, :m1, :c], o.foo)
+ sc.prepend m3
+ assert_equal([:m3, :m2, :sc, :m1, :c], o.foo)
+ m1, m2, m3, sc, o = modules.call
+ sc.include m1
+ assert_equal([:sc, :m1, :c], o.foo)
+ m2.prepend m3
+ m1.include m2
+ assert_equal([:sc, :m1, :m3, :m2, :c], o.foo)
+ end
+
def test_included_modules
assert_equal([], Mixin.included_modules)
assert_equal([Mixin], User.included_modules)
@@ -454,6 +711,92 @@ class TestModule < Test::Unit::TestCase
assert_equal([Comparable, Kernel], String.included_modules - mixins)
end
+ def test_included_modules_with_prepend
+ m1 = Module.new
+ m2 = Module.new
+ m3 = Module.new
+
+ m2.prepend m1
+ m3.include m2
+ assert_equal([m1, m2], m3.included_modules)
+ end
+
+ def test_include_with_prepend
+ c = Class.new{def m; [:c] end}
+ p = Module.new{def m; [:p] + super end}
+ q = Module.new{def m; [:q] + super end; include p}
+ r = Module.new{def m; [:r] + super end; prepend q}
+ s = Module.new{def m; [:s] + super end; include r}
+ a = Class.new(c){def m; [:a] + super end; prepend p; include s}
+ assert_equal([:p, :a, :s, :q, :r, :c], a.new.m)
+ end
+
+ def test_prepend_after_include
+ c = Class.new{def m; [:c] end}
+ sc = Class.new(c){def m; [:sc] + super end}
+ m = Module.new{def m; [:m] + super end}
+ sc.include m
+ sc.prepend m
+ sc.prepend m
+ assert_equal([:m, :sc, :m, :c], sc.new.m)
+
+ c = Class.new{def m; [:c] end}
+ sc = Class.new(c){def m; [:sc] + super end}
+ m0 = Module.new{def m; [:m0] + super end}
+ m1 = Module.new{def m; [:m1] + super end}
+ m1.prepend m0
+ sc.include m1
+ sc.prepend m1
+ assert_equal([:m0, :m1, :sc, :m0, :m1, :c], sc.new.m)
+ sc.prepend m
+ assert_equal([:m, :m0, :m1, :sc, :m0, :m1, :c], sc.new.m)
+ sc.prepend m1
+ assert_equal([:m, :m0, :m1, :sc, :m0, :m1, :c], sc.new.m)
+
+
+ c = Class.new{def m; [:c] end}
+ sc = Class.new(c){def m; [:sc] + super end}
+ m0 = Module.new{def m; [:m0] + super end}
+ m1 = Module.new{def m; [:m1] + super end}
+ m1.include m0
+ sc.include m1
+ sc.prepend m
+ sc.prepend m1
+ sc.prepend m1
+ assert_equal([:m1, :m0, :m, :sc, :m1, :m0, :c], sc.new.m)
+ end
+
+ def test_include_into_module_after_prepend_bug_20871
+ bar = Module.new{def bar; 'bar'; end}
+ foo = Module.new{def foo; 'foo'; end}
+ m = Module.new
+ c = Class.new{include m}
+ m.prepend bar
+ Class.new{include m}
+ m.include foo
+ assert_include c.ancestors, foo
+ assert_equal "foo", c.new.foo
+ end
+
+ def test_protected_include_into_included_module
+ m1 = Module.new do
+ def other_foo(other)
+ other.foo
+ end
+
+ protected
+ def foo
+ :ok
+ end
+ end
+ m2 = Module.new
+ c1 = Class.new { include m2 }
+ c2 = Class.new { include m2 }
+ m2.include(m1)
+
+ assert_equal :ok, c1.new.other_foo(c2.new)
+ end
+
def test_instance_methods
assert_equal([:user, :user2], User.instance_methods(false).sort)
assert_equal([:user, :user2, :mixin].sort, User.instance_methods(true).sort)
@@ -462,26 +805,53 @@ class TestModule < Test::Unit::TestCase
assert_equal([:cClass], (class << CClass; self; end).instance_methods(false))
assert_equal([], (class << BClass; self; end).instance_methods(false))
assert_equal([:cm2], (class << AClass; self; end).instance_methods(false))
- # Ruby 1.8 feature change:
- # #instance_methods includes protected methods.
- #assert_equal([:aClass], AClass.instance_methods(false))
assert_equal([:aClass, :aClass2], AClass.instance_methods(false).sort)
assert_equal([:aClass, :aClass2],
(AClass.instance_methods(true) - Object.instance_methods(true)).sort)
end
def test_method_defined?
- assert !User.method_defined?(:wombat)
- assert User.method_defined?(:mixin)
- assert User.method_defined?(:user)
- assert User.method_defined?(:user2)
- assert !User.method_defined?(:user3)
+ [User, Class.new{include User}, Class.new{prepend User}].each do |klass|
+ [[], [true]].each do |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 !User.method_defined?("wombat")
- assert User.method_defined?("mixin")
- assert User.method_defined?("user")
- assert User.method_defined?("user2")
- assert !User.method_defined?("user3")
+ 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_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_method_not_defined?(c, [:user, false])
+ c.define_method(:user){}
+ assert_method_defined?(c, [:user, false])
+
+ assert_method_not_defined?(c, [:mixin, false])
+ c.define_method(:mixin){}
+ assert_method_defined?(c, [:mixin, false])
+
+ assert_method_not_defined?(c, [:userx, false])
+ c.define_method(:userx){}
+ assert_method_defined?(c, [:userx, false])
+
+ # cleanup
+ User.class_eval do
+ remove_const :FOO
+ end
end
def module_exec_aux
@@ -512,6 +882,14 @@ class TestModule < Test::Unit::TestCase
def dynamically_added_method_4; end
end
assert_method_defined?(User, :dynamically_added_method_4)
+
+ # cleanup
+ User.class_eval do
+ remove_method :dynamically_added_method_1
+ remove_method :dynamically_added_method_2
+ remove_method :dynamically_added_method_3
+ remove_method :dynamically_added_method_4
+ end
end
def test_module_eval
@@ -526,6 +904,28 @@ class TestModule < Test::Unit::TestCase
assert_equal("Integer", Integer.name)
assert_equal("TestModule::Mixin", Mixin.name)
assert_equal("TestModule::User", User.name)
+
+ assert_predicate Integer.name, :frozen?
+ assert_predicate Mixin.name, :frozen?
+ assert_predicate User.name, :frozen?
+ end
+
+ def test_accidental_singleton_naming_with_module
+ o = Object.new
+ assert_nil(o.singleton_class.name)
+ class << o
+ module Hi; end
+ end
+ assert_nil(o.singleton_class.name)
+ end
+
+ def test_accidental_singleton_naming_with_class
+ o = Object.new
+ assert_nil(o.singleton_class.name)
+ class << o
+ class Hi; end
+ end
+ assert_nil(o.singleton_class.name)
end
def test_classpath
@@ -533,13 +933,13 @@ class TestModule < Test::Unit::TestCase
n = Module.new
m.const_set(:N, n)
assert_nil(m.name)
- assert_nil(n.name)
+ assert_match(/::N$/, n.name)
assert_equal([:N], m.constants)
m.module_eval("module O end")
assert_equal([:N, :O], m.constants.sort)
m.module_eval("class C; end")
assert_equal([:C, :N, :O], m.constants.sort)
- assert_nil(m::N.name)
+ assert_match(/::N$/, m::N.name)
assert_match(/\A#<Module:.*>::O\z/, m::O.name)
assert_match(/\A#<Module:.*>::C\z/, m::C.name)
self.class.const_set(:M, m)
@@ -547,12 +947,21 @@ class TestModule < Test::Unit::TestCase
assert_equal(prefix+"N", m.const_get(:N).name)
assert_equal(prefix+"O", m.const_get(:O).name)
assert_equal(prefix+"C", m.const_get(:C).name)
+ c = m.class_eval("Bug15891 = Class.new.freeze")
+ assert_equal(prefix+"Bug15891", c.name)
+ ensure
+ self.class.class_eval {remove_const(:M)}
end
def test_private_class_method
assert_raise(ExpectedException) { AClass.cm1 }
assert_raise(ExpectedException) { AClass.cm3 }
assert_equal("cm1cm2cm3", AClass.cm2)
+
+ c = Class.new(AClass)
+ c.class_eval {private_class_method [:cm1, :cm2]}
+ assert_raise(NoMethodError, /private method/) {c.cm1}
+ assert_raise(NoMethodError, /private method/) {c.cm2}
end
def test_private_instance_methods
@@ -575,6 +984,11 @@ class TestModule < Test::Unit::TestCase
assert_equal("cm1", MyClass.cm1)
assert_equal("cm1cm2cm3", MyClass.cm2)
assert_raise(ExpectedException) { eval "MyClass.cm3" }
+
+ c = Class.new(AClass)
+ c.class_eval {public_class_method [:cm1, :cm2]}
+ assert_equal("cm1", c.cm1)
+ assert_equal("cm1cm2cm3", c.cm2)
end
def test_public_instance_methods
@@ -582,12 +996,125 @@ class TestModule < Test::Unit::TestCase
assert_equal([:bClass1], BClass.public_instance_methods(false))
end
+ def test_undefined_instance_methods
+ assert_equal([], AClass.undefined_instance_methods)
+ assert_equal([], BClass.undefined_instance_methods)
+ c = Class.new(AClass) {undef aClass}
+ assert_equal([:aClass], c.undefined_instance_methods)
+ c = Class.new(c)
+ assert_equal([], c.undefined_instance_methods)
+ end
+
+ def test_s_public
+ o = (c = Class.new(AClass)).new
+ assert_raise(NoMethodError, /private method/) {o.aClass1}
+ assert_raise(NoMethodError, /protected method/) {o.aClass2}
+ c.class_eval {public :aClass1}
+ assert_equal(:aClass1, o.aClass1)
+
+ o = (c = Class.new(AClass)).new
+ c.class_eval {public :aClass1, :aClass2}
+ assert_equal(:aClass1, o.aClass1)
+ assert_equal(:aClass2, o.aClass2)
+
+ o = (c = Class.new(AClass)).new
+ c.class_eval {public [:aClass1, :aClass2]}
+ assert_equal(:aClass1, o.aClass1)
+ assert_equal(:aClass2, o.aClass2)
+
+ o = AClass.new
+ assert_equal(:aClass, o.aClass)
+ assert_raise(NoMethodError, /private method/) {o.aClass1}
+ assert_raise(NoMethodError, /protected method/) {o.aClass2}
+ end
+
+ def test_s_private
+ o = (c = Class.new(AClass)).new
+ assert_equal(:aClass, o.aClass)
+ c.class_eval {private :aClass}
+ assert_raise(NoMethodError, /private method/) {o.aClass}
+
+ o = (c = Class.new(AClass)).new
+ c.class_eval {private :aClass, :aClass2}
+ assert_raise(NoMethodError, /private method/) {o.aClass}
+ assert_raise(NoMethodError, /private method/) {o.aClass2}
+
+ o = (c = Class.new(AClass)).new
+ c.class_eval {private [:aClass, :aClass2]}
+ assert_raise(NoMethodError, /private method/) {o.aClass}
+ assert_raise(NoMethodError, /private method/) {o.aClass2}
+
+ o = AClass.new
+ assert_equal(:aClass, o.aClass)
+ assert_raise(NoMethodError, /private method/) {o.aClass1}
+ assert_raise(NoMethodError, /protected method/) {o.aClass2}
+ end
+
+ def test_s_protected
+ aclass = Class.new(AClass) do
+ def _aClass(o) o.aClass; end
+ def _aClass1(o) o.aClass1; end
+ def _aClass2(o) o.aClass2; end
+ end
+
+ o = (c = Class.new(aclass)).new
+ assert_equal(:aClass, o.aClass)
+ c.class_eval {protected :aClass}
+ assert_raise(NoMethodError, /protected method/) {o.aClass}
+ assert_equal(:aClass, c.new._aClass(o))
+
+ o = (c = Class.new(aclass)).new
+ c.class_eval {protected :aClass, :aClass1}
+ assert_raise(NoMethodError, /protected method/) {o.aClass}
+ assert_raise(NoMethodError, /protected method/) {o.aClass1}
+ assert_equal(:aClass, c.new._aClass(o))
+ assert_equal(:aClass1, c.new._aClass1(o))
+
+ o = (c = Class.new(aclass)).new
+ c.class_eval {protected [:aClass, :aClass1]}
+ assert_raise(NoMethodError, /protected method/) {o.aClass}
+ assert_raise(NoMethodError, /protected method/) {o.aClass1}
+ assert_equal(:aClass, c.new._aClass(o))
+ assert_equal(:aClass1, c.new._aClass1(o))
+
+ o = AClass.new
+ assert_equal(:aClass, o.aClass)
+ assert_raise(NoMethodError, /private method/) {o.aClass1}
+ assert_raise(NoMethodError, /protected method/) {o.aClass2}
+ end
+
+ def test_visibility_method_return_value
+ no_arg_results = nil
+ c = Module.new do
+ singleton_class.send(:public, :public, :private, :protected, :module_function)
+ def foo; end
+ def bar; end
+ no_arg_results = [public, private, protected, module_function]
+ end
+
+ assert_equal([nil]*4, no_arg_results)
+
+ assert_equal(:foo, c.private(:foo))
+ assert_equal(:foo, c.public(:foo))
+ assert_equal(:foo, c.protected(:foo))
+ assert_equal(:foo, c.module_function(:foo))
+
+ assert_equal([:foo, :bar], c.private(:foo, :bar))
+ assert_equal([:foo, :bar], c.public(:foo, :bar))
+ assert_equal([:foo, :bar], c.protected(:foo, :bar))
+ assert_equal([:foo, :bar], c.module_function(:foo, :bar))
+ end
+
def test_s_constants
c1 = Module.constants
Object.module_eval "WALTER = 99"
c2 = Module.constants
assert_equal([:WALTER], c2 - c1)
+ Object.class_eval do
+ remove_const :WALTER
+ end
+
assert_equal([], Module.constants(true))
assert_equal([], Module.constants(false))
@@ -650,14 +1177,19 @@ class TestModule < Test::Unit::TestCase
end
def test_attr_obsoleted_flag
- c = Class.new
- c.class_eval do
+ c = Class.new do
+ extend Test::Unit::Assertions
+ extend Test::Unit::CoreAssertions
def initialize
@foo = :foo
@bar = :bar
end
- attr :foo, true
- attr :bar, false
+ assert_deprecated_warning(/optional boolean argument/) do
+ attr :foo, true
+ end
+ assert_deprecated_warning(/optional boolean argument/) do
+ attr :bar, false
+ end
end
o = c.new
assert_equal(true, o.respond_to?(:foo))
@@ -666,6 +1198,32 @@ class TestModule < Test::Unit::TestCase
assert_equal(false, o.respond_to?(:bar=))
end
+ def test_attr_public_at_toplevel
+ s = Object.new
+ TOPLEVEL_BINDING.eval(<<-END).call(s.singleton_class)
+ proc do |c|
+ c.send(:attr_accessor, :x)
+ c.send(:attr, :y)
+ c.send(:attr_reader, :z)
+ c.send(:attr_writer, :w)
+ end
+ END
+ assert_nil s.x
+ s.x = 1
+ assert_equal 1, s.x
+
+ assert_nil s.y
+ s.instance_variable_set(:@y, 2)
+ assert_equal 2, s.y
+
+ assert_nil s.z
+ s.instance_variable_set(:@z, 3)
+ assert_equal 3, s.z
+
+ s.w = 4
+ assert_equal 4, s.instance_variable_get(:@w)
+ end
+
def test_const_get_evaled
c1 = Class.new
c2 = Class.new(c1)
@@ -676,6 +1234,7 @@ class TestModule < Test::Unit::TestCase
assert_equal(:foo, c2.const_get(:Foo))
assert_raise(NameError) { c2.const_get(:Foo, false) }
+ c1.__send__(:remove_const, :Foo)
eval("c1::Foo = :foo")
assert_raise(NameError) { c1::Bar }
assert_raise(NameError) { c2::Bar }
@@ -714,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
@@ -723,10 +1285,6 @@ class TestModule < Test::Unit::TestCase
assert_raise(NameError) { c1.const_get(:foo) }
bug5084 = '[ruby-dev:44200]'
assert_raise(TypeError, bug5084) { c1.const_get(1) }
- bug7574 = '[ruby-dev:46749]'
- assert_raise_with_message(NameError, "wrong constant name \"String\\u0000\"", bug7574) {
- Object.const_get("String\0")
- }
end
def test_const_defined_invalid_name
@@ -734,10 +1292,6 @@ class TestModule < Test::Unit::TestCase
assert_raise(NameError) { c1.const_defined?(:foo) }
bug5084 = '[ruby-dev:44200]'
assert_raise(TypeError, bug5084) { c1.const_defined?(1) }
- bug7574 = '[ruby-dev:46749]'
- assert_raise_with_message(NameError, "wrong constant name \"String\\u0000\"", bug7574) {
- Object.const_defined?("String\0")
- }
end
def test_const_get_no_inherited
@@ -779,8 +1333,6 @@ class TestModule < Test::Unit::TestCase
end
end
include LangModuleSpecInObject
- module LangModuleTop
- end
puts "ok" if LangModuleSpecInObject::LangModuleTop == LangModuleTop
INPUT
@@ -881,6 +1433,39 @@ class TestModule < Test::Unit::TestCase
assert_raise(NameError) do
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 })
+ 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 })
+
+ 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
+ c = Class.new do
+ def foo; :foo end
+ end
+ o = c.new
+ assert_respond_to(o, :foo)
+ assert_not_respond_to(o, :bar)
+ r = c.class_eval {alias_method :bar, :foo}
+ assert_respond_to(o, :bar)
+ assert_equal(:foo, o.bar)
+ assert_equal(:bar, r)
end
def test_undef
@@ -899,8 +1484,8 @@ class TestModule < Test::Unit::TestCase
class << o; self; end.instance_eval { undef_method(:foo) }
end
- %w(object_id __send__ initialize).each do |n|
- assert_in_out_err([], <<-INPUT, [], %r"warning: undefining `#{n}' may cause serious problems$")
+ %w(object_id __id__ __send__ initialize).each do |n|
+ assert_in_out_err([], <<-INPUT, [], %r"warning: undefining '#{n}' may cause serious problems$")
$VERBOSE = false
Class.new.instance_eval { undef_method(:#{n}) }
INPUT
@@ -977,8 +1562,8 @@ class TestModule < Test::Unit::TestCase
end
def test_method_defined
- c = Class.new
- c.class_eval do
+ cl = Class.new
+ def_methods = proc do
def foo; end
def bar; end
def baz; end
@@ -986,43 +1571,69 @@ class TestModule < Test::Unit::TestCase
protected :bar
private :baz
end
+ cl.class_eval(&def_methods)
+ sc = Class.new(cl)
+ mod = Module.new(&def_methods)
+ only_prepend = Class.new{prepend(mod)}
+ empty_prepend = cl.clone
+ empty_prepend.prepend(Module.new)
+ overlap_prepend = cl.clone
+ overlap_prepend.prepend(mod)
+
+ [[], [true], [false]].each do |args|
+ [cl, sc, only_prepend, empty_prepend, overlap_prepend].each do |c|
+ always_false = [sc, only_prepend].include?(c) && args == [false]
- assert_equal(true, c.public_method_defined?(:foo))
- assert_equal(false, c.public_method_defined?(:bar))
- assert_equal(false, c.public_method_defined?(:baz))
+ assert_equal(always_false ? false : true, c.public_method_defined?(:foo, *args))
+ assert_equal(always_false ? false : false, c.public_method_defined?(:bar, *args))
+ assert_equal(always_false ? false : false, c.public_method_defined?(:baz, *args))
- # Test if string arguments are converted to symbols
- assert_equal(true, c.public_method_defined?("foo"))
- assert_equal(false, c.public_method_defined?("bar"))
- assert_equal(false, c.public_method_defined?("baz"))
+ # Test if string arguments are converted to symbols
+ assert_equal(always_false ? false : true, c.public_method_defined?("foo", *args))
+ assert_equal(always_false ? false : false, c.public_method_defined?("bar", *args))
+ assert_equal(always_false ? false : false, c.public_method_defined?("baz", *args))
- assert_equal(false, c.protected_method_defined?(:foo))
- assert_equal(true, c.protected_method_defined?(:bar))
- assert_equal(false, c.protected_method_defined?(:baz))
+ assert_equal(always_false ? false : false, c.protected_method_defined?(:foo, *args))
+ assert_equal(always_false ? false : true, c.protected_method_defined?(:bar, *args))
+ assert_equal(always_false ? false : false, c.protected_method_defined?(:baz, *args))
- # Test if string arguments are converted to symbols
- assert_equal(false, c.protected_method_defined?("foo"))
- assert_equal(true, c.protected_method_defined?("bar"))
- assert_equal(false, c.protected_method_defined?("baz"))
+ # Test if string arguments are converted to symbols
+ assert_equal(always_false ? false : false, c.protected_method_defined?("foo", *args))
+ assert_equal(always_false ? false : true, c.protected_method_defined?("bar", *args))
+ assert_equal(always_false ? false : false, c.protected_method_defined?("baz", *args))
- assert_equal(false, c.private_method_defined?(:foo))
- assert_equal(false, c.private_method_defined?(:bar))
- assert_equal(true, c.private_method_defined?(:baz))
+ assert_equal(always_false ? false : false, c.private_method_defined?(:foo, *args))
+ assert_equal(always_false ? false : false, c.private_method_defined?(:bar, *args))
+ assert_equal(always_false ? false : true, c.private_method_defined?(:baz, *args))
- # Test if string arguments are converted to symbols
- assert_equal(false, c.private_method_defined?("foo"))
- assert_equal(false, c.private_method_defined?("bar"))
- assert_equal(true, c.private_method_defined?("baz"))
+ # Test if string arguments are converted to symbols
+ assert_equal(always_false ? false : false, c.private_method_defined?("foo", *args))
+ assert_equal(always_false ? false : false, c.private_method_defined?("bar", *args))
+ assert_equal(always_false ? false : true, c.private_method_defined?("baz", *args))
+ end
+ end
end
def test_top_public_private
- assert_in_out_err([], <<-INPUT, %w([:foo] [:bar]), [])
+ assert_in_out_err([], <<-INPUT, %w([:foo] [:bar] [:bar,\ :foo] [] [:bar,\ :foo] []), [])
private
def foo; :foo; end
public
def bar; :bar; end
p self.private_methods.grep(/^foo$|^bar$/)
p self.methods.grep(/^foo$|^bar$/)
+
+ private :foo, :bar
+ p self.private_methods.grep(/^foo$|^bar$/).sort
+
+ public :foo, :bar
+ p self.private_methods.grep(/^foo$|^bar$/).sort
+
+ private [:foo, :bar]
+ p self.private_methods.grep(/^foo$|^bar$/).sort
+
+ public [:foo, :bar]
+ p self.private_methods.grep(/^foo$|^bar$/).sort
INPUT
end
@@ -1133,6 +1744,47 @@ class TestModule < Test::Unit::TestCase
assert_equal("TestModule::C\u{df}", c.name, '[ruby-core:24600]')
c = Module.new.module_eval("class X\u{df} < Module; self; end")
assert_match(/::X\u{df}:/, c.new.to_s)
+ ensure
+ Object.send(:remove_const, "C\u{df}")
+ end
+
+
+ def test_const_added
+ eval(<<~RUBY)
+ module TestConstAdded
+ @memo = []
+ class << self
+ attr_accessor :memo
+
+ def const_added(sym)
+ memo << sym
+ end
+ end
+ CONST = 1
+ module SubModule
+ end
+
+ class SubClass
+ end
+ end
+ TestConstAdded::OUTSIDE_CONST = 2
+ module TestConstAdded::OutsideSubModule; end
+ class TestConstAdded::OutsideSubClass; end
+ RUBY
+ TestConstAdded.const_set(:CONST_SET, 3)
+ assert_equal [
+ :CONST,
+ :SubModule,
+ :SubClass,
+ :OUTSIDE_CONST,
+ :OutsideSubModule,
+ :OutsideSubClass,
+ :CONST_SET,
+ ], TestConstAdded.memo
+ ensure
+ if self.class.const_defined? :TestConstAdded
+ self.class.send(:remove_const, :TestConstAdded)
+ end
end
def test_method_added
@@ -1301,6 +1953,17 @@ class TestModule < Test::Unit::TestCase
assert_match(/: warning: previous definition of foo/, stderr)
end
+ def test_module_function_inside_method
+ assert_warn(/calling module_function without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do
+ Module.new do
+ def self.foo
+ module_function
+ end
+ foo
+ end
+ end
+ end
+
def test_protected_singleton_method
klass = Class.new
x = klass.new
@@ -1425,6 +2088,21 @@ class TestModule < Test::Unit::TestCase
RUBY
end
+ def test_private_constant_const_missing
+ c = Class.new
+ c.const_set(:FOO, "foo")
+ c.private_constant(:FOO)
+ class << c
+ attr_reader :const_missing_arg
+ def const_missing(name)
+ @const_missing_arg = name
+ name == :FOO ? const_get(:FOO) : super
+ end
+ end
+ assert_equal("foo", c::FOO)
+ assert_equal(:FOO, c.const_missing_arg)
+ end
+
class PrivateClass
end
private_constant :PrivateClass
@@ -1457,10 +2135,30 @@ class TestModule < Test::Unit::TestCase
c = Class.new
c.const_set(:FOO, "foo")
c.deprecate_constant(:FOO)
- assert_warn(/deprecated/) {c::FOO}
- assert_warn(/#{c}::FOO is deprecated/) {Class.new(c)::FOO}
+ assert_warn(/deprecated/) do
+ Warning[:deprecated] = true
+ c::FOO
+ end
+ assert_warn(/#{c}::FOO is deprecated/) do
+ Warning[:deprecated] = true
+ Class.new(c)::FOO
+ end
bug12382 = '[ruby-core:75505] [Bug #12382]'
- assert_warn(/deprecated/, bug12382) {c.class_eval "FOO"}
+ assert_warn(/deprecated/, bug12382) do
+ Warning[:deprecated] = true
+ c.class_eval "FOO"
+ end
+ assert_warn('') do
+ Warning[:deprecated] = false
+ c::FOO
+ end
+ assert_warn('') do
+ Warning[:deprecated] = false
+ Class.new(c)::FOO
+ end
+ assert_warn(/deprecated/) do
+ c.class_eval {remove_const "FOO"}
+ end
end
def test_constants_with_private_constant
@@ -1669,6 +2367,18 @@ class TestModule < Test::Unit::TestCase
assert_equal(:foo, removed)
end
+ def test_frozen_prepend_remove_method
+ [Module, Class].each do |klass|
+ mod = klass.new do
+ prepend(Module.new)
+ def foo; end
+ end
+ mod.freeze
+ assert_raise(FrozenError, '[Bug #19166]') { mod.send(:remove_method, :foo) }
+ assert_equal([:foo], mod.instance_methods(false))
+ end
+ end
+
def test_prepend_class_ancestors
bug6658 = '[ruby-core:45919]'
m = labeled_module("m")
@@ -1700,7 +2410,7 @@ class TestModule < Test::Unit::TestCase
assert_equal([:c2, :m0, :m1, :m2, :c0], c2.new.x)
m3 = labeled_module("m3") {include m1; prepend m1}
- assert_equal([m3, m0, m1], m3.ancestors)
+ assert_equal([m0, m1, m3, m0, m1], m3.ancestors)
m3 = labeled_module("m3") {prepend m1; include m1}
assert_equal([m0, m1, m3], m3.ancestors)
m3 = labeled_module("m3") {prepend m1; prepend m1}
@@ -1781,6 +2491,137 @@ class TestModule < Test::Unit::TestCase
assert_equal(0, 1 / 2)
end
+ def test_visibility_after_refine_and_visibility_change_with_origin_class
+ m = Module.new
+ c = Class.new do
+ def x; :x end
+ end
+ c.prepend(m)
+ Module.new do
+ refine c do
+ def x; :y end
+ end
+ end
+
+ o1 = c.new
+ o2 = c.new
+ assert_equal(:x, o1.public_send(:x))
+ assert_equal(:x, o2.public_send(:x))
+ o1.singleton_class.send(:private, :x)
+ o2.singleton_class.send(:public, :x)
+
+ assert_raise(NoMethodError) { o1.public_send(:x) }
+ assert_equal(:x, o2.public_send(:x))
+ end
+
+ def test_visibility_after_multiple_refine_and_visibility_change_with_origin_class
+ m = Module.new
+ c = Class.new do
+ def x; :x end
+ end
+ c.prepend(m)
+ Module.new do
+ refine c do
+ def x; :y end
+ end
+ end
+ Module.new do
+ refine c do
+ def x; :z end
+ end
+ end
+
+ o1 = c.new
+ o2 = c.new
+ assert_equal(:x, o1.public_send(:x))
+ assert_equal(:x, o2.public_send(:x))
+ o1.singleton_class.send(:private, :x)
+ o2.singleton_class.send(:public, :x)
+
+ assert_raise(NoMethodError) { o1.public_send(:x) }
+ assert_equal(:x, o2.public_send(:x))
+ end
+
+ def test_visibility_after_refine_and_visibility_change_without_origin_class
+ c = Class.new do
+ def x; :x end
+ end
+ Module.new do
+ refine c do
+ def x; :y end
+ end
+ end
+ o1 = c.new
+ o2 = c.new
+ o1.singleton_class.send(:private, :x)
+ o2.singleton_class.send(:public, :x)
+ assert_raise(NoMethodError) { o1.public_send(:x) }
+ assert_equal(:x, o2.public_send(:x))
+ end
+
+ def test_visibility_after_multiple_refine_and_visibility_change_without_origin_class
+ c = Class.new do
+ def x; :x end
+ end
+ Module.new do
+ refine c do
+ def x; :y end
+ end
+ end
+ Module.new do
+ refine c do
+ def x; :z end
+ end
+ end
+ o1 = c.new
+ o2 = c.new
+ o1.singleton_class.send(:private, :x)
+ o2.singleton_class.send(:public, :x)
+ assert_raise(NoMethodError) { o1.public_send(:x) }
+ assert_equal(:x, o2.public_send(:x))
+ end
+
+ def test_visibility_after_refine_and_visibility_change_with_superclass
+ c = Class.new do
+ def x; :x end
+ end
+ sc = Class.new(c)
+ Module.new do
+ refine sc do
+ def x; :y end
+ end
+ end
+ o1 = sc.new
+ o2 = sc.new
+ o1.singleton_class.send(:private, :x)
+ o2.singleton_class.send(:public, :x)
+ assert_raise(NoMethodError) { o1.public_send(:x) }
+ assert_equal(:x, o2.public_send(:x))
+ end
+
+ def test_visibility_after_multiple_refine_and_visibility_change_with_superclass
+ c = Class.new do
+ def x; :x end
+ end
+ sc = Class.new(c)
+ Module.new do
+ refine sc do
+ def x; :y end
+ end
+ end
+ Module.new do
+ refine sc do
+ def x; :z end
+ end
+ end
+ o1 = sc.new
+ o2 = sc.new
+ o1.singleton_class.send(:private, :x)
+ o2.singleton_class.send(:public, :x)
+ assert_raise(NoMethodError) { o1.public_send(:x) }
+ assert_equal(:x, o2.public_send(:x))
+ end
+
def test_prepend_visibility
bug8005 = '[ruby-core:53106] [Bug #8005]'
c = Class.new do
@@ -1823,6 +2664,33 @@ class TestModule < Test::Unit::TestCase
assert_include(im, mixin, bug8025)
end
+ def test_prepended_module_with_super_and_alias
+ bug16736 = '[Bug #16736]'
+
+ a = labeled_class("A") do
+ def m; "A"; end
+ end
+ m = labeled_module("M") do
+ prepend Module.new
+
+ def self.included(base)
+ base.alias_method :base_m, :m
+ end
+
+ def m
+ super + "M"
+ end
+
+ def m2
+ base_m
+ end
+ end
+ b = labeled_class("B", a) do
+ include m
+ end
+ assert_equal("AM", b.new.m2, bug16736)
+ end
+
def test_prepend_super_in_alias
bug7842 = '[Bug #7842]'
@@ -1941,6 +2809,22 @@ class TestModule < Test::Unit::TestCase
assert_equal([:@@bar], m2.class_variables(false))
end
+ def test_class_variable_in_dup_class
+ a = Class.new do
+ @@a = 'A'
+ def a=(x)
+ @@a = x
+ end
+ def a
+ @@a
+ end
+ end
+
+ b = a.dup
+ b.new.a = 'B'
+ assert_equal 'A', a.new.a, '[ruby-core:17019]'
+ end
+
Bug6891 = '[ruby-core:47241]'
def test_extend_module_with_protected_method
@@ -2001,26 +2885,26 @@ class TestModule < Test::Unit::TestCase
def test_invalid_attr
%W[
+ foo=
foo?
@foo
@@foo
$foo
\u3042$
].each do |name|
- assert_raise_with_message(NameError, /#{Regexp.quote(quote(name))}/) do
+ e = assert_raise(NameError) do
Module.new { attr_accessor name.to_sym }
end
+ assert_equal(name, e.name.to_s)
end
end
- private def quote(name)
- encoding = Encoding.default_internal || Encoding.default_external
- (name.encoding == encoding || name.ascii_only?) ? name : name.inspect
- end
-
class AttrTest
class << self
attr_accessor :cattr
+ def reset
+ self.cattr = nil
+ end
end
attr_accessor :iattr
def ivar
@@ -2030,7 +2914,7 @@ class TestModule < Test::Unit::TestCase
def test_uninitialized_instance_variable
a = AttrTest.new
- assert_warning(/instance variable @ivar not initialized/) do
+ assert_warning('') do
assert_nil(a.ivar)
end
a.instance_variable_set(:@ivar, 42)
@@ -2039,7 +2923,7 @@ class TestModule < Test::Unit::TestCase
end
name = "@\u{5909 6570}"
- assert_warning(/instance variable #{name} not initialized/) do
+ assert_warning('') do
assert_nil(a.instance_eval(name))
end
end
@@ -2063,6 +2947,8 @@ class TestModule < Test::Unit::TestCase
assert_warning '' do
assert_equal(42, AttrTest.cattr)
end
+
+ AttrTest.reset
end
def test_uninitialized_attr_non_object
@@ -2130,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
@@ -2166,31 +3052,6 @@ class TestModule < Test::Unit::TestCase
assert_raise(NoMethodError, bug8284) {Object.remove_const}
end
- def test_include_module_with_constants_does_not_invalidate_method_cache
- assert_in_out_err([], <<-RUBY, %w(123 456 true), [])
- A = 123
-
- class Foo
- def self.a
- A
- end
- end
-
- module M
- A = 456
- end
-
- puts Foo.a
- starting = RubyVM.stat[:global_method_state]
-
- Foo.send(:include, M)
-
- ending = RubyVM.stat[:global_method_state]
- puts Foo.a
- puts starting == ending
- RUBY
- end
-
def test_return_value_of_define_method
retvals = []
Class.new.class_eval do
@@ -2212,7 +3073,7 @@ class TestModule < Test::Unit::TestCase
end
def test_prepend_gc
- assert_separately [], %{
+ assert_ruby_status [], %{
module Foo
end
class Object
@@ -2227,9 +3088,67 @@ class TestModule < Test::Unit::TestCase
}
end
+ def test_prepend_constant_lookup
+ m = Module.new do
+ const_set(:C, :m)
+ end
+ c = Class.new do
+ const_set(:C, :c)
+ prepend m
+ end
+ sc = Class.new(c)
+ # Situation from [Bug #17887]
+ assert_equal(sc.ancestors.take(3), [sc, m, c])
+ assert_equal(:m, sc.const_get(:C))
+ assert_equal(:m, sc::C)
+
+ assert_equal(:c, c::C)
+
+ m.send(:remove_const, :C)
+ assert_equal(:c, sc.const_get(:C))
+ assert_equal(:c, sc::C)
+
+ # Same ancestors, built with include instead of prepend
+ m = Module.new do
+ const_set(:C, :m)
+ end
+ c = Class.new do
+ const_set(:C, :c)
+ end
+ sc = Class.new(c) do
+ include m
+ end
+
+ assert_equal(sc.ancestors.take(3), [sc, m, c])
+ assert_equal(:m, sc.const_get(:C))
+ assert_equal(:m, sc::C)
+
+ m.send(:remove_const, :C)
+ assert_equal(:c, sc.const_get(:C))
+ assert_equal(:c, sc::C)
+
+ # Situation from [Bug #17887], but with modules
+ m = Module.new do
+ const_set(:C, :m)
+ end
+ m2 = Module.new do
+ const_set(:C, :m2)
+ prepend m
+ end
+ c = Class.new do
+ include m2
+ end
+ assert_equal(c.ancestors.take(3), [c, m, m2])
+ assert_equal(:m, c.const_get(:C))
+ assert_equal(:m, c::C)
+ end
+
def test_inspect_segfault
bug_10282 = '[ruby-core:65214] [Bug #10282]'
- assert_separately [], <<-RUBY
+ assert_separately [], "#{<<~"begin;"}\n#{<<~'end;'}"
+ bug_10282 = "#{bug_10282}"
+ begin;
+ line = __LINE__ + 2
module ShallowInspect
def shallow_inspect
"foo"
@@ -2246,9 +3165,22 @@ class TestModule < Test::Unit::TestCase
A.prepend InspectIsShallow
- expect = "#<Method: A(ShallowInspect)#inspect(shallow_inspect)>"
- assert_equal expect, A.new.method(:inspect).inspect, "#{bug_10282}"
- RUBY
+ expect = "#<Method: A(ShallowInspect)#inspect(shallow_inspect)() -:#{line}>"
+ assert_equal expect, A.new.method(:inspect).inspect, bug_10282
+ end;
+ end
+
+ def test_define_method_changes_visibility_with_existing_method_bug_19749
+ c = Class.new do
+ def a; end
+ private def b; end
+
+ define_method(:b, instance_method(:b))
+ private
+ define_method(:a, instance_method(:a))
+ end
+ assert_equal([:b], c.public_instance_methods(false))
+ assert_equal([:a], c.private_instance_methods(false))
end
def test_define_method_with_unbound_method
@@ -2272,15 +3204,17 @@ class TestModule < Test::Unit::TestCase
def test_redefinition_mismatch
m = Module.new
- m.module_eval "A = 1"
- assert_raise_with_message(TypeError, /is not a module/) {
+ m.module_eval "A = 1", __FILE__, line = __LINE__
+ e = assert_raise_with_message(TypeError, /is not a module/) {
m.module_eval "module A; end"
}
+ assert_include(e.message, "#{__FILE__}:#{line}: previous definition")
n = "M\u{1f5ff}"
- m.module_eval "#{n} = 42"
- assert_raise_with_message(TypeError, "#{n} is not a module") {
+ m.module_eval "#{n} = 42", __FILE__, line = __LINE__
+ e = assert_raise_with_message(TypeError, /#{n} is not a module/) {
m.module_eval "module #{n}; end"
}
+ assert_include(e.message, "#{__FILE__}:#{line}: previous definition")
assert_separately([], <<-"end;")
Etc = (class C\u{1f5ff}; self; end).new
@@ -2308,6 +3242,171 @@ class TestModule < Test::Unit::TestCase
}
end
+ ConstLocation = [__FILE__, __LINE__]
+
+ def test_const_source_location
+ assert_equal(ConstLocation, self.class.const_source_location(:ConstLocation))
+ assert_equal(ConstLocation, self.class.const_source_location("ConstLocation"))
+ assert_equal(ConstLocation, Object.const_source_location("#{self.class.name}::ConstLocation"))
+ assert_raise(TypeError) {
+ self.class.const_source_location(nil)
+ }
+ assert_raise_with_message(NameError, /wrong constant name/) {
+ self.class.const_source_location("xxx")
+ }
+ assert_raise_with_message(TypeError, %r'does not refer to class/module') {
+ self.class.const_source_location("ConstLocation::FILE")
+ }
+ end
+
+ module CloneTestM_simple
+ C = 1
+ def self.m; C; end
+ end
+
+ module CloneTestM0
+ def foo; TEST; end
+ end
+
+ CloneTestM1 = CloneTestM0.clone
+ CloneTestM2 = CloneTestM0.clone
+ module CloneTestM1
+ TEST = :M1
+ end
+ module CloneTestM2
+ TEST = :M2
+ end
+ class CloneTestC1
+ include CloneTestM1
+ end
+ class CloneTestC2
+ include CloneTestM2
+ end
+
+ def test_constant_access_from_method_in_cloned_module
+ m = CloneTestM_simple.dup
+ assert_equal 1, m::C, '[ruby-core:47834]'
+ assert_equal 1, m.m, '[ruby-core:47834]'
+
+ assert_equal :M1, CloneTestC1.new.foo, '[Bug #15877]'
+ assert_equal :M2, CloneTestC2.new.foo, '[Bug #15877]'
+ end
+
+ def test_clone_freeze
+ m = Module.new.freeze
+ assert_predicate m.clone, :frozen?
+ assert_not_predicate m.clone(freeze: false), :frozen?
+ end
+
+ def test_module_name_in_singleton_method
+ s = Object.new.singleton_class
+ mod = s.const_set(:Foo, Module.new)
+ assert_match(/::Foo$/, mod.name, '[Bug #14895]')
+ end
+
+ def test_iclass_memory_leak
+ # [Bug #19550]
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ code = proc do
+ mod = Module.new
+ Class.new do
+ include mod
+ end
+ end
+ 1_000.times(&code)
+ PREP
+ 3_000_000.times(&code)
+ CODE
+ end
+
+ def test_complemented_method_entry_memory_leak
+ # [Bug #19894] [Bug #19896]
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ code = proc do
+ $c = Class.new do
+ def foo; end
+ end
+
+ $m = Module.new do
+ refine $c do
+ def foo; end
+ end
+ end
+
+ Class.new do
+ using $m
+
+ def initialize
+ o = $c.new
+ o.method(:foo).unbind
+ end
+ end.new
+ end
+ 1_000.times(&code)
+ PREP
+ 300_000.times(&code)
+ CODE
+ end
+
+ def test_module_clone_memory_leak
+ # [Bug #19901]
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ code = proc do
+ Module.new.clone
+ end
+ 1_000.times(&code)
+ PREP
+ 1_000_000.times(&code)
+ CODE
+ end
+
+ def test_set_temporary_name
+ m = Module.new
+ assert_nil m.name
+
+ m.const_set(:N, Module.new)
+
+ assert_match(/\A#<Module:0x\h+>::N\z/, m::N.name)
+ 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!}
+ assert_same m::N, m::N.set_temporary_name(nil)
+ assert_nil(m::N.name)
+
+ m::N.const_set(:O, Module.new)
+ m.const_set(:Recursive, m)
+ m::N.const_set(:Recursive, m)
+ m.const_set(:A, 42)
+
+ 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)
+
+ assert_same m, m.set_temporary_name(nil)
+ assert_nil m.name
+ assert_nil m::N.name
+ assert_nil m::N::O.name
+
+ assert_raise_with_message(ArgumentError, "empty class/module name") do
+ m.set_temporary_name("")
+ end
+ %w[A A::B ::A ::A::B].each do |name|
+ assert_raise_with_message(ArgumentError, /must not be a constant path/) do
+ m.set_temporary_name(name)
+ end
+ end
+
+ [Object, User, AClass].each do |mod|
+ assert_raise_with_message(RuntimeError, /permanent name/) do
+ mod.set_temporary_name("fake_name")
+ end
+ end
+ end
+
private
def assert_top_method_is_private(method)
@@ -2315,8 +3414,9 @@ class TestModule < Test::Unit::TestCase
methods = singleton_class.private_instance_methods(false)
assert_include(methods, :#{method}, ":#{method} should be private")
- assert_raise_with_message(NoMethodError, "private method `#{method}' called for main:Object") {
- self.#{method}
+ assert_raise_with_message(NoMethodError, /^private method '#{method}' called for /) {
+ recv = self
+ recv.#{method}
}
}
end
diff --git a/test/ruby/test_name_error.rb b/test/ruby/test_name_error.rb
new file mode 100644
index 0000000000..f0402de4b9
--- /dev/null
+++ b/test/ruby/test_name_error.rb
@@ -0,0 +1,156 @@
+require 'test/unit'
+
+class TestNameError < Test::Unit::TestCase
+ def test_new_default
+ error = NameError.new
+ assert_equal("NameError", error.message)
+ end
+
+ def test_new_message
+ error = NameError.new("Message")
+ assert_equal("Message", error.message)
+ end
+
+ def test_new_name
+ error = NameError.new("Message")
+ assert_nil(error.name)
+
+ error = NameError.new("Message", :foo)
+ assert_equal(:foo, error.name)
+ end
+
+ def test_new_receiver
+ receiver = Object.new
+
+ error = NameError.new
+ assert_raise(ArgumentError) {error.receiver}
+ assert_equal("NameError", error.message)
+
+ error = NameError.new(receiver: receiver)
+ assert_equal(["NameError", receiver],
+ [error.message, error.receiver])
+
+ error = NameError.new("Message", :foo, receiver: receiver)
+ assert_equal(["Message", receiver, :foo],
+ [error.message, error.receiver, error.name])
+ end
+
+ PrettyObject =
+ Class.new(BasicObject) do
+ alias object_id __id__
+ def pretty_inspect; "`obj'"; end
+ alias inspect pretty_inspect
+ end
+
+ def test_info_const
+ obj = PrettyObject.new
+
+ e = assert_raise(NameError) {
+ obj.instance_eval("Object")
+ }
+ assert_equal(:Object, e.name)
+
+ e = assert_raise(NameError) {
+ BasicObject::X
+ }
+ assert_same(BasicObject, e.receiver)
+ assert_equal(:X, e.name)
+ end
+
+ def test_info_const_name
+ mod = Module.new do
+ def self.name
+ "ModuleName"
+ end
+
+ def self.inspect
+ raise "<unusable info>"
+ end
+ end
+ assert_raise_with_message(NameError, /ModuleName/) {mod::DOES_NOT_EXIST}
+ end
+
+ def test_info_method
+ obj = PrettyObject.new
+
+ e = assert_raise(NameError) {
+ obj.instance_eval {foo}
+ }
+ assert_equal(:foo, e.name)
+ assert_same(obj, e.receiver)
+
+ e = assert_raise(NoMethodError) {
+ obj.foo(1, 2)
+ }
+ assert_equal(:foo, e.name)
+ assert_equal([1, 2], e.args)
+ assert_same(obj, e.receiver)
+ assert_not_predicate(e, :private_call?)
+
+ e = assert_raise(NoMethodError) {
+ obj.instance_eval {foo(1, 2)}
+ }
+ assert_equal(:foo, e.name)
+ assert_equal([1, 2], e.args)
+ assert_same(obj, e.receiver)
+ assert_predicate(e, :private_call?)
+ end
+
+ def test_info_local_variables
+ obj = PrettyObject.new
+ def obj.test(a, b=nil, *c, &d)
+ e = a
+ 1.times {|f| g = foo; g}
+ e
+ end
+
+ e = assert_raise(NameError) {
+ obj.test(3)
+ }
+ assert_equal(:foo, e.name)
+ assert_same(obj, e.receiver)
+ assert_equal(%i[a b c d e f g], e.local_variables.sort)
+ end
+
+ def test_info_method_missing
+ obj = PrettyObject.new
+ def obj.method_missing(*)
+ super
+ end
+
+ e = assert_raise(NoMethodError) {
+ obj.foo(1, 2)
+ }
+ assert_equal(:foo, e.name)
+ assert_equal([1, 2], e.args)
+ assert_same(obj, e.receiver)
+ assert_not_predicate(e, :private_call?)
+
+ e = assert_raise(NoMethodError) {
+ obj.instance_eval {foo(1, 2)}
+ }
+ assert_equal(:foo, e.name)
+ assert_equal([1, 2], e.args)
+ assert_same(obj, e.receiver)
+ assert_predicate(e, :private_call?)
+ end
+
+ def test_info_parent_iseq_mark
+ assert_separately(['-', File.join(__dir__, 'bug-11928.rb')], <<-'end;')
+ -> {require ARGV[0]}.call
+ end;
+ end
+
+ def test_large_receiver_inspect
+ receiver = Class.new do
+ def self.inspect
+ 'A' * 120
+ end
+ end
+
+ error = assert_raise(NameError) do
+ receiver::FOO
+ end
+ assert_match(/\Auninitialized constant #{'A' * 120}::FOO$/, error.message)
+ end
+end
diff --git a/test/ruby/test_nomethod_error.rb b/test/ruby/test_nomethod_error.rb
new file mode 100644
index 0000000000..6abd20cc81
--- /dev/null
+++ b/test/ruby/test_nomethod_error.rb
@@ -0,0 +1,137 @@
+require 'test/unit'
+
+class TestNoMethodError < Test::Unit::TestCase
+ def test_new_default
+ error = NoMethodError.new
+ assert_equal("NoMethodError", error.message)
+ end
+
+ def test_new_message
+ error = NoMethodError.new("Message")
+ assert_equal("Message", error.message)
+ end
+
+ def test_new_name
+ error = NoMethodError.new("Message")
+ assert_nil(error.name)
+
+ error = NoMethodError.new("Message", :foo)
+ assert_equal(:foo, error.name)
+ end
+
+ def test_new_name_args
+ error = NoMethodError.new("Message", :foo)
+ assert_nil(error.args)
+
+ error = NoMethodError.new("Message", :foo, [1, 2])
+ assert_equal([:foo, [1, 2]], [error.name, error.args])
+ end
+
+ def test_new_name_args_priv
+ error = NoMethodError.new("Message", :foo, [1, 2])
+ assert_not_predicate(error, :private_call?)
+
+ error = NoMethodError.new("Message", :foo, [1, 2], true)
+ assert_equal([:foo, [1, 2], true],
+ [error.name, error.args, error.private_call?])
+ end
+
+ def test_new_receiver
+ receiver = Object.new
+
+ error = NoMethodError.new
+ assert_raise(ArgumentError) {error.receiver}
+
+ error = NoMethodError.new(receiver: receiver)
+ assert_equal(receiver, error.receiver)
+
+ error = NoMethodError.new("Message")
+ assert_raise(ArgumentError) {error.receiver}
+
+ error = NoMethodError.new("Message", receiver: receiver)
+ assert_equal(["Message", receiver],
+ [error.message, error.receiver])
+
+ error = NoMethodError.new("Message", :foo)
+ assert_raise(ArgumentError) {error.receiver}
+
+ msg = "Message"
+
+ error = NoMethodError.new("Message", :foo, receiver: receiver)
+ assert_match msg, error.message
+ assert_equal :foo, error.name
+ assert_equal receiver, error.receiver
+
+ error = NoMethodError.new("Message", :foo, [1, 2])
+ assert_raise(ArgumentError) {error.receiver}
+
+ error = NoMethodError.new("Message", :foo, [1, 2], receiver: receiver)
+ assert_match msg, error.message
+ assert_equal :foo, error.name
+ assert_equal [1, 2], error.args
+ assert_equal receiver, error.receiver
+
+ error = NoMethodError.new("Message", :foo, [1, 2], true)
+ assert_raise(ArgumentError) {error.receiver}
+
+ error = NoMethodError.new("Message", :foo, [1, 2], true, receiver: receiver)
+ assert_equal :foo, error.name
+ assert_equal [1, 2], error.args
+ assert_equal receiver, error.receiver
+ assert_predicate error, :private_call?
+ end
+
+ def test_message_encoding
+ bug3237 = '[ruby-core:29948]'
+ str = "\u2600"
+ id = :"\u2604"
+ msg = "undefined method '#{id}' for an instance of String"
+ assert_raise_with_message(NoMethodError, Regexp.compile(Regexp.quote(msg)), bug3237) do
+ str.__send__(id)
+ end
+ end
+
+ def test_to_s
+ pre = Module.new do
+ def name
+ BasicObject.new
+ end
+ end
+ mod = Module.new
+ mod.singleton_class.prepend(pre)
+
+ err = assert_raise(NoMethodError) do
+ mod.this_method_does_not_exist
+ end
+
+ 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_not.rb b/test/ruby/test_not.rb
index 721f868a5a..12e4c4b696 100644
--- a/test/ruby/test_not.rb
+++ b/test/ruby/test_not.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: false
require 'test/unit'
-class TestIfunless < Test::Unit::TestCase
+class TestNot < Test::Unit::TestCase
def test_not_with_grouped_expression
assert_equal(false, (not (true)))
assert_equal(true, (not (false)))
diff --git a/test/ruby/test_notimp.rb b/test/ruby/test_notimp.rb
deleted file mode 100644
index ddebb657bf..0000000000
--- a/test/ruby/test_notimp.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-# frozen_string_literal: false
-require 'test/unit'
-require 'timeout'
-require 'tmpdir'
-
-class TestNotImplement < Test::Unit::TestCase
- def test_respond_to_fork
- assert_include(Process.methods, :fork)
- if /linux/ =~ RUBY_PLATFORM
- assert_equal(true, Process.respond_to?(:fork))
- end
- end
-
- def test_respond_to_lchmod
- assert_include(File.methods, :lchmod)
- if /linux/ =~ RUBY_PLATFORM
- assert_equal(false, File.respond_to?(:lchmod))
- end
- if /freebsd/ =~ RUBY_PLATFORM
- assert_equal(true, File.respond_to?(:lchmod))
- end
- end
-
- def test_call_fork
- GC.start
- pid = nil
- ps =
- case RUBY_PLATFORM
- when /linux/ # assume Linux Distribution uses procps
- proc {`ps -eLf #{pid}`}
- when /freebsd/
- proc {`ps -lH #{pid}`}
- when /darwin/
- proc {`ps -lM #{pid}`}
- else
- proc {`ps -l #{pid}`}
- end
- assert_nothing_raised(Timeout::Error, ps) do
- Timeout.timeout(EnvUtil.apply_timeout_scale(5)) {
- pid = fork {}
- Process.wait pid
- pid = nil
- }
- end
- ensure
- if pid
- Process.kill(:KILL, pid)
- Process.wait pid
- end
- end if Process.respond_to?(:fork)
-
- def test_call_lchmod
- if File.respond_to?(:lchmod)
- Dir.mktmpdir {|d|
- f = "#{d}/f"
- g = "#{d}/g"
- File.open(f, "w") {}
- File.symlink f, g
- newmode = 0444
- File.lchmod newmode, "#{d}/g"
- snew = File.lstat(g)
- assert_equal(newmode, snew.mode & 0777)
- }
- end
- end
-
- def test_method_inspect_fork
- m = Process.method(:fork)
- if Process.respond_to?(:fork)
- assert_not_match(/not-implemented/, m.inspect)
- else
- assert_match(/not-implemented/, m.inspect)
- end
- end
-
- def test_method_inspect_lchmod
- m = File.method(:lchmod)
- if File.respond_to?(:lchmod)
- assert_not_match(/not-implemented/, m.inspect)
- else
- assert_match(/not-implemented/, m.inspect)
- end
- end
-
-end
diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb
index 6efc40320a..35496ac875 100644
--- a/test/ruby/test_numeric.rb
+++ b/test/ruby/test_numeric.rb
@@ -18,14 +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}"}
+ 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"
@@ -52,7 +62,6 @@ class TestNumeric < Test::Unit::TestCase
end.new
assert_equal(-1, -a)
- bug7688 = '[ruby-core:51389] [Bug #7688]'
a = Class.new(Numeric) do
def coerce(x); raise StandardError, "my error"; end
end.new
@@ -197,6 +206,14 @@ class TestNumeric < Test::Unit::TestCase
assert_nil(a <=> :foo)
end
+ def test_float_round_ndigits
+ bug14635 = "[ruby-core:86323]"
+ f = 0.5
+ 31.times do |i|
+ assert_equal(0.5, f.round(i+1), bug14635 + " (argument: #{i+1})")
+ end
+ end
+
def test_floor_ceil_round_truncate
a = Class.new(Numeric) do
def to_f; 1.5; end
@@ -226,8 +243,18 @@ class TestNumeric < Test::Unit::TestCase
assert_equal(-1, a.truncate)
end
+ def test_floor_ceil_ndigits
+ bug17183 = "[ruby-core:100090]"
+ f = 291.4
+ 31.times do |i|
+ assert_equal(291.4, f.floor(i+1), bug17183)
+ assert_equal(291.4, f.ceil(i+1), bug17183)
+ end
+ end
+
def assert_step(expected, (from, *args), inf: false)
- enum = from.step(*args)
+ kw = args.last.is_a?(Hash) ? args.pop : {}
+ enum = from.step(*args, **kw)
size = enum.size
xsize = expected.size
@@ -236,7 +263,7 @@ class TestNumeric < Test::Unit::TestCase
assert_send [size, :>, 0], "step size: +infinity"
a = []
- from.step(*args) { |x| a << x; break if a.size == xsize }
+ from.step(*args, **kw) { |x| a << x; break if a.size == xsize }
assert_equal expected, a, "step"
a = []
@@ -246,7 +273,7 @@ class TestNumeric < Test::Unit::TestCase
assert_equal expected.size, size, "step size"
a = []
- from.step(*args) { |x| a << x }
+ from.step(*args, **kw) { |x| a << x }
assert_equal expected, a, "step"
a = []
@@ -260,18 +287,21 @@ class TestNumeric < Test::Unit::TestCase
assert_raise(ArgumentError) { 1.step(10, 1, 0) { } }
assert_raise(ArgumentError) { 1.step(10, 1, 0).size }
assert_raise(ArgumentError) { 1.step(10, 0) { } }
- assert_raise(ArgumentError) { 1.step(10, 0).size }
assert_raise(ArgumentError) { 1.step(10, "1") { } }
assert_raise(ArgumentError) { 1.step(10, "1").size }
assert_raise(TypeError) { 1.step(10, nil) { } }
- assert_raise(TypeError) { 1.step(10, nil).size }
- assert_nothing_raised { 1.step(by: 0, to: nil) }
- assert_nothing_raised { 1.step(by: 0, to: nil).size }
- assert_nothing_raised { 1.step(by: 0) }
- assert_nothing_raised { 1.step(by: 0).size }
+ assert_nothing_raised { 1.step(10, nil).size }
assert_nothing_raised { 1.step(by: nil) }
assert_nothing_raised { 1.step(by: nil).size }
+ assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10))
+ assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10, 2))
+ assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10, by: 2))
+ assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2))
+ assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2, to: nil))
+ assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2, to: 10))
+ assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: -1))
+
bug9811 = '[ruby-dev:48177] [Bug #9811]'
assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil) {} }
assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil).size }
@@ -280,6 +310,30 @@ class TestNumeric < Test::Unit::TestCase
assert_raise(ArgumentError, bug9811) { 1.step(10, 1, by: 11) {} }
assert_raise(ArgumentError, bug9811) { 1.step(10, 1, by: 11).size }
+ feature15573 = "[ruby-core:91324] [Feature #15573]"
+ assert_raise(ArgumentError, feature15573) { 1.step(10, 0) }
+ assert_raise(ArgumentError, feature15573) { 1.step(10, by: 0) }
+ assert_raise(ArgumentError, feature15573) { 1.step(10, 0) { break } }
+ assert_raise(ArgumentError, feature15573) { 1.step(10, by: 0) { break } }
+ assert_raise(ArgumentError, feature15573) { 42.step(by: 0, to: -Float::INFINITY) }
+ assert_raise(ArgumentError, feature15573) { 42.step(by: 0, to: 42.5) }
+ assert_raise(ArgumentError, feature15573) { 4.2.step(by: 0.0) }
+ assert_raise(ArgumentError, feature15573) { 4.2.step(by: -0.0) }
+ assert_raise(ArgumentError, feature15573) { 42.step(by: 0.0, to: 44) }
+ assert_raise(ArgumentError, feature15573) { 42.step(by: 0.0, to: 0) }
+ assert_raise(ArgumentError, feature15573) { 42.step(by: -0.0, to: 44) }
+ assert_raise(ArgumentError, feature15573) { bignum.step(by: 0) }
+ assert_raise(ArgumentError, feature15573) { bignum.step(by: 0.0) }
+ assert_raise(ArgumentError, feature15573) { bignum.step(by: 0, to: bignum+1) }
+ assert_raise(ArgumentError, feature15573) { bignum.step(by: 0, to: 0) }
+
+ e = 1.step(10, {by: "1"})
+ assert_raise(TypeError) {e.next}
+ assert_raise(TypeError) {e.size}
+ e = 1.step(to: "10")
+ assert_raise(ArgumentError) {e.next}
+ assert_raise(ArgumentError) {e.size}
+
assert_equal(bignum*2+1, (-bignum).step(bignum, 1).size)
assert_equal(bignum*2, (-bignum).step(bignum-1, 1).size)
@@ -289,7 +343,6 @@ class TestNumeric < Test::Unit::TestCase
i <<= 1 until (bigflo - i).to_i < bignum
bigflo -= i >> 1
assert_equal(bigflo.to_i, (0.0).step(bigflo-1.0, 1.0).size)
- assert_operator((0.0).step(bignum.to_f, 1.0).size, :>=, bignum) # may loose precision
assert_step [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 10]
assert_step [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, to: 10]
@@ -306,8 +359,6 @@ class TestNumeric < Test::Unit::TestCase
assert_step [], [2, 1, 3]
assert_step [], [-2, -1, -3]
- assert_step [3, 3, 3, 3], [3, by: 0], inf: true
- assert_step [3, 3, 3, 3], [3, by: 0, to: 42], inf: true
assert_step [10], [10, 1, -bignum]
assert_step [], [1, 0, Float::INFINITY]
@@ -317,19 +368,20 @@ class TestNumeric < Test::Unit::TestCase
assert_step [10, 11, 12, 13], [10], inf: true
assert_step [10, 9, 8, 7], [10, by: -1], inf: true
assert_step [10, 9, 8, 7], [10, by: -1, to: nil], inf: true
+ end
- assert_step [42, 42, 42, 42], [42, by: 0, to: -Float::INFINITY], inf: true
- assert_step [42, 42, 42, 42], [42, by: 0, to: 42.5], inf: true
- assert_step [4.2, 4.2, 4.2, 4.2], [4.2, by: 0.0], inf: true
- assert_step [4.2, 4.2, 4.2, 4.2], [4.2, by: -0.0], inf: true
- assert_step [42.0, 42.0, 42.0, 42.0], [42, by: 0.0, to: 44], inf: true
- assert_step [42.0, 42.0, 42.0, 42.0], [42, by: 0.0, to: 0], inf: true
- assert_step [42.0, 42.0, 42.0, 42.0], [42, by: -0.0, to: 44], inf: true
-
- assert_step [bignum]*4, [bignum, by: 0], inf: true
- assert_step [bignum]*4, [bignum, by: 0.0], inf: true
- assert_step [bignum]*4, [bignum, by: 0, to: bignum+1], inf: true
- assert_step [bignum]*4, [bignum, by: 0, to: 0], inf: true
+ def test_step_bug15537
+ assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10.0, 1, -2]
+ assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10.0, to: 1, by: -2]
+ assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10.0, 1, -2]
+ assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10, to: 1.0, by: -2]
+ assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10, 1.0, -2]
+
+ assert_step [10.0, 9.0, 8.0, 7.0], [10, by: -1.0], inf: true
+ assert_step [10.0, 9.0, 8.0, 7.0], [10, by: -1.0, to: nil], inf: true
+ assert_step [10.0, 9.0, 8.0, 7.0], [10, nil, -1.0], inf: true
+ assert_step [10.0, 9.0, 8.0, 7.0], [10.0, by: -1], inf: true
+ assert_step [10.0, 9.0, 8.0, 7.0], [10.0, nil, -1], inf: true
end
def test_num2long
@@ -358,6 +410,18 @@ class TestNumeric < Test::Unit::TestCase
end;
end
+ def test_remainder_infinity
+ assert_equal(4, 4.remainder(Float::INFINITY))
+ assert_equal(4, 4.remainder(-Float::INFINITY))
+ assert_equal(-4, -4.remainder(Float::INFINITY))
+ assert_equal(-4, -4.remainder(-Float::INFINITY))
+
+ assert_equal(4.2, 4.2.remainder(Float::INFINITY))
+ assert_equal(4.2, 4.2.remainder(-Float::INFINITY))
+ assert_equal(-4.2, -4.2.remainder(Float::INFINITY))
+ assert_equal(-4.2, -4.2.remainder(-Float::INFINITY))
+ end
+
def test_comparison_comparable
bug12864 = '[ruby-core:77713] [Bug #12864]'
@@ -405,6 +469,30 @@ class TestNumeric < Test::Unit::TestCase
assert_equal(12, 12.pow(1, 10000000001), '[Bug #14259]')
assert_equal(12, 12.pow(1, 10000000002), '[Bug #14259]')
assert_equal(17298641040, 12.pow(72387894339363242, 243682743764), '[Bug #14259]')
+
+ integers = [-2, -1, 0, 1, 2, 3, 6, 1234567890123456789]
+ integers.each do |i|
+ assert_equal(0, i.pow(0, 1), '[Bug #17257]')
+ assert_equal(1, i.pow(0, 2))
+ assert_equal(1, i.pow(0, 3))
+ assert_equal(1, i.pow(0, 6))
+ assert_equal(1, i.pow(0, 1234567890123456789))
+
+ assert_equal(0, i.pow(0, -1))
+ assert_equal(-1, i.pow(0, -2))
+ assert_equal(-2, i.pow(0, -3))
+ assert_equal(-5, i.pow(0, -6))
+ assert_equal(-1234567890123456788, i.pow(0, -1234567890123456789))
+ end
+
+ assert_equal(0, 0.pow(2, 1))
+ 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 c25dcf9c37..f4dfe2251b 100644
--- a/test/ruby/test_object.rb
+++ b/test/ruby/test_object.rb
@@ -5,7 +5,6 @@ require 'test/unit'
class TestObject < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
end
def teardown
@@ -47,15 +46,27 @@ class TestObject < Test::Unit::TestCase
a = Object.new
def a.b; 2 end
+ c = a.clone
+ assert_equal(false, c.frozen?)
+ assert_equal(false, a.frozen?)
+ assert_equal(2, c.b)
+
+ c = a.clone(freeze: true)
+ assert_equal(true, c.frozen?)
+ assert_equal(false, a.frozen?)
+ assert_equal(2, c.b)
+
a.freeze
c = a.clone
assert_equal(true, c.frozen?)
+ assert_equal(true, a.frozen?)
assert_equal(2, c.b)
assert_raise(ArgumentError) {a.clone(freeze: [])}
d = a.clone(freeze: false)
def d.e; 3; end
assert_equal(false, d.frozen?)
+ assert_equal(true, a.frozen?)
assert_equal(2, d.b)
assert_equal(3, d.e)
@@ -75,6 +86,30 @@ class TestObject < Test::Unit::TestCase
assert_raise_with_message(ArgumentError, /\u{1f4a9}/) do
Object.new.clone(freeze: x)
end
+
+ c = Class.new do
+ attr_reader :f
+ end
+ o = c.new
+ def o.initialize_clone(_, freeze: true)
+ @f = freeze
+ super
+ end
+ clone = o.clone
+ assert_kind_of c, clone
+ assert_equal true, clone.f
+ clone = o.clone(freeze: false)
+ assert_kind_of c, clone
+ assert_equal false, clone.f
+
+ class << o
+ remove_method(:initialize_clone)
+ end
+ def o.initialize_clone(_)
+ super
+ end
+ assert_kind_of c, o.clone
+ assert_raise(ArgumentError) { o.clone(freeze: false) }
end
def test_init_dupclone
@@ -96,17 +131,6 @@ class TestObject < Test::Unit::TestCase
assert_raise(TypeError) { 1.kind_of?(1) }
end
- def test_taint_frozen_obj
- o = Object.new
- o.freeze
- assert_raise(FrozenError) { o.taint }
-
- o = Object.new
- o.taint
- o.freeze
- assert_raise(FrozenError) { o.untaint }
- end
-
def test_freeze_immediate
assert_equal(true, 1.frozen?)
1.freeze
@@ -132,6 +156,27 @@ class TestObject < Test::Unit::TestCase
assert_equal(0.0, nil.to_f)
end
+ def test_nil_to_s
+ str = nil.to_s
+ assert_equal("", str)
+ assert_predicate(str, :frozen?)
+ assert_same(str, nil.to_s)
+ end
+
+ def test_true_to_s
+ str = true.to_s
+ assert_equal("true", str)
+ assert_predicate(str, :frozen?)
+ assert_same(str, true.to_s)
+ end
+
+ def test_false_to_s
+ str = false.to_s
+ assert_equal("false", str)
+ assert_predicate(str, :frozen?)
+ assert_same(str, false.to_s)
+ end
+
def test_not
assert_equal(false, Object.new.send(:!))
assert_equal(true, nil.send(:!))
@@ -227,6 +272,20 @@ class TestObject < Test::Unit::TestCase
assert_equal([:foo], o.methods(false), bug8044)
end
+ def test_methods_prepend_singleton
+ c = Class.new(Module) {private def foo; end}
+ k = c.new
+ k.singleton_class
+ c.module_eval {prepend(Module.new)}
+ 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 }
@@ -238,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
@@ -255,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)
@@ -274,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
@@ -302,10 +355,49 @@ class TestObject < Test::Unit::TestCase
end
end
+ def test_remove_instance_variable_re_embed
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ c = Class.new do
+ attr_reader :a, :b, :c
+
+ def initialize
+ @a = nil
+ @b = nil
+ @c = nil
+ end
+ end
+
+ 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
o = Object.new
def o.to_s; 1; end
assert_raise(TypeError) { String(o) }
+ o.singleton_class.remove_method(:to_s)
def o.to_s; "o"; end
assert_equal("o", String(o))
def o.to_str; "O"; end
@@ -318,6 +410,7 @@ class TestObject < Test::Unit::TestCase
o = Object.new
def o.to_a; 1; end
assert_raise(TypeError) { Array(o) }
+ o.singleton_class.remove_method(:to_a)
def o.to_a; [1]; end
assert_equal([1], Array(o))
def o.to_ary; [2]; end
@@ -335,6 +428,7 @@ class TestObject < Test::Unit::TestCase
o = Object.new
def o.to_hash; {a: 1, b: 2}; end
assert_equal({a: 1, b: 2}, Hash(o))
+ o.singleton_class.remove_method(:to_hash)
def o.to_hash; 9; end
assert_raise(TypeError) { Hash(o) }
end
@@ -343,6 +437,7 @@ class TestObject < Test::Unit::TestCase
o = Object.new
def o.to_i; nil; end
assert_raise(TypeError) { Integer(o) }
+ o.singleton_class.remove_method(:to_i)
def o.to_i; 42; end
assert_equal(42, Integer(o))
def o.respond_to?(*) false; end
@@ -365,6 +460,18 @@ class TestObject < Test::Unit::TestCase
assert_equal(1+3+5+7+9, n)
end
+ def test_max_shape_variation_with_performance_warnings
+ assert_in_out_err([], <<-INPUT, %w(), /The class Foo reached 8 shape variations, instance variables accesses will be slower and memory usage increased/)
+ $VERBOSE = false
+ Warning[:performance] = true
+
+ class Foo; end
+ 10.times do |i|
+ Foo.new.instance_variable_set(:"@a\#{i}", nil)
+ end
+ INPUT
+ end
+
def test_redefine_method_under_verbose
assert_in_out_err([], <<-INPUT, %w(2), /warning: method redefined; discarding old foo$/)
$VERBOSE = true
@@ -376,15 +483,30 @@ class TestObject < Test::Unit::TestCase
end
def test_redefine_method_which_may_case_serious_problem
- assert_in_out_err([], <<-INPUT, [], %r"warning: redefining `object_id' may cause serious problems$")
- $VERBOSE = false
- def (Object.new).object_id; end
- INPUT
+ %w(object_id __id__ __send__).each do |m|
+ assert_in_out_err([], <<-INPUT, [], %r"warning: redefining '#{m}' may cause serious problems$")
+ $VERBOSE = false
+ def (Object.new).#{m}; end
+ INPUT
- assert_in_out_err([], <<-INPUT, [], %r"warning: redefining `__send__' may cause serious problems$")
- $VERBOSE = false
- def (Object.new).__send__; end
- INPUT
+ assert_in_out_err([], <<-INPUT, [], %r"warning: redefining '#{m}' may cause serious problems$")
+ $VERBOSE = false
+ Class.new.define_method(:#{m}) {}
+ INPUT
+
+ assert_in_out_err([], <<-INPUT, [], %r"warning: redefining '#{m}' may cause serious problems$")
+ $VERBOSE = false
+ Class.new.attr_reader(:#{m})
+ INPUT
+
+ assert_in_out_err([], <<-INPUT, [], %r"warning: redefining '#{m}' may cause serious problems$")
+ $VERBOSE = false
+ Class.new do
+ def foo; end
+ alias #{m} foo
+ end
+ INPUT
+ end
bug10421 = '[ruby-dev:48691] [Bug #10421]'
assert_in_out_err([], <<-INPUT, ["1"], [], bug10421)
@@ -423,8 +545,8 @@ class TestObject < Test::Unit::TestCase
bug2202 = '[ruby-core:26074]'
assert_raise(NoMethodError, bug2202) {o2.meth2}
- %w(object_id __send__ initialize).each do |m|
- assert_in_out_err([], <<-INPUT, %w(:ok), %r"warning: removing `#{m}' may cause serious problems$")
+ %w(object_id __id__ __send__ initialize).each do |m|
+ assert_in_out_err([], <<-INPUT, %w(:ok), %r"warning: removing '#{m}' may cause serious problems$")
$VERBOSE = false
begin
Class.new.instance_eval { remove_method(:#{m}) }
@@ -577,7 +699,7 @@ class TestObject < Test::Unit::TestCase
called = []
p.singleton_class.class_eval do
- define_method(:respond_to?) do |a|
+ define_method(:respond_to?) do |a, priv = false|
called << [:respond_to?, a]
false
end
@@ -720,7 +842,7 @@ class TestObject < Test::Unit::TestCase
e = assert_raise(NoMethodError) {
o.never_defined_test_no_superclass_method
}
- assert_equal(m1, e.message, bug2312)
+ assert_equal(m1.lines.first, e.message.lines.first, bug2312)
end
def test_superclass_method
@@ -765,36 +887,7 @@ class TestObject < Test::Unit::TestCase
end
end
- def test_untrusted
- verbose = $VERBOSE
- $VERBOSE = false
- begin
- obj = Object.new
- assert_equal(false, obj.untrusted?)
- assert_equal(false, obj.tainted?)
- obj.untrust
- assert_equal(true, obj.untrusted?)
- assert_equal(true, obj.tainted?)
- obj.trust
- assert_equal(false, obj.untrusted?)
- assert_equal(false, obj.tainted?)
- obj.taint
- assert_equal(true, obj.untrusted?)
- assert_equal(true, obj.tainted?)
- obj.untaint
- assert_equal(false, obj.untrusted?)
- assert_equal(false, obj.tainted?)
- ensure
- $VERBOSE = verbose
- end
- end
-
def test_to_s
- x = Object.new
- x.taint
- s = x.to_s
- assert_equal(true, s.tainted?)
-
x = eval(<<-EOS)
class ToS\u{3042}
new.to_s
@@ -803,14 +896,10 @@ class TestObject < Test::Unit::TestCase
assert_match(/\bToS\u{3042}:/, x)
name = "X".freeze
- x = Object.new.taint
+ x = Object.new
class<<x;self;end.class_eval {define_method(:to_s) {name}}
assert_same(name, x.to_s)
- assert_not_predicate(name, :tainted?)
- assert_raise(FrozenError) {name.taint}
assert_equal("X", [x].join(""))
- assert_not_predicate(name, :tainted?)
- assert_not_predicate(eval('"X".freeze'), :tainted?)
end
def test_inspect
@@ -829,6 +918,15 @@ class TestObject < Test::Unit::TestCase
x.instance_variable_set(:@bar, 42)
assert_match(/\A#<Object:0x\h+ (?:@foo="value", @bar=42|@bar=42, @foo="value")>\z/, x.inspect)
+ # Bug: [ruby-core:19167]
+ x = Object.new
+ x.instance_variable_set(:@foo, NilClass)
+ assert_match(/\A#<Object:0x\h+ @foo=NilClass>\z/, x.inspect)
+ x.instance_variable_set(:@foo, TrueClass)
+ assert_match(/\A#<Object:0x\h+ @foo=TrueClass>\z/, x.inspect)
+ x.instance_variable_set(:@foo, FalseClass)
+ assert_match(/\A#<Object:0x\h+ @foo=FalseClass>\z/, x.inspect)
+
# #inspect does not call #to_s anymore
feature6130 = '[ruby-core:43238]'
x = Object.new
@@ -855,6 +953,42 @@ 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
+ assert_equal([], Object.new.singleton_methods)
+ assert_equal([], Object.new.singleton_methods(false))
+ c = Class.new
+ def c.foo; end
+ assert_equal([:foo], c.singleton_methods - [:yaml_tag])
+ assert_equal([:foo], c.singleton_methods(false))
+ assert_equal([], c.singleton_class.singleton_methods(false))
+ c.singleton_class.singleton_class
+ assert_equal([], c.singleton_class.singleton_methods(false))
+
+ o = c.new.singleton_class
+ assert_equal([:foo], o.singleton_methods - [:yaml_tag])
+ assert_equal([], o.singleton_methods(false))
+ o.singleton_class
+ assert_equal([:foo], o.singleton_methods - [:yaml_tag])
+ assert_equal([], o.singleton_methods(false))
+
+ c.extend(Module.new{def bar; end})
+ assert_equal([:bar, :foo], c.singleton_methods.sort - [:yaml_tag])
+ assert_equal([:foo], c.singleton_methods(false))
end
def test_singleton_class
@@ -878,11 +1012,25 @@ class TestObject < Test::Unit::TestCase
end
end
+ def test_singleton_class_freeze
+ x = Object.new
+ xs = x.singleton_class
+ x.freeze
+ assert_predicate(xs, :frozen?)
+
+ y = Object.new
+ ys = y.singleton_class
+ ys.prepend(Module.new)
+ y.freeze
+ assert_predicate(ys, :frozen?, '[Bug #19169]')
+ end
+
def test_redef_method_missing
bug5473 = '[ruby-core:40287]'
['ArgumentError.new("bug5473")', 'ArgumentError, "bug5473"', '"bug5473"'].each do |code|
exc = code[/\A[A-Z]\w+/] || 'RuntimeError'
assert_separately([], <<-SRC)
+ $VERBOSE = nil
class ::Object
def method_missing(m, *a, &b)
raise #{code}
@@ -900,7 +1048,7 @@ class TestObject < Test::Unit::TestCase
assert_nothing_raised("copy") {a.instance_eval {initialize_copy(b)}}
c = a.dup.freeze
assert_raise(FrozenError, "frozen") {c.instance_eval {initialize_copy(b)}}
- d = a.dup.trust
+ d = a.dup
[a, b, c, d]
end
@@ -945,4 +1093,13 @@ class TestObject < Test::Unit::TestCase
end
EOS
end
+
+ def test_frozen_inspect
+ obj = Object.new
+ obj.instance_variable_set(:@a, "a")
+ ins = obj.inspect
+ obj.freeze
+
+ assert_equal(ins, obj.inspect)
+ end
end
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 c352b75b70..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
@@ -35,6 +35,44 @@ End
deftest_id2ref(false)
deftest_id2ref(nil)
+ def test_id2ref_liveness
+ assert_normal_exit <<-EOS
+ ids = []
+ 10.times{
+ 1_000.times{
+ ids << 'hello'.object_id
+ }
+ objs = ids.map{|id|
+ begin
+ ObjectSpace._id2ref(id)
+ rescue RangeError
+ nil
+ end
+ }
+ GC.start
+ objs.each{|e| e.inspect}
+ }
+ EOS
+ end
+
+ def test_id2ref_invalid_argument
+ msg = /no implicit conversion/
+ 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
+ # RB_STATIC_SYM_P checks for static symbols by checking that the bottom
+ # 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) { EnvUtil.suppress_warning { ObjectSpace._id2ref(:a.object_id + 256) } }
+ end
+
def test_count_objects
h = {}
ObjectSpace.count_objects(h)
@@ -56,13 +94,27 @@ 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
ObjectSpace.define_finalizer(a) { p :ok }
!b
END
+
+ assert_in_out_err(["-e", <<~RUBY], "", %w(:ok :ok), [], timeout: 60)
+ a = Object.new
+ ObjectSpace.define_finalizer(a) { p :ok }
+
+ 1_000_000.times do
+ o = Object.new
+ ObjectSpace.define_finalizer(o) { }
+ end
+
+ b = Object.new
+ ObjectSpace.define_finalizer(b) { p :ok }
+ RUBY
+
assert_raise(ArgumentError) { ObjectSpace.define_finalizer([], Object.new) }
code = proc do |priv|
@@ -85,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
@@ -131,6 +202,38 @@ End
END
end
+ def test_exception_in_finalizer
+ assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", [], /finalizing \(RuntimeError\)/)
+ begin;
+ ObjectSpace.define_finalizer(Object.new) {raise "finalizing"}
+ end;
+ end
+
+ def test_finalizer_thread_raise
+ EnvUtil.without_gc do
+ fzer = proc do |id|
+ sleep 0.2
+ end
+ 2.times do
+ o = Object.new
+ ObjectSpace.define_finalizer(o, fzer)
+ end
+
+ my_error = Class.new(RuntimeError)
+ begin
+ main_th = Thread.current
+ Thread.new do
+ sleep 0.1
+ main_th.raise(my_error)
+ end
+ GC.start
+ sleep(10)
+ assert(false)
+ rescue my_error
+ end
+ end
+ end
+
def test_each_object
klass = Class.new
new_obj = klass.new
@@ -155,7 +258,7 @@ End
assert_same(new_obj, found[0])
end
- def test_each_object_no_gabage
+ def test_each_object_no_garbage
assert_separately([], <<-End)
GC.disable
eval('begin; 1.times{}; rescue; ensure; end')
@@ -181,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
@@ -203,4 +321,11 @@ End
assert_kind_of(meta, sclass)
assert_include(ObjectSpace.each_object(meta).to_a, sclass)
end
+
+ def test_each_object_with_allocation
+ assert_normal_exit(<<-End)
+ list = []
+ ObjectSpace.each_object { |o| list << Object.new }
+ End
+ end
end
diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb
index 2a4cc19699..5d16984eef 100644
--- a/test/ruby/test_optimization.rb
+++ b/test/ruby/test_optimization.rb
@@ -16,6 +16,18 @@ class TestRubyOptimization < Test::Unit::TestCase
end;
end
+ def assert_performance_warning(klass, method)
+ assert_in_out_err([], "#{<<-"begin;"}\n#{<<~"end;"}", [], ["-:4: warning: Redefining '#{klass}##{method}' disables interpreter and JIT optimizations"])
+ begin;
+ Warning[:performance] = true
+ class #{klass}
+ undef #{method}
+ def #{method}
+ end
+ end
+ end;
+ end
+
def disasm(name)
RubyVM::InstructionSequence.of(method(name)).disasm
end
@@ -23,102 +35,122 @@ class TestRubyOptimization < Test::Unit::TestCase
def test_fixnum_plus
assert_equal 21, 10 + 11
assert_redefine_method('Integer', '+', 'assert_equal 11, 10 + 11')
+ assert_performance_warning('Integer', '+')
end
def test_fixnum_minus
assert_equal 5, 8 - 3
assert_redefine_method('Integer', '-', 'assert_equal 3, 8 - 3')
+ assert_performance_warning('Integer', '-')
end
def test_fixnum_mul
assert_equal 15, 3 * 5
assert_redefine_method('Integer', '*', 'assert_equal 5, 3 * 5')
+ assert_performance_warning('Integer', '*')
end
def test_fixnum_div
assert_equal 3, 15 / 5
assert_redefine_method('Integer', '/', 'assert_equal 5, 15 / 5')
+ assert_performance_warning('Integer', '/')
end
def test_fixnum_mod
assert_equal 1, 8 % 7
assert_redefine_method('Integer', '%', 'assert_equal 7, 8 % 7')
+ assert_performance_warning('Integer', '%')
end
def test_fixnum_lt
assert_equal true, 1 < 2
assert_redefine_method('Integer', '<', 'assert_equal 2, 1 < 2')
+ assert_performance_warning('Integer', '<')
end
def test_fixnum_le
assert_equal true, 1 <= 2
assert_redefine_method('Integer', '<=', 'assert_equal 2, 1 <= 2')
+ assert_performance_warning('Integer', '<=')
end
def test_fixnum_gt
assert_equal false, 1 > 2
assert_redefine_method('Integer', '>', 'assert_equal 2, 1 > 2')
+ assert_performance_warning('Integer', '>')
end
def test_fixnum_ge
assert_equal false, 1 >= 2
assert_redefine_method('Integer', '>=', 'assert_equal 2, 1 >= 2')
+ assert_performance_warning('Integer', '>=')
end
def test_float_plus
assert_equal 4.0, 2.0 + 2.0
assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0')
+ assert_performance_warning('Float', '+')
end
def test_float_minus
assert_equal 4.0, 2.0 + 2.0
- assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0')
+ assert_redefine_method('Float', '-', 'assert_equal 2.0, 4.0 - 2.0')
+ assert_performance_warning('Float', '-')
end
def test_float_mul
assert_equal 29.25, 4.5 * 6.5
assert_redefine_method('Float', '*', 'assert_equal 6.5, 4.5 * 6.5')
+ assert_performance_warning('Float', '*')
end
def test_float_div
assert_in_delta 0.63063063063063063, 4.2 / 6.66
assert_redefine_method('Float', '/', 'assert_equal 6.66, 4.2 / 6.66', "[Bug #9238]")
+ assert_performance_warning('Float', '/')
end
def test_float_lt
assert_equal true, 1.1 < 2.2
assert_redefine_method('Float', '<', 'assert_equal 2.2, 1.1 < 2.2')
+ assert_performance_warning('Float', '<')
end
def test_float_le
assert_equal true, 1.1 <= 2.2
assert_redefine_method('Float', '<=', 'assert_equal 2.2, 1.1 <= 2.2')
+ assert_performance_warning('Float', '<=')
end
def test_float_gt
assert_equal false, 1.1 > 2.2
assert_redefine_method('Float', '>', 'assert_equal 2.2, 1.1 > 2.2')
+ assert_performance_warning('Float', '>')
end
def test_float_ge
assert_equal false, 1.1 >= 2.2
assert_redefine_method('Float', '>=', 'assert_equal 2.2, 1.1 >= 2.2')
+ assert_performance_warning('Float', '>=')
end
def test_string_length
assert_equal 6, "string".length
assert_redefine_method('String', 'length', 'assert_nil "string".length')
+ assert_performance_warning('String', 'length')
end
def test_string_size
assert_equal 6, "string".size
assert_redefine_method('String', 'size', 'assert_nil "string".size')
+ assert_performance_warning('String', 'size')
end
def test_string_empty?
assert_equal true, "".empty?
assert_equal false, "string".empty?
assert_redefine_method('String', 'empty?', 'assert_nil "string".empty?')
+ assert_performance_warning('String', 'empty?')
end
def test_string_plus
@@ -127,27 +159,96 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_equal "x", "" + "x"
assert_equal "ab", "a" + "b"
assert_redefine_method('String', '+', 'assert_equal "b", "a" + "b"')
+ assert_performance_warning('String', '+')
end
def test_string_succ
assert_equal 'b', 'a'.succ
assert_equal 'B', 'A'.succ
+ assert_performance_warning('String', 'succ')
end
def test_string_format
assert_equal '2', '%d' % 2
assert_redefine_method('String', '%', 'assert_equal 2, "%d" % 2')
+ assert_performance_warning('String', '%')
end
def test_string_freeze
assert_equal "foo", "foo".freeze
assert_equal "foo".freeze.object_id, "foo".freeze.object_id
assert_redefine_method('String', 'freeze', 'assert_nil "foo".freeze')
+ assert_performance_warning('String', 'freeze')
end
def test_string_uminus
assert_same "foo".freeze, -"foo"
assert_redefine_method('String', '-@', 'assert_nil(-"foo")')
+ assert_performance_warning('String', '-@')
+ end
+
+ def test_array_min
+ assert_equal 1, [1, 2, 4].min
+ assert_redefine_method('Array', 'min', 'assert_nil([1, 2, 4].min)')
+ assert_redefine_method('Array', 'min', 'assert_nil([1 + 0, 2, 4].min)')
+ assert_performance_warning('Array', 'min')
+ end
+
+ def test_array_max
+ assert_equal 4, [1, 2, 4].max
+ assert_redefine_method('Array', 'max', 'assert_nil([1, 2, 4].max)')
+ assert_redefine_method('Array', 'max', 'assert_nil([1 + 0, 2, 4].max)')
+ assert_performance_warning('Array', 'max')
+ end
+
+ def test_array_hash
+ assert_performance_warning('Array', 'hash')
+ end
+
+ def test_trace_optimized_methods
+ bug14870 = "[ruby-core:87638]"
+ expected = [:-@, :max, :min, :+, :-, :*, :/, :%, :==, :<, :<=, :>, :>=, :<<,
+ :&, :|, :[], :[]=, :length, :empty?, :nil?, :succ, :!, :=~]
+ [:c_call, :c_return].each do |type|
+ methods = []
+ tp = TracePoint.new(type) { |tp| methods << tp.method_id }
+ tp.enable do
+ x = "a"; x = -x
+ [1].max
+ [1].min
+ x = 42 + 2
+ x = 42 - 2
+ x = 42 * 2
+ x = 42 / 2
+ x = 42 % 2
+ y = x == 42
+ y = x < 42
+ y = x <= 42
+ y = x > 42
+ y = x >= 42
+ x = x << 1
+ x = x & 1
+ x = x | 1
+ x = []; x[1]
+ x[1] = 2
+ x.length
+ x.empty?
+ x.nil?
+ x = 1; x.succ
+ !x
+ x = 'a'; x =~ /a/
+ x = y
+ end
+ assert_equal(expected, methods, bug14870)
+ end
+
+ methods = []
+ tp = TracePoint.new(:c_call, :c_return) { |tp| methods << tp.method_id }
+ tp.enable do
+ x = 1
+ x != 42
+ end
+ assert_equal([:!=, :==, :==, :!=], methods, bug14870)
end
def test_string_freeze_saves_memory
@@ -177,6 +278,8 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_equal :b, (b #{m} "b").to_sym
end
end
+
+ assert_performance_warning('String', '==')
end
def test_string_ltlt
@@ -185,40 +288,59 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_equal "x", "" << "x"
assert_equal "ab", "a" << "b"
assert_redefine_method('String', '<<', 'assert_equal "b", "a" << "b"')
+ assert_performance_warning('String', '<<')
+ end
+
+ def test_fixnum_and
+ assert_equal 1, 1&3
+ assert_redefine_method('Integer', '&', 'assert_equal 3, 1&3')
+ assert_performance_warning('Integer', '&')
+ end
+
+ def test_fixnum_or
+ assert_equal 3, 1|3
+ assert_redefine_method('Integer', '|', 'assert_equal 1, 3|1')
+ assert_performance_warning('Integer', '|')
end
def test_array_plus
assert_equal [1,2], [1]+[2]
assert_redefine_method('Array', '+', 'assert_equal [2], [1]+[2]')
+ assert_performance_warning('Array', '+')
end
def test_array_minus
assert_equal [2], [1,2] - [1]
assert_redefine_method('Array', '-', 'assert_equal [1], [1,2]-[1]')
+ assert_performance_warning('Array', '-')
end
def test_array_length
assert_equal 0, [].length
assert_equal 3, [1,2,3].length
assert_redefine_method('Array', 'length', 'assert_nil([].length); assert_nil([1,2,3].length)')
+ assert_performance_warning('Array', 'length')
end
def test_array_empty?
assert_equal true, [].empty?
assert_equal false, [1,2,3].empty?
assert_redefine_method('Array', 'empty?', 'assert_nil([].empty?); assert_nil([1,2,3].empty?)')
+ assert_performance_warning('Array', 'empty?')
end
def test_hash_length
assert_equal 0, {}.length
assert_equal 1, {1=>1}.length
assert_redefine_method('Hash', 'length', 'assert_nil({}.length); assert_nil({1=>1}.length)')
+ assert_performance_warning('Hash', 'length')
end
def test_hash_empty?
assert_equal true, {}.empty?
assert_equal false, {1=>1}.empty?
assert_redefine_method('Hash', 'empty?', 'assert_nil({}.empty?); assert_nil({1=>1}.empty?)')
+ assert_performance_warning('Hash', 'empty?')
end
def test_hash_aref_with
@@ -229,6 +351,7 @@ class TestRubyOptimization < Test::Unit::TestCase
h = { "foo" => 1 }
assert_equal "foo", h["foo"]
end;
+ assert_performance_warning('Hash', '[]')
end
def test_hash_aset_with
@@ -240,6 +363,7 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_equal 1, h["foo"] = 1, "assignment always returns value set"
assert_nil h["foo"]
end;
+ assert_performance_warning('Hash', '[]=')
end
class MyObj
@@ -262,7 +386,7 @@ class TestRubyOptimization < Test::Unit::TestCase
unless file
loc, = caller_locations(1, 1)
file = loc.path
- line ||= loc.lineno
+ line ||= loc.lineno + 1
end
RubyVM::InstructionSequence.new("proc {|_|_.class_eval {#{src}}}",
file, (path || file), line,
@@ -337,7 +461,7 @@ class TestRubyOptimization < Test::Unit::TestCase
def test_tailcall_inhibited_by_rescue
bug12082 = '[ruby-core:73871] [Bug #12082]'
- tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
+ EnvUtil.suppress_warning {tailcall("#{<<-"begin;"}\n#{<<~"end;"}")}
begin;
def to_be_rescued
return do_raise
@@ -369,6 +493,31 @@ class TestRubyOptimization < Test::Unit::TestCase
message(bug12565) {disasm(:add_one_and_two)})
end
+ def test_c_func_with_sp_offset_under_tailcall
+ tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
+ def calc_one_plus_two
+ 1 + 2.abs
+ end
+
+ def one_plus_two
+ calc_one_plus_two
+ end
+ end;
+ assert_equal(3, one_plus_two)
+ end
+
+ def test_tailcall_and_post_arg
+ tailcall(<<~RUBY)
+ def ret_const = :ok
+
+ def post_arg(_a = 1, _b) = ret_const
+ RUBY
+
+ # YJIT probably uses a fallback on the call to post_arg
+ assert_equal(:ok, post_arg(0))
+ end
+
def test_tailcall_interrupted_by_sigint
bug12576 = 'ruby-core:76327'
script = "#{<<-"begin;"}\n#{<<~'end;'}"
@@ -388,7 +537,7 @@ class TestRubyOptimization < Test::Unit::TestCase
foo
end;1
end;
- status, _err = EnvUtil.invoke_ruby([], "", true, true, {}) {
+ status, _err = EnvUtil.invoke_ruby([], "", true, true, **{}) {
|in_p, out_p, err_p, pid|
in_p.write(script)
in_p.close
@@ -415,7 +564,7 @@ class TestRubyOptimization < Test::Unit::TestCase
def test_tailcall_condition_block
bug = '[ruby-core:78015] [Bug #12905]'
- src = "#{<<-"begin;"}\n#{<<~"end;"}"
+ src = "#{<<-"begin;"}\n#{<<~"end;"}", __FILE__, nil, __LINE__+1
begin;
def run(current, final)
if current < final
@@ -427,13 +576,13 @@ class TestRubyOptimization < Test::Unit::TestCase
end;
obj = Object.new
- self.class.tailcall(obj.singleton_class, src, tailcall: false)
+ self.class.tailcall(obj.singleton_class, *src, tailcall: false)
e = assert_raise(SystemStackError) {
obj.run(1, Float::INFINITY)
}
level = e.backtrace_locations.size
obj = Object.new
- self.class.tailcall(obj.singleton_class, src, tailcall: true)
+ self.class.tailcall(obj.singleton_class, *src, tailcall: true)
level *= 2
mesg = message {"#{bug}: #{$!.backtrace_locations.size} / #{level} stack levels"}
assert_nothing_raised(SystemStackError, mesg) {
@@ -441,12 +590,27 @@ class TestRubyOptimization < Test::Unit::TestCase
}
end
+ def test_tailcall_not_to_grow_stack
+ bug16161 = '[ruby-core:94881]'
+
+ tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
+ def foo(n)
+ return :ok if n < 1
+ foo(n - 1)
+ end
+ end;
+ assert_nothing_raised(SystemStackError, bug16161) do
+ assert_equal(:ok, foo(1_000_000), bug16161)
+ end
+ end
+
class Bug10557
- def [](_)
+ def [](_, &)
block_given?
end
- def []=(_, _)
+ def []=(_, _, &)
block_given?
end
end
@@ -468,7 +632,7 @@ class TestRubyOptimization < Test::Unit::TestCase
begin;
class String
undef freeze
- def freeze
+ def freeze(&)
block_given?
end
end
@@ -530,6 +694,7 @@ class TestRubyOptimization < Test::Unit::TestCase
[ nil, true, false, 0.1, :sym, 'str', 0xffffffffffffffff ].each do |v|
k = v.class.to_s
assert_redefine_method(k, '===', "assert_equal(#{v.inspect} === 0, 0)")
+ assert_performance_warning(k, '===')
end
end
@@ -557,12 +722,122 @@ class TestRubyOptimization < Test::Unit::TestCase
when "1.8.0"..."1.8.8" then :bar
end
end;
+ [ true, false ].each do |opt|
+ iseq = RubyVM::InstructionSequence.compile(code,
+ frozen_string_literal: opt)
+ insn = iseq.disasm
+ assert_match %r{putobject\s+#{Regexp.quote('"1.8.0"..."1.8.8"')}}, insn
+ assert_match %r{putobject\s+#{Regexp.quote('"2.0.0".."2.3.2"')}}, insn
+ assert_no_match(/putstring/, insn)
+ assert_no_match(/newrange/, insn)
+ end
+ end
+
+ def test_peephole_dstr
+ code = "#{<<~'begin;'}\n#{<<~'end;'}"
+ begin;
+ exp = -'a'
+ z = 'a'
+ [exp, -"#{z}"]
+ end;
+ [ false, true ].each do |fsl|
+ iseq = RubyVM::InstructionSequence.compile(code,
+ frozen_string_literal: fsl)
+ assert_same(*iseq.eval,
+ "[ruby-core:85542] [Bug #14475] fsl: #{fsl}")
+ end
+ end
+
+ def test_peephole_array_freeze
+ code = "#{<<~'begin;'}\n#{<<~'end;'}"
+ begin;
+ [1].freeze
+ end;
+ iseq = RubyVM::InstructionSequence.compile(code)
+ insn = iseq.disasm
+ assert_match(/opt_ary_freeze/, insn)
+ assert_no_match(/duparray/, insn)
+ assert_no_match(/send/, insn)
+ assert_predicate([1].freeze, :frozen?)
+ assert_in_out_err([], <<~RUBY, [":ok"])
+ class Array
+ prepend Module.new {
+ def freeze
+ :ok
+ end
+ }
+ end
+ p [1].freeze
+ RUBY
+ end
+
+ def test_peephole_array_freeze_empty
+ code = "#{<<~'begin;'}\n#{<<~'end;'}"
+ begin;
+ [].freeze
+ end;
+ iseq = RubyVM::InstructionSequence.compile(code)
+ insn = iseq.disasm
+ assert_match(/opt_ary_freeze/, insn)
+ assert_no_match(/duparray/, insn)
+ assert_no_match(/send/, insn)
+ assert_predicate([].freeze, :frozen?)
+ assert_in_out_err([], <<~RUBY, [":ok"])
+ class Array
+ prepend Module.new {
+ def freeze
+ :ok
+ end
+ }
+ end
+ p [].freeze
+ RUBY
+ end
+
+ def test_peephole_hash_freeze
+ code = "#{<<~'begin;'}\n#{<<~'end;'}"
+ begin;
+ {a:1}.freeze
+ end;
+ iseq = RubyVM::InstructionSequence.compile(code)
+ insn = iseq.disasm
+ assert_match(/opt_hash_freeze/, insn)
+ assert_no_match(/duphash/, insn)
+ assert_no_match(/send/, insn)
+ assert_predicate([1].freeze, :frozen?)
+ assert_in_out_err([], <<~RUBY, [":ok"])
+ class Hash
+ prepend Module.new {
+ def freeze
+ :ok
+ end
+ }
+ end
+ p({a:1}.freeze)
+ RUBY
+ end
+
+ def test_peephole_hash_freeze_empty
+ code = "#{<<~'begin;'}\n#{<<~'end;'}"
+ begin;
+ {}.freeze
+ end;
iseq = RubyVM::InstructionSequence.compile(code)
insn = iseq.disasm
- assert_match %r{putobject\s+#{Regexp.quote('"1.8.0"..."1.8.8"')}}, insn
- assert_match %r{putobject\s+#{Regexp.quote('"2.0.0".."2.3.2"')}}, insn
- assert_no_match(/putstring/, insn)
- assert_no_match(/newrange/, insn)
+ assert_match(/opt_hash_freeze/, insn)
+ assert_no_match(/duphash/, insn)
+ assert_no_match(/send/, insn)
+ assert_predicate([].freeze, :frozen?)
+ assert_in_out_err([], <<~RUBY, [":ok"])
+ class Hash
+ prepend Module.new {
+ def freeze
+ :ok
+ end
+ }
+ end
+ p({}.freeze)
+ RUBY
end
def test_branch_condition_backquote
@@ -657,39 +932,28 @@ class TestRubyOptimization < Test::Unit::TestCase
def test_block_parameter_should_not_create_objects
assert_separately [], <<-END
- #
def foo &b
end
h1 = {}; h2 = {}
- ObjectSpace.count_objects(h1) # reharsal
+ ObjectSpace.count_objects(h1) # rehearsal
+ GC.start; GC.disable # to disable GC while foo{}
ObjectSpace.count_objects(h1)
foo{}
ObjectSpace.count_objects(h2)
- assert_equal 0, h2[:TOTAL] - h1[:TOTAL]
- END
- end
-
- def test_block_parameter_should_restore_safe_level
- assert_separately [], <<-END
- #
- def foo &b
- $SAFE = 1
- b.call
- end
- assert_equal 0, foo{$SAFE}
+ assert_equal 0, h2[:T_DATA] - h1[:T_DATA] # Proc is T_DATA
END
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: 15
+ assert_ruby_status [], <<-END, timeout: 60
script = <<-EOS
if true
else
@@ -723,6 +987,7 @@ class TestRubyOptimization < Test::Unit::TestCase
h = {}
assert_equal(bug, eval('{ok: 42, **h}; bug'))
assert_equal(:ok, eval('{ok: bug = :ok, **h}; bug'))
+ assert_empty(h)
end
def test_overwritten_blockparam
@@ -735,6 +1000,18 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_equal(:ok, obj.a())
end
+ def test_blockparam_in_rescue
+ obj = Object.new
+ def obj.foo(&b)
+ raise
+ rescue
+ b.call
+ end
+ result = nil
+ assert_equal(42, obj.foo {result = 42})
+ assert_equal(42, result)
+ end
+
def test_unconditional_branch_to_leave_block
assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}")
begin;
@@ -784,4 +1061,218 @@ class TestRubyOptimization < Test::Unit::TestCase
}
end;
end
+
+ def test_optimized_rescue
+ assert_in_out_err("", "#{<<~"begin;"}\n#{<<~'end;'}", [], /END \(RuntimeError\)/)
+ begin;
+ if false
+ begin
+ require "some_mad_stuff"
+ rescue LoadError
+ puts "no mad stuff loaded"
+ end
+ end
+
+ raise "END"
+ end;
+ end
+
+ class Objtostring
+ end
+
+ 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}"
+ end
+ assert_redefine_method('NilClass', 'to_s', <<-'end')
+ assert_match %r{\A#<NilClass:0x[0-9a-f]+>\z}, "#{nil}"
+ end
+ assert_redefine_method('TrueClass', 'to_s', <<-'end')
+ assert_match %r{\A#<TrueClass:0x[0-9a-f]+>\z}, "#{true}"
+ end
+ 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}"
+ o = Object.new
+ def o.to_s; 1; end
+ assert_match %r{\A#<Object:0x[0-9a-f]+>\z}, "#{o}"
+ end
+
+ def test_opt_duparray_send_include_p
+ [
+ 'x = :b; [:a, :b].include?(x)',
+ '@c = :b; [:a, :b].include?(@c)',
+ '@c = "b"; %i[a b].include?(@c.to_sym)',
+ '[:a, :b].include?(self) == false',
+ ].each do |code|
+ iseq = RubyVM::InstructionSequence.compile(code)
+ insn = iseq.disasm
+ assert_match(/opt_duparray_send/, insn)
+ assert_no_match(/\bduparray\b/, insn)
+ assert_equal(true, eval(code))
+ end
+
+ x, y = :b, :c
+ assert_equal(true, [:a, :b].include?(x))
+ assert_equal(false, [:a, :b].include?(y))
+
+ assert_in_out_err([], <<~RUBY, ["1,2", "3,3", "1,2", "4,4"])
+ class Array
+ prepend(Module.new do
+ def include?(i)
+ puts self.join(",")
+ # Modify self to prove that we are operating on a copy.
+ map! { i }
+ puts self.join(",")
+ end
+ end)
+ end
+ def x(i)
+ [1, 2].include?(i)
+ end
+ x(3)
+ x(4)
+ RUBY
+
+ # Ensure raises happen correctly.
+ assert_in_out_err([], <<~RUBY, ["will raise", "int 1 not 3"])
+ class Integer
+ undef_method :==
+ def == x
+ raise "int \#{self} not \#{x}"
+ end
+ end
+ x = 3
+ puts "will raise"
+ begin
+ p [1, 2].include?(x)
+ rescue
+ puts $!
+ end
+ RUBY
+ end
+
+ def test_opt_newarray_send_include_p
+ [
+ 'b = :b; [:a, b].include?(:b)',
+ # Use Object.new to ensure that we get newarray rather than duparray.
+ 'value = 1; [Object.new, true, "true", 1].include?(value)',
+ 'value = 1; [Object.new, "1"].include?(value.to_s)',
+ '[Object.new, "1"].include?(self) == false',
+ ].each do |code|
+ iseq = RubyVM::InstructionSequence.compile(code)
+ insn = iseq.disasm
+ assert_match(/opt_newarray_send/, insn)
+ assert_no_match(/\bnewarray\b/, insn)
+ assert_equal(true, eval(code))
+ end
+
+ x, y = :b, :c
+ assert_equal(true, [:a, x].include?(x))
+ assert_equal(false, [:a, x].include?(y))
+
+ assert_in_out_err([], <<~RUBY, ["1,3", "3,3", "1,4", "4,4"])
+ class Array
+ prepend(Module.new do
+ def include?(i)
+ puts self.join(",")
+ # Modify self to prove that we are operating on a copy.
+ map! { i }
+ puts self.join(",")
+ end
+ end)
+ end
+ def x(i)
+ [1, i].include?(i)
+ end
+ x(3)
+ x(4)
+ RUBY
+
+ # Ensure raises happen correctly.
+ assert_in_out_err([], <<~RUBY, ["will raise", "int 1 not 3"])
+ class Integer
+ undef_method :==
+ def == x
+ raise "int \#{self} not \#{x}"
+ end
+ end
+ x = 3
+ puts "will raise"
+ begin
+ p [1, x].include?(x)
+ rescue
+ puts $!
+ end
+ RUBY
+ end
+
+ def test_opt_new_with_safe_navigation
+ payload = nil
+ assert_nil payload&.new
+ end
+
+ def test_opt_new
+ pos_initialize = "
+ def initialize a, b
+ @a = a
+ @b = b
+ end
+ "
+ kw_initialize = "
+ def initialize a:, b:
+ @a = a
+ @b = b
+ end
+ "
+ kw_hash_initialize = "
+ def initialize a, **kw
+ @a = a
+ @b = kw[:b]
+ end
+ "
+ pos_prelude = "class OptNewFoo; #{pos_initialize}; end;"
+ kw_prelude = "class OptNewFoo; #{kw_initialize}; end;"
+ kw_hash_prelude = "class OptNewFoo; #{kw_hash_initialize}; end;"
+ [
+ "#{pos_prelude} OptNewFoo.new 1, 2",
+ "#{pos_prelude} a = 1; b = 2; OptNewFoo.new a, b",
+ "#{pos_prelude} def optnew_foo(a, b) = OptNewFoo.new(a, b); optnew_foo 1, 2",
+ "#{pos_prelude} def optnew_foo(*a) = OptNewFoo.new(*a); optnew_foo 1, 2",
+ "#{pos_prelude} def optnew_foo(...) = OptNewFoo.new(...); optnew_foo 1, 2",
+ "#{kw_prelude} def optnew_foo(**a) = OptNewFoo.new(**a); optnew_foo a: 1, b: 2",
+ "#{kw_hash_prelude} def optnew_foo(*a, **b) = OptNewFoo.new(*a, **b); optnew_foo 1, b: 2",
+ ].each do |code|
+ iseq = RubyVM::InstructionSequence.compile(code)
+ 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(&)',
+ 'def optnew_foo(a, ...) = OptNewFoo.new(a, ...)',
+ ].each do |code|
+ iseq = RubyVM::InstructionSequence.compile(code)
+ insn = iseq.disasm
+ assert_no_match(/opt_new/, insn)
+ end
+ end
end
diff --git a/test/ruby/test_pack.rb b/test/ruby/test_pack.rb
index aec418913e..ca089f09c3 100644
--- a/test/ruby/test_pack.rb
+++ b/test/ruby/test_pack.rb
@@ -1,25 +1,44 @@
# coding: US-ASCII
# frozen_string_literal: false
require 'test/unit'
+require 'rbconfig'
+require 'rbconfig/sizeof'
class TestPack < Test::Unit::TestCase
+ # Note: the size of intptr_t and uintptr_t should be equal.
+ J_SIZE = RbConfig::SIZEOF['uintptr_t']
+
def test_pack
- $format = "c2x5CCxsdils_l_a6";
+ format = "c2x5CCxsdils_l_a6";
# Need the expression in here to force ary[5] to be numeric. This avoids
# test2 failing because ary2 goes str->numeric->str and ary does not.
ary = [1,-100,127,128,32767,987.654321098 / 100.0,12345,123456,-32767,-123456,"abcdef"]
- $x = ary.pack($format)
- ary2 = $x.unpack($format)
+ x = ary.pack(format)
+ ary2 = x.unpack(format)
assert_equal(ary.length, ary2.length)
assert_equal(ary.join(':'), ary2.join(':'))
- assert_match(/def/, $x)
+ assert_match(/def/, x)
+
+ x = [-1073741825]
+ assert_equal(x, x.pack("q").unpack("q"))
+
+ x = [-1]
+ assert_equal(x, x.pack("l").unpack("l"))
+ end
- $x = [-1073741825]
- assert_equal($x, $x.pack("q").unpack("q"))
+ def test_ascii_incompatible
+ assert_raise(Encoding::CompatibilityError) do
+ ["foo"].pack("u".encode("UTF-32BE"))
+ end
+
+ assert_raise(Encoding::CompatibilityError) do
+ "foo".unpack("C".encode("UTF-32BE"))
+ end
- $x = [-1]
- assert_equal($x, $x.pack("l").unpack("l"))
+ assert_raise(Encoding::CompatibilityError) do
+ "foo".unpack1("C".encode("UTF-32BE"))
+ end
end
def test_pack_n
@@ -79,11 +98,11 @@ class TestPack < Test::Unit::TestCase
assert_equal("\x01\x02\x03\x04", [0x01020304].pack("L"+mod))
assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("q"+mod))
assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("Q"+mod))
- psize = [nil].pack('p').bytesize
- if psize == 4
+ case J_SIZE
+ when 4
assert_equal("\x01\x02\x03\x04", [0x01020304].pack("j"+mod))
assert_equal("\x01\x02\x03\x04", [0x01020304].pack("J"+mod))
- elsif psize == 8
+ when 8
assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("j"+mod))
assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("J"+mod))
end
@@ -95,10 +114,11 @@ class TestPack < Test::Unit::TestCase
assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("I!"+mod))
assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("l!"+mod))
assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("L!"+mod))
- if psize == 4
+ case J_SIZE
+ when 4
assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("j!"+mod))
assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("J!"+mod))
- elsif psize == 8
+ when 8
assert_match(/\A\x00*\x01\x02\x03\x04\x05\x06\x07\x08\z/, [0x0102030405060708].pack("j!"+mod))
assert_match(/\A\x00*\x01\x02\x03\x04\x05\x06\x07\x08\z/, [0x0102030405060708].pack("J!"+mod))
end
@@ -127,11 +147,11 @@ class TestPack < Test::Unit::TestCase
assert_equal("\x04\x03\x02\x01", [0x01020304].pack("L"+mod))
assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("q"+mod))
assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("Q"+mod))
- psize = [nil].pack('p').bytesize
- if psize == 4
+ case J_SIZE
+ when 4
assert_equal("\x04\x03\x02\x01", [0x01020304].pack("j"+mod))
assert_equal("\x04\x03\x02\x01", [0x01020304].pack("J"+mod))
- elsif psize == 8
+ when 8
assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("j"+mod))
assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("J"+mod))
end
@@ -143,10 +163,11 @@ class TestPack < Test::Unit::TestCase
assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("I!"+mod))
assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("l!"+mod))
assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("L!"+mod))
- if psize == 4
+ case J_SIZE
+ when 4
assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("j!"+mod))
assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("J!"+mod))
- elsif psize == 8
+ when 8
assert_match(/\A\x08\x07\x06\x05\x04\x03\x02\x01\x00*\z/, [0x0102030405060708].pack("j!"+mod))
assert_match(/\A\x08\x07\x06\x05\x04\x03\x02\x01\x00*\z/, [0x0102030405060708].pack("J!"+mod))
end
@@ -182,8 +203,8 @@ class TestPack < Test::Unit::TestCase
end
def test_integer_endian_explicit
- _integer_big_endian('>')
- _integer_little_endian('<')
+ _integer_big_endian('>')
+ _integer_little_endian('<')
end
def test_pack_U
@@ -437,6 +458,7 @@ class TestPack < Test::Unit::TestCase
# Note: q! and Q! should not work on platform which has no long long type.
# Is there a such platform now?
+ # @shyouhei: Yes. gcc -ansi is one of such platform.
s1 = [578437695752307201, -506097522914230529].pack("q!*")
s2 = [578437695752307201, 17940646550795321087].pack("Q!*")
assert_equal([578437695752307201, -506097522914230529], s2.unpack("q!*"))
@@ -446,13 +468,11 @@ class TestPack < Test::Unit::TestCase
assert_equal(8, [1].pack("Q").bytesize)
assert_operator(8, :<=, [1].pack("q!").bytesize)
assert_operator(8, :<=, [1].pack("Q!").bytesize)
- end
+ end if RbConfig::CONFIG['HAVE_LONG_LONG']
def test_pack_unpack_jJ
- # Note: we assume that the size of intptr_t and uintptr_t equals to the size
- # of real pointer.
- psize = [nil].pack("p").bytesize
- if psize == 4
+ case J_SIZE
+ when 4
s1 = [67305985, -50462977].pack("j*")
s2 = [67305985, 4244504319].pack("J*")
assert_equal(s1, s2)
@@ -466,7 +486,7 @@ class TestPack < Test::Unit::TestCase
assert_equal(4, [1].pack("j").bytesize)
assert_equal(4, [1].pack("J").bytesize)
- elsif psize == 8
+ when 8
s1 = [578437695752307201, -506097522914230529].pack("j*")
s2 = [578437695752307201, 17940646550795321087].pack("J*")
assert_equal(s1, s2)
@@ -636,6 +656,14 @@ EXPECTED
end;
end
+ def test_bug_18343
+ bug18343 = '[ruby-core:106096] [Bug #18343]'
+ assert_separately(%W[- #{bug18343}], <<-'end;')
+ bug = ARGV.shift
+ assert_raise(ArgumentError, bug){[0].pack('c', {})}
+ end;
+ end
+
def test_pack_unpack_m0
assert_equal("", [""].pack("m0"))
assert_equal("AA==", ["\0"].pack("m0"))
@@ -753,53 +781,34 @@ EXPECTED
end
def test_pack_garbage
- verbose = $VERBOSE
- $VERBOSE = false
-
- assert_silent do
+ assert_raise(ArgumentError, %r%unknown pack directive '\*' in '\*U'$%) do
assert_equal "\000", [0].pack("*U")
end
-
- $VERBOSE = true
-
- _, err = capture_io do
- assert_equal "\000", [0].pack("*U")
- end
-
- assert_match %r%unknown pack directive '\*' in '\*U'$%, err
- ensure
- $VERBOSE = verbose
end
def test_unpack_garbage
- verbose = $VERBOSE
- $VERBOSE = false
-
- assert_silent do
+ assert_raise(ArgumentError, %r%unknown unpack directive '\*' in '\*U'$%) do
assert_equal [0], "\000".unpack("*U")
end
-
- $VERBOSE = true
-
- _, err = capture_io do
- assert_equal [0], "\000".unpack("*U")
- end
-
- assert_match %r%unknown unpack directive '\*' in '\*U'$%, err
- ensure
- $VERBOSE = verbose
end
def test_invalid_warning
- assert_warning(/unknown pack directive ',' in ','/) {
+ assert_raise(ArgumentError, /unknown pack directive ',' in ','/) {
[].pack(",")
}
- assert_warning(/\A[ -~]+\Z/) {
+ assert_raise(ArgumentError, /\A[ -~]+\Z/) {
[].pack("\x7f")
}
- assert_warning(/\A(.* in '\u{3042}'\n)+\z/) {
+ assert_raise(ArgumentError, /\A(.* in '\u{3042}'\n)+\z/) {
[].pack("\u{3042}")
}
+
+ assert_raise(ArgumentError, /\A.* in '.*U'\Z/) {
+ assert_equal "\000", [0].pack("\0U")
+ }
+ assert_raise(ArgumentError, /\A.* in '.*U'\Z/) {
+ "\000".unpack("\0U")
+ }
end
def test_pack_resize
@@ -861,19 +870,70 @@ EXPECTED
assert_equal "01000001", "A".unpack1("B*")
end
- def test_pack_infection
- tainted_array_string = ["123456"]
- tainted_array_string.first.taint
- ['a', 'A', 'Z', 'B', 'b', 'H', 'h', 'u', 'M', 'm', 'P', 'p'].each do |f|
- assert_predicate(tainted_array_string.pack(f), :tainted?)
- end
+ def test_unpack1_offset
+ assert_equal 65, "ZA".unpack1("C", offset: 1)
+ assert_equal "01000001", "YZA".unpack1("B*", offset: 2)
+ assert_nil "abc".unpack1("C", offset: 3)
+ assert_raise_with_message(ArgumentError, /offset can't be negative/) {
+ "a".unpack1("C", offset: -1)
+ }
+ assert_raise_with_message(ArgumentError, /offset outside of string/) {
+ "a".unpack1("C", offset: 2)
+ }
+ assert_nil "a".unpack1("C", offset: 1)
end
- def test_unpack_infection
- tainted_string = "123456"
- tainted_string.taint
- ['a', 'A', 'Z', 'B', 'b', 'H', 'h', 'u', 'M', 'm'].each do |f|
- assert_predicate(tainted_string.unpack(f).first, :tainted?)
- end
+ def test_unpack_offset
+ assert_equal [65], "ZA".unpack("C", offset: 1)
+ assert_equal ["01000001"], "YZA".unpack("B*", offset: 2)
+ assert_equal [nil, nil, nil], "abc".unpack("CCC", offset: 3)
+ assert_raise_with_message(ArgumentError, /offset can't be negative/) {
+ "a".unpack("C", offset: -1)
+ }
+ assert_raise_with_message(ArgumentError, /offset outside of string/) {
+ "a".unpack("C", offset: 2)
+ }
+ assert_equal [nil], "a".unpack("C", offset: 1)
+ end
+
+ def test_monkey_pack
+ assert_separately([], <<-'end;')
+ $-w = false
+ class Array
+ alias :old_pack :pack
+ def pack _; "oh no"; end
+ end
+
+ v = [2 ** 15].pack('n')
+
+ class Array
+ alias :pack :old_pack
+ end
+
+ assert_equal "oh no", v
+ end;
+ end
+
+ def test_monkey_pack_buffer
+ assert_separately([], <<-'end;')
+ $-w = false
+ class Array
+ alias :old_pack :pack
+ def pack _, buffer:; buffer << " no"; end
+ end
+
+ def test
+ b = +"oh"
+ [2 ** 15].pack('n', buffer: b)
+ end
+
+ v = test
+
+ class Array
+ alias :pack :old_pack
+ end
+
+ assert_equal "oh no", v
+ end;
end
end
diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb
index b725634a38..def41d6017 100644
--- a/test/ruby/test_parse.rb
+++ b/test/ruby/test_parse.rb
@@ -2,32 +2,36 @@
# frozen_string_literal: false
require 'test/unit'
require 'stringio'
+require_relative '../lib/parser_support'
class TestParse < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
end
def teardown
$VERBOSE = @verbose
end
+ def test_error_line
+ assert_syntax_error('------,,', /\n\z/, 'Message to pipe should end with a newline')
+ assert_syntax_error("{hello\n world}", /hello/)
+ end
+
def test_else_without_rescue
- x = eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error(<<-END, %r"(:#{__LINE__+2}:|#{__LINE__+2} \|.+?\n.+?\^~.+?;) else without rescue"o, [__FILE__, __LINE__+1])
begin
else
42
end
END
- assert_equal(42, x)
end
def test_alias_backref
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /can't make alias/) do
+ begin;
alias $foo $1
- END
+ end;
end
end
@@ -82,10 +86,10 @@ class TestParse < Test::Unit::TestCase
assert_equal([42, 42], [o.Foo, o.Bar])
assert_equal([42, 42], [o::baz, o::qux])
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do
+ begin;
$1 ||= t.foo 42
- END
+ end;
end
def t.bar(x); x + yield; end
@@ -150,67 +154,74 @@ class TestParse < Test::Unit::TestCase
end
def test_dynamic_constant_assignment
- assert_raise(SyntaxError) do
- Object.new.instance_eval <<-END, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /dynamic constant/) do
+ begin;
def foo
self::FOO, self::BAR = 1, 2
::FOO, ::BAR = 1, 2
end
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do
+ begin;
$1, $2 = 1, 2
- END
+ end;
end
- assert_raise(SyntaxError) do
- Object.new.instance_eval <<-END, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /dynamic constant/) do
+ begin;
def foo
::FOO = 1
end
- END
+ end;
end
c = Class.new
c.freeze
- assert_nothing_raised(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- if false
+ assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do
+ begin;
c::FOO &= 1
::FOO &= 1
- end
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ c = Class.new
+ c.freeze
+ assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do
+ begin;
+ c::FOO &= p 1
+ ::FOO &= p 1
+ end;
+ end
+
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do
+ begin;
$1 &= 1
- END
+ end;
end
end
def test_class_module
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /must be CONSTANT/) do
+ begin;
class foo; end
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /in method body/) do
+ begin;
def foo
class Foo; end
module Bar; end
end
- END
+ end;
end
- assert_nothing_raised(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do
+ begin;
class Foo 1; end
- END
+ end;
end
end
@@ -270,37 +281,34 @@ class TestParse < Test::Unit::TestCase
end
def test_bad_arg
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a constant/) do
+ begin;
def foo(FOO); end
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be an instance variable/) do
+ begin;
def foo(@foo); end
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a global variable/) do
+ begin;
def foo($foo); end
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a class variable/) do
+ begin;
def foo(@@foo); end
- END
+ end;
end
- o = Object.new
- def o.foo(*r); yield(*r); end
-
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- o.foo 1 {|; @a| @a = 42 }
- END
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be an instance variable/) do
+ begin;
+ o.foo {|; @a| @a = 42 }
+ end;
end
end
@@ -344,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("``")
@@ -352,6 +375,7 @@ class TestParse < Test::Unit::TestCase
def test_words
assert_equal([], %W( ))
+ assert_syntax_error('%w[abc', /unterminated list/)
end
def test_dstr
@@ -359,11 +383,12 @@ class TestParse < Test::Unit::TestCase
assert_equal("foo 1 bar", "foo #@@foo bar")
"1" =~ /(.)/
assert_equal("foo 1 bar", "foo #$1 bar")
+ assert_equal('foo #@1 bar', eval('"foo #@1 bar"'))
end
def test_dstr_disallowed_variable
bug8375 = '[ruby-core:54885] [Bug #8375]'
- %w[@ @1 @. @@ @@1 @@. $ $%].each do |src|
+ %w[@ @. @@ @@1 @@. $ $%].each do |src|
src = '#'+src+' '
str = assert_nothing_raised(SyntaxError, "#{bug8375} #{src.dump}") do
break eval('"'+src+'"')
@@ -376,28 +401,29 @@ class TestParse < Test::Unit::TestCase
assert_nothing_raised { eval(':""') }
end
- def assert_disallowed_variable(type, noname, *invalid)
- assert_syntax_error(noname, "`#{noname}' without identifiers is not allowed as #{type} variable name")
+ def assert_disallowed_variable(type, noname, invalid)
+ noname.each do |name|
+ assert_syntax_error("proc{a = #{name} }", "'#{noname[0]}' without identifiers is not allowed as #{type} variable name")
+ end
invalid.each do |name|
- assert_syntax_error(name, "`#{name}' is not allowed as #{type} variable name")
+ assert_syntax_error("proc {a = #{name} }", "'#{name}' is not allowed as #{type} variable name")
end
end
def test_disallowed_instance_variable
- assert_disallowed_variable("an instance", *%w[@ @1 @.])
+ assert_disallowed_variable("an instance", %w[@ @.], %w[])
end
def test_disallowed_class_variable
- assert_disallowed_variable("a class", *%w[@@ @@1 @@.])
+ assert_disallowed_variable("a class", %w[@@ @@.], %w[@@1])
end
def test_disallowed_gloal_variable
- assert_disallowed_variable("a global", *%w[$ $%])
+ assert_disallowed_variable("a global", %w[$], %w[$%])
end
def test_arg2
o = Object.new
-
assert_nothing_raised do
eval <<-END, nil, __FILE__, __LINE__+1
def o.foo(a=42,*r,z,&b); b.call(r.inject(a*1000+z*100, :+)); end
@@ -409,6 +435,7 @@ class TestParse < Test::Unit::TestCase
assert_equal(-42100, o.foo(1) {|x| -x })
assert_raise(ArgumentError) { o.foo() }
+ o = Object.new
assert_nothing_raised do
eval <<-END, nil, __FILE__, __LINE__+1
def o.foo(a=42,z,&b); b.call(a*1000+z*100); end
@@ -418,6 +445,7 @@ class TestParse < Test::Unit::TestCase
assert_equal(-42100, o.foo(1) {|x| -x } )
assert_raise(ArgumentError) { o.foo() }
+ o = Object.new
assert_nothing_raised do
eval <<-END, nil, __FILE__, __LINE__+1
def o.foo(*r,z,&b); b.call(r.inject(z*100, :+)); end
@@ -430,33 +458,104 @@ class TestParse < Test::Unit::TestCase
end
def test_duplicate_argument
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", '') do
+ begin;
1.times {|&b?| }
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /duplicated argument/) do
+ begin;
1.times {|a, a|}
- END
+ end;
end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /duplicated argument/) do
+ begin;
def foo(a, a); end
- END
+ end;
end
end
def test_define_singleton_error
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- def ("foo").foo; end
- END
+ msg = /singleton method for literals/
+ assert_parse_error(%q[def ("foo").foo; end], msg)
+ assert_parse_error(%q[def (1).foo; end], msg)
+ assert_parse_error(%q[def ((1;1)).foo; end], msg)
+ assert_parse_error(%q[def ((;1)).foo; end], msg)
+ assert_parse_error(%q[def ((1+1;1)).foo; end], msg)
+ assert_parse_error(%q[def ((%s();1)).foo; end], msg)
+ assert_parse_error(%q[def ((%w();1)).foo; end], msg)
+ assert_parse_error(%q[def ("#{42}").foo; end], msg)
+ assert_parse_error(%q[def (:"#{42}").foo; end], msg)
+ assert_parse_error(%q[def ([]).foo; end], msg)
+ assert_parse_error(%q[def ([1]).foo; end], msg)
+ assert_parse_error(%q[def (__FILE__).foo; end], msg)
+ assert_parse_error(%q[def (__LINE__).foo; end], msg)
+ assert_parse_error(%q[def (__ENCODING__).foo; end], msg)
+ assert_parse_error(%q[def __FILE__.foo; end], msg)
+ assert_parse_error(%q[def __LINE__.foo; end], msg)
+ assert_parse_error(%q[def __ENCODING__.foo; end], msg)
+ end
+
+ def test_flip_flop
+ all_assertions_foreach(nil,
+ ['(cond1..cond2)', true],
+ ['((cond1..cond2))', true],
+
+ # '(;;;cond1..cond2)', # don't care
+
+ '(1; cond1..cond2)',
+ '(%s(); cond1..cond2)',
+ '(%w(); cond1..cond2)',
+ '(1; (2; (3; 4; cond1..cond2)))',
+ '(1+1; cond1..cond2)',
+ ) do |code, pass|
+ code = code.sub("cond1", "n==4").sub("cond2", "n==5")
+ if pass
+ assert_equal([4,5], eval("(1..9).select {|n| true if #{code}}"))
+ else
+ assert_raise_with_message(ArgumentError, /bad value for range/, code) {
+ verbose_bak, $VERBOSE = $VERBOSE, nil # disable "warning: possibly useless use of a literal in void context"
+ begin
+ eval("[4].each {|n| true if #{code}}")
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ }
+ end
end
end
+ def test_op_asgn1_with_block
+ t = Object.new
+ a = []
+ blk = proc {|x| a << x }
+
+ # Prevent an "assigned but unused variable" warning
+ _ = blk
+
+ def t.[](_)
+ yield(:aref)
+ nil
+ end
+ def t.[]=(_, _)
+ yield(:aset)
+ end
+ def t.dummy(_)
+ end
+
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /block arg given in index assignment/)
+ begin;
+ t[42, &blk] ||= 42
+ end;
+
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /block arg given in index assignment/)
+ begin;
+ t[42, &blk] ||= t.dummy 42 # command_asgn test
+ end;
+ end
+
def test_backquote
t = Object.new
@@ -465,7 +564,7 @@ class TestParse < Test::Unit::TestCase
def t.`(x); "foo" + x + "bar"; end
END
end
- a = b = nil
+ a = b = c = nil
assert_nothing_raised do
eval <<-END, nil, __FILE__, __LINE__+1
a = t.` "zzz"
@@ -473,10 +572,12 @@ class TestParse < Test::Unit::TestCase
END
t.instance_eval <<-END, __FILE__, __LINE__+1
b = `zzz`
+ c = %x(ccc)
END
end
assert_equal("foozzzbar", a)
assert_equal("foozzzbar", b)
+ assert_equal("foocccbar", c)
end
def test_carrige_return
@@ -487,34 +588,42 @@ class TestParse < Test::Unit::TestCase
mesg = 'from the backslash through the invalid char'
e = assert_syntax_error('"\xg1"', /hex escape/)
- assert_equal(' ^', e.message.lines.last, mesg)
+ assert_match(/(^|\| ) \^~(?!~)/, e.message.lines.last, mesg)
e = assert_syntax_error('"\u{1234"', 'unterminated Unicode escape')
- assert_equal(' ^', e.message.lines.last, mesg)
+ assert_match(/(^|\| ) \^(?!~)/, e.message.lines.last, mesg)
e = assert_syntax_error('"\u{xxxx}"', 'invalid Unicode escape')
- assert_equal(' ^', e.message.lines.last, mesg)
+ assert_match(/(^|\| ) \^(?!~)/, e.message.lines.last, mesg)
e = assert_syntax_error('"\u{xxxx', 'Unicode escape')
- assert_pattern_list([
- /.*: invalid Unicode escape\n.*\n/,
- / \^/,
- /\n/,
- /.*: unterminated Unicode escape\n.*\n/,
- / \^/,
- /\n/,
- /.*: unterminated string.*\n.*\n/,
- / \^/,
- ], e.message)
+ if e.message.lines.first == "#{__FILE__}:#{__LINE__ - 1}: syntax errors found\n"
+ assert_pattern_list([
+ /\s+\| \^ unterminated string;.+\n/,
+ /\s+\| \^ unterminated Unicode escape\n/,
+ /\s+\| \^ invalid Unicode escape sequence\n/,
+ ], e.message.lines[2..-1].join)
+ else
+ assert_pattern_list([
+ /.*: invalid Unicode escape\n.*\n/,
+ / \^/,
+ /\n/,
+ /.*: unterminated Unicode escape\n.*\n/,
+ / \^/,
+ /\n/,
+ /.*: unterminated string.*\n.*\n/,
+ / \^\n/,
+ ], e.message)
+ end
e = assert_syntax_error('"\M1"', /escape character syntax/)
- assert_equal(' ^~~', e.message.lines.last, mesg)
+ assert_match(/(^|\| ) \^~~(?!~)/, e.message.lines.last, mesg)
e = assert_syntax_error('"\C1"', /escape character syntax/)
- assert_equal(' ^~~', e.message.lines.last, mesg)
+ assert_match(/(^|\| ) \^~~(?!~)/, e.message.lines.last, mesg)
src = '"\xD0\u{90'"\n""000000000000000000000000"
- assert_syntax_error(src, /:#{__LINE__}: unterminated/o)
+ assert_syntax_error(src, /(:#{__LINE__}:|> #{__LINE__} \|.+) unterminated/om)
assert_syntax_error('"\u{100000000}"', /invalid Unicode escape/)
assert_equal("", eval('"\u{}"'))
@@ -522,27 +631,82 @@ class TestParse < Test::Unit::TestCase
assert_equal("\x81", eval('"\C-\M-a"'))
assert_equal("\177", eval('"\c?"'))
+
+ assert_warning(/use \\C-\\s/) {assert_equal("\x00", eval('"\C- "'))}
+ assert_warning(/use \\M-\\s/) {assert_equal("\xa0", eval('"\M- "'))}
+ assert_warning(/use \\M-\\C-\\s/) {assert_equal("\x80", eval('"\M-\C- "'))}
+ assert_warning(/use \\C-\\M-\\s/) {assert_equal("\x80", eval('"\C-\M- "'))}
+ assert_warning(/use \\t/) {assert_equal("\x09", eval("\"\\C-\t\""))}
+ assert_warning(/use \\M-\\t/) {assert_equal("\x89", eval("\"\\M-\t\""))}
+ assert_warning(/use \\M-\\t/) {assert_equal("\x89", eval("\"\\M-\\C-\t\""))}
+ assert_warning(/use \\M-\\t/) {assert_equal("\x89", eval("\"\\C-\\M-\t\""))}
+ assert_syntax_error("\"\\C-\x01\"", 'Invalid escape character syntax')
+ assert_syntax_error("\"\\M-\x01\"", 'Invalid escape character syntax')
+ assert_syntax_error("\"\\M-\\C-\x01\"", 'Invalid escape character syntax')
+ assert_syntax_error("\"\\C-\\M-\x01\"", 'Invalid escape character syntax')
+
+ e = assert_syntax_error('"\c\u0000"', 'Invalid escape character syntax')
+ assert_match(/(^|\| ) \^~~~(?!~)/, e.message.lines.last)
+ e = assert_syntax_error('"\c\U0000"', 'Invalid escape character syntax')
+ assert_match(/(^|\| ) \^~~~(?!~)/, e.message.lines.last)
+
+ e = assert_syntax_error('"\C-\u0000"', 'Invalid escape character syntax')
+ assert_match(/(^|\| ) \^~~~~(?!~)/, e.message.lines.last)
+ e = assert_syntax_error('"\C-\U0000"', 'Invalid escape character syntax')
+ assert_match(/(^|\| ) \^~~~~(?!~)/, e.message.lines.last)
+
+ e = assert_syntax_error('"\M-\u0000"', 'Invalid escape character syntax')
+ assert_match(/(^|\| ) \^~~~~(?!~)/, e.message.lines.last)
+ e = assert_syntax_error('"\M-\U0000"', 'Invalid escape character syntax')
+ assert_match(/(^|\| ) \^~~~~(?!~)/, e.message.lines.last)
+
+ e = assert_syntax_error(%["\\C-\u3042"], 'Invalid escape character syntax')
+ assert_match(/(^|\|\s)\s \^(?# \\ ) ~(?# C ) ~(?# - ) ~+(?# U+3042 )($|\s)/x, e.message.lines.last)
+ assert_not_include(e.message, "invalid multibyte char")
end
def test_question
- assert_raise(SyntaxError) { eval('?') }
- assert_raise(SyntaxError) { eval('? ') }
- assert_raise(SyntaxError) { eval("?\n") }
- assert_raise(SyntaxError) { eval("?\t") }
- assert_raise(SyntaxError) { eval("?\v") }
- assert_raise(SyntaxError) { eval("?\r") }
- assert_raise(SyntaxError) { eval("?\f") }
- assert_raise(SyntaxError) { eval("?\f") }
- assert_raise(SyntaxError) { eval(" ?a\x8a".force_encoding("utf-8")) }
+ assert_syntax_error('?', /incomplete/)
+ assert_syntax_error('? ', /unexpected/)
+ assert_syntax_error("?\n", /unexpected/)
+ assert_syntax_error("?\t", /unexpected/)
+ assert_syntax_error("?\v", /unexpected/)
+ assert_syntax_error("?\r", /unexpected/)
+ assert_syntax_error("?\f", /unexpected/)
+ assert_syntax_error(" ?a\x8a".force_encoding("utf-8"), /invalid multibyte/)
assert_equal("\u{1234}", eval("?\u{1234}"))
assert_equal("\u{1234}", eval('?\u{1234}'))
+ assert_equal("\u{1234}", eval('?\u1234'))
+ assert_syntax_error('?\u{41 42}', 'Multiple codepoints at single character literal')
+ assert_syntax_error("?and", /unexpected '\?'/)
+ assert_syntax_error("?\u1234and", /unexpected '\?'/)
+ e = assert_syntax_error('"#{?\u123}"', 'invalid Unicode escape')
+ assert_not_match(/end-of-input/, e.message)
+
+ assert_warning(/use ?\\C-\\s/) {assert_equal("\x00", eval('?\C- '))}
+ assert_warning(/use ?\\M-\\s/) {assert_equal("\xa0", eval('?\M- '))}
+ assert_warning(/use ?\\M-\\C-\\s/) {assert_equal("\x80", eval('?\M-\C- '))}
+ assert_warning(/use ?\\C-\\M-\\s/) {assert_equal("\x80", eval('?\C-\M- '))}
+ assert_warning(/use ?\\t/) {assert_equal("\x09", eval("?\\C-\t"))}
+ assert_warning(/use ?\\M-\\t/) {assert_equal("\x89", eval("?\\M-\t"))}
+ assert_warning(/use ?\\M-\\t/) {assert_equal("\x89", eval("?\\M-\\C-\t"))}
+ assert_warning(/use ?\\M-\\t/) {assert_equal("\x89", eval("?\\C-\\M-\t"))}
+ assert_syntax_error("?\\C-\x01", 'Invalid escape character syntax')
+ assert_syntax_error("?\\M-\x01", 'Invalid escape character syntax')
+ assert_syntax_error("?\\M-\\C-\x01", 'Invalid escape character syntax')
+ assert_syntax_error("?\\C-\\M-\x01", 'Invalid escape character syntax')
+
+ assert_equal("\xff", eval("# encoding: ascii-8bit\n""?\\\xFF"))
end
def test_percent
assert_equal(:foo, eval('%s(foo)'))
- assert_raise(SyntaxError) { eval('%s') }
- assert_raise(SyntaxError) { eval('%ss') }
- assert_raise(SyntaxError) { eval('%z()') }
+ assert_syntax_error('%s', /unterminated quoted string/)
+ assert_syntax_error('%ss', /unknown type/)
+ assert_syntax_error('%z()', /unknown type/)
+ assert_syntax_error("%\u3042", /unknown type/)
+ assert_syntax_error("%q\u3042", /unknown type/)
+ assert_syntax_error("%", /unterminated quoted string/)
end
def test_symbol
@@ -561,24 +725,22 @@ class TestParse < Test::Unit::TestCase
assert_equal(:foobar, eval(':"foo\u{}bar"'))
assert_equal(:foobar, eval(':"foo\u{ }bar"'))
end
+
+ assert_syntax_error(':@@', /is not allowed/)
+ assert_syntax_error(':@@1', /is not allowed/)
+ assert_syntax_error(':@', /is not allowed/)
+ assert_syntax_error(':@1', /is not allowed/)
+ assert_syntax_error(':$01234', /is not allowed/)
end
def test_parse_string
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
-/
- END
- end
+ assert_syntax_error("/\n", /unterminated/)
end
def test_here_document
x = nil
- assert_raise(SyntaxError) do
- eval %Q(
-<\<FOO
- )
- end
+ assert_syntax_error("<\<FOO\n", /can't find string "FOO"/)
assert_nothing_raised(SyntaxError) do
x = eval %q(
@@ -589,23 +751,11 @@ FOO
end
assert_equal "\#$\n", x
- assert_raise(SyntaxError) do
- eval %Q(
-<\<\"
- )
- end
+ assert_syntax_error("<\<\"\n", /unterminated here document identifier/)
- assert_raise(SyntaxError) do
- eval %q(
-<<``
- )
- end
+ assert_syntax_error("<<``\n", /can't find string ""/)
- assert_raise(SyntaxError) do
- eval %q(
-<<--
- )
- end
+ assert_syntax_error("<<--\n", /unexpected <</)
assert_nothing_raised(SyntaxError) do
x = eval %q(
@@ -621,10 +771,29 @@ FOO
eval "x = <<""FOO\r\n1\r\nFOO"
end
assert_equal("1\n", x)
+
+ assert_nothing_raised do
+ x = eval "<<' FOO'\n""[Bug #19539]\n"" FOO\n"
+ end
+ assert_equal("[Bug #19539]\n", x)
+
+ assert_nothing_raised do
+ x = eval "<<-' FOO'\n""[Bug #19539]\n"" FOO\n"
+ end
+ assert_equal("[Bug #19539]\n", x)
end
def test_magic_comment
x = nil
+
+ assert_nothing_raised do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding: utf-8
+x = __ENCODING__
+ END
+ end
+ assert_equal(Encoding.find("UTF-8"), x)
+
assert_nothing_raised do
eval <<-END, nil, __FILE__, __LINE__+1
# coding = utf-8
@@ -639,6 +808,62 @@ x = __ENCODING__
x = __ENCODING__
END
end
+
+ assert_nothing_raised do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# xxxx : coding sjis
+x = __ENCODING__
+ END
+ end
+ assert_equal(__ENCODING__, x)
+
+ assert_raise(ArgumentError) do
+ EnvUtil.with_default_external(Encoding::US_ASCII) {eval <<-END, nil, __FILE__, __LINE__+1}
+# coding = external
+x = __ENCODING__
+ END
+ end
+
+ assert_raise(ArgumentError) do
+ EnvUtil.with_default_internal(Encoding::US_ASCII) {eval <<-END, nil, __FILE__, __LINE__+1}
+# coding = internal
+x = __ENCODING__
+ END
+ end
+
+ assert_raise(ArgumentError) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding = filesystem
+x = __ENCODING__
+ END
+ end
+
+ assert_raise(ArgumentError) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding = locale
+x = __ENCODING__
+ END
+ end
+
+ e = assert_raise(ArgumentError) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding: foo
+ END
+ end
+
+ message = e.message.gsub(/\033\[.*?m/, "")
+ assert_include(message, "# coding: foo\n")
+ assert_include(message, " ^")
+
+ e = assert_raise(ArgumentError) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding = foo
+ END
+ end
+
+ message = e.message.gsub(/\033\[.*?m/, "")
+ assert_include(message, "# coding = foo\n")
+ assert_include(message, " ^")
end
def test_utf8_bom
@@ -668,42 +893,50 @@ x = __ENCODING__
end
def test_embedded_rd
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
-=begin
- END
- end
+ assert_valid_syntax("=begin\n""=end")
+ assert_valid_syntax("=begin\n""=end\0")
+ assert_valid_syntax("=begin\n""=end\C-d")
+ assert_valid_syntax("=begin\n""=end\C-z")
+ end
+
+ def test_embedded_rd_error
+ error = 'embedded document meets end of file'
+ assert_syntax_error("=begin\n", error)
+ assert_syntax_error("=begin", error)
end
def test_float
- assert_equal(1.0/0, eval("1e10000"))
- assert_raise(SyntaxError) { eval('1_E') }
- assert_raise(SyntaxError) { eval('1E1E1') }
+ assert_predicate(assert_warning(/out of range/) {eval("1e10000")}, :infinite?)
+ assert_syntax_error('1_E', /trailing '_'/)
+ assert_syntax_error('1E1E1', /unexpected constant/)
end
def test_global_variable
- assert_equal(nil, eval('$-x'))
+ assert_equal(nil, assert_warning(/not initialized/) {eval('$-x')})
assert_equal(nil, eval('alias $preserve_last_match $&'))
assert_equal(nil, eval('alias $& $test_parse_foobarbazqux'))
$test_parse_foobarbazqux = nil
assert_equal(nil, $&)
assert_equal(nil, eval('alias $& $preserve_last_match'))
- assert_raise(SyntaxError) { eval('$#') }
+ assert_syntax_error('a = $#', /as a global variable name/)
+ assert_syntax_error('a = $#', /a = \$\#\n(^|.+?\| ) \^~(?!~)/)
end
def test_invalid_instance_variable
- assert_raise(SyntaxError) { eval('@#') }
- assert_raise(SyntaxError) { eval('@') }
+ pattern = /without identifiers is not allowed as an instance variable name/
+ assert_syntax_error('@%', pattern)
+ assert_syntax_error('@', pattern)
end
def test_invalid_class_variable
- assert_raise(SyntaxError) { eval('@@1') }
- assert_raise(SyntaxError) { eval('@@') }
+ pattern = /without identifiers is not allowed as a class variable name/
+ assert_syntax_error('@@%', pattern)
+ assert_syntax_error('@@', pattern)
end
def test_invalid_char
bug10117 = '[ruby-core:64243] [Bug #10117]'
- invalid_char = /Invalid char `\\x01'/
+ invalid_char = /Invalid char '\\x01'/
x = 1
assert_in_out_err(%W"-e \x01x", "", [], invalid_char, bug10117)
assert_syntax_error("\x01x", invalid_char, bug10117)
@@ -718,109 +951,56 @@ x = __ENCODING__
end
def test_unassignable
- assert_raise(SyntaxError) do
- eval %q(self = 1)
- end
- assert_raise(SyntaxError) do
- eval %q(nil = 1)
- end
- assert_raise(SyntaxError) do
- eval %q(true = 1)
- end
- assert_raise(SyntaxError) do
- eval %q(false = 1)
- end
- assert_raise(SyntaxError) do
- eval %q(__FILE__ = 1)
- end
- assert_raise(SyntaxError) do
- eval %q(__LINE__ = 1)
- end
- assert_raise(SyntaxError) do
- eval %q(__ENCODING__ = 1)
- end
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- def foo
- FOO = 1
- end
- END
- end
- assert_raise(SyntaxError) do
- eval "#{<<~"begin;"}\n#{<<~'end;'}", nil, __FILE__, __LINE__+1
- begin;
- x, true
- end;
- end
+ assert_syntax_error(%q(self = 1), /Can't change the value of self/)
+ assert_syntax_error(%q(nil = 1), /Can't assign to nil/)
+ assert_syntax_error(%q(true = 1), /Can't assign to true/)
+ assert_syntax_error(%q(false = 1), /Can't assign to false/)
+ assert_syntax_error(%q(__FILE__ = 1), /Can't assign to __FILE__/)
+ assert_syntax_error(%q(__LINE__ = 1), /Can't assign to __LINE__/)
+ assert_syntax_error(%q(__ENCODING__ = 1), /Can't assign to __ENCODING__/)
+ assert_syntax_error("def foo; FOO = 1; end", /dynamic constant assignment/)
+ assert_syntax_error("x, true", /Can't assign to true/)
end
def test_block_dup
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- foo(&proc{}) {}
- END
- end
+ assert_syntax_error("foo(&proc{}) {}", /both block arg and actual block/)
end
def test_set_backref
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- $& = 1
- END
- end
- end
-
- def test_arg_concat
- o = Object.new
- class << o; self; end.instance_eval do
- define_method(:[]=) {|*r, &b| b.call(r) }
- end
- r = nil
- assert_nothing_raised do
- eval <<-END, nil, __FILE__, __LINE__+1
- o[&proc{|x| r = x }] = 1
- END
- end
- assert_equal([1], r)
+ assert_syntax_error("$& = 1", /Can't set variable/)
end
def test_void_expr_stmts_value
- # This test checks if void contexts are warned correctly.
- # Thus, warnings MUST NOT be suppressed.
- $VERBOSE = true
- stderr = $stderr
- $stderr = StringIO.new("")
x = 1
- assert_nil eval("x; nil")
- assert_nil eval("1+1; nil")
- assert_nil eval("1.+(1); nil")
- assert_nil eval("TestParse; nil")
- assert_nil eval("::TestParse; nil")
- assert_nil eval("x..x; nil")
- assert_nil eval("x...x; nil")
- assert_nil eval("self; nil")
- assert_nil eval("nil; nil")
- assert_nil eval("true; nil")
- assert_nil eval("false; nil")
- assert_nil eval("defined?(1); nil")
+ useless_use = /useless use/
+ assert_nil assert_warning(useless_use) {eval("x; nil")}
+ assert_nil assert_warning(useless_use) {eval("1+1; nil")}
+ assert_nil assert_warning('') {eval("1.+(1); nil")}
+ assert_nil assert_warning(useless_use) {eval("TestParse; nil")}
+ assert_nil assert_warning(useless_use) {eval("::TestParse; nil")}
+ assert_nil assert_warning(useless_use) {eval("x..x; nil")}
+ assert_nil assert_warning(useless_use) {eval("x...x; nil")}
+ assert_nil assert_warning(useless_use) {eval("self; nil")}
+ assert_nil assert_warning(useless_use) {eval("nil; nil")}
+ assert_nil assert_warning(useless_use) {eval("true; nil")}
+ assert_nil assert_warning(useless_use) {eval("false; nil")}
+ assert_nil assert_warning(useless_use) {eval("defined?(1); nil")}
+ assert_nil assert_warning(useless_use) {eval("begin; ensure; x; end")}
assert_equal 1, x
- assert_raise(SyntaxError) do
- eval %q(1; next; 2)
- end
-
- assert_equal(13, $stderr.string.lines.to_a.size)
- $stderr = stderr
+ assert_syntax_error("1; next; 2", /Invalid next/)
end
def test_assign_in_conditional
- assert_nothing_raised do
+ # multiple assignment
+ assert_warning(/'= literal' in conditional/) do
eval <<-END, nil, __FILE__, __LINE__+1
(x, y = 1, 2) ? 1 : 2
END
end
- assert_nothing_raised do
+ # instance variable assignment
+ assert_warning(/'= literal' in conditional/) do
eval <<-END, nil, __FILE__, __LINE__+1
if @x = true
1
@@ -829,16 +1009,81 @@ x = __ENCODING__
end
END
end
+
+ # local variable assignment
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ def m
+ if x = true
+ 1
+ else
+ 2
+ end
+ end
+ END
+ end
+
+ # global variable assignment
+ assert_separately([], <<-RUBY)
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ if $x = true
+ 1
+ else
+ 2
+ end
+ END
+ end
+ RUBY
+
+ # dynamic variable assignment
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ y = 1
+
+ 1.times do
+ if y = true
+ 1
+ else
+ 2
+ end
+ end
+ END
+ end
+
+ # class variable assignment
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ c = Class.new
+ class << c
+ if @@a = 1
+ end
+ end
+ END
+ end
+
+ # constant declaration
+ assert_separately([], <<-RUBY)
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ if Const = true
+ 1
+ else
+ 2
+ end
+ END
+ end
+ RUBY
end
def test_literal_in_conditional
- assert_nothing_raised do
+ assert_warning(/string literal in condition/) do
eval <<-END, nil, __FILE__, __LINE__+1
"foo" ? 1 : 2
END
end
- assert_nothing_raised do
+ assert_warning(/regex literal in condition/) do
x = "bar"
eval <<-END, nil, __FILE__, __LINE__+1
/foo#{x}baz/ ? 1 : 2
@@ -851,13 +1096,13 @@ x = __ENCODING__
END
end
- assert_nothing_raised do
+ assert_warning(/string literal in flip-flop/) do
eval <<-END, nil, __FILE__, __LINE__+1
("foo".."bar") ? 1 : 2
END
end
- assert_nothing_raised do
+ assert_warning(/literal in condition/) do
x = "bar"
eval <<-END, nil, __FILE__, __LINE__+1
:"foo#{"x"}baz" ? 1 : 2
@@ -867,11 +1112,7 @@ x = __ENCODING__
end
def test_no_blockarg
- assert_raise(SyntaxError) do
- eval <<-END, nil, __FILE__, __LINE__+1
- yield(&:+)
- END
- end
+ assert_syntax_error("yield(&:+)", /block argument should not be given/)
end
def test_method_block_location
@@ -888,10 +1129,12 @@ x = __ENCODING__
assert_equal(expected, actual, bug5614)
end
- def test_shadowing_variable
- assert_warning(/shadowing outer local variable/) {eval("a=1; tap {|a|}")}
- a = "\u{3042}"
- assert_warning(/#{a}/o) {eval("#{a}=1; tap {|#{a}|}")}
+ def test_no_shadowing_variable_warning
+ assert_no_warning(/shadowing outer local variable/) {eval("a=1; tap {|a|}")}
+ end
+
+ def test_shadowing_private_local_variable
+ assert_equal 1, eval("_ = 1; [[2]].each{ |(_)| }; _")
end
def test_unused_variable
@@ -899,10 +1142,26 @@ x = __ENCODING__
assert_warning(/assigned but unused variable/) {o.instance_eval("def foo; a=1; nil; end")}
assert_warning(/assigned but unused variable/) {o.instance_eval("def bar; a=1; a(); end")}
a = "\u{3042}"
- assert_warning(/#{a}/) {o.instance_eval("def foo; #{a}=1; nil; end")}
- o = Object.new
- assert_warning(/assigned but unused variable/) {o.instance_eval("def foo; tap {a=1; a()}; end")}
- assert_warning('') {o.instance_eval("def bar; a=a=1; nil; end")}
+ assert_warning(/#{a}/) {o.instance_eval("def foo0; #{a}=1; nil; end")}
+ assert_warning(/assigned but unused variable/) {o.instance_eval("def foo1; tap {a=1; a()}; end")}
+ assert_warning('') {o.instance_eval("def bar1; a=a=1; nil; end")}
+ assert_warning(/assigned but unused variable/) {o.instance_eval("def bar2; a, = 1, 2; end")}
+ assert_warning('') {o.instance_eval("def marg1(a); nil; end")}
+ assert_warning('') {o.instance_eval("def marg2((a)); nil; end")}
+ end
+
+ def test_parsing_begin_statement_inside_method_definition
+ assert_equal :bug_20234, eval("def (begin;end).bug_20234; end")
+ NilClass.remove_method(:bug_20234)
+ assert_equal :bug_20234, eval("def (begin;rescue;end).bug_20234; end")
+ NilClass.remove_method(:bug_20234)
+ assert_equal :bug_20234, eval("def (begin;ensure;end).bug_20234; end")
+ NilClass.remove_method(:bug_20234)
+ assert_equal :bug_20234, eval("def (begin;rescue;else;end).bug_20234; end")
+ NilClass.remove_method(:bug_20234)
+
+ assert_raise(SyntaxError) { eval("def (begin;else;end).bug_20234; end") }
+ assert_raise(SyntaxError) { eval("def (begin;ensure;else;end).bug_20234; end") }
end
def test_named_capture_conflict
@@ -912,6 +1171,30 @@ x = __ENCODING__
assert_warning('') {eval("#{a} = 1; /(?<#{a}>)/ =~ ''")}
end
+ def test_named_capture_in_block
+ all_assertions_foreach(nil,
+ '(/(?<a>.*)/)',
+ '(;/(?<a>.*)/)',
+ '(%s();/(?<a>.*)/)',
+ '(%w();/(?<a>.*)/)',
+ '(1; (2; 3; (4; /(?<a>.*)/)))',
+ '(1+1; /(?<a>.*)/)',
+ '/#{""}(?<a>.*)/',
+ ) do |code, pass|
+ token = Random.bytes(4).unpack1("H*")
+ if pass
+ assert_equal(token, eval("#{code} =~ #{token.dump}; a"))
+ else
+ verbose_bak, $VERBOSE = $VERBOSE, nil # disable "warning: possibly useless use of a literal in void context"
+ begin
+ assert_nil(eval("#{code} =~ #{token.dump}; defined?(a)"), code)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ end
+ end
+ end
+
def test_rescue_in_command_assignment
bug = '[ruby-core:75621] [Bug #12402]'
all_assertions(bug) do |a|
@@ -999,14 +1282,30 @@ x = __ENCODING__
assert_syntax_error(" 0b\n", /\^/)
end
+ def test_unclosed_unicode_escape_at_eol_bug_19750
+ assert_separately([], "#{<<-"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_syntax_error("/\\u", /too short escape sequence/)
+ assert_syntax_error("/\\u{", /unterminated regexp meets end of file/)
+ assert_syntax_error("/\\u{\\n", /invalid Unicode list/)
+ assert_syntax_error("/a#\\u{\\n/", /invalid Unicode list/)
+ re = eval("/a#\\u{\n$/x")
+ assert_match(re, 'a')
+ assert_not_match(re, 'a#')
+ re = eval("/a#\\u\n$/x")
+ assert_match(re, 'a')
+ assert_not_match(re, 'a#')
+ end;
+ end
+
def test_error_def_in_argument
assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}")
begin;
assert_syntax_error("def f r:def d; def f 0end", /unexpected/)
end;
- assert_syntax_error("def\nf(000)end", /^ \^~~/)
- assert_syntax_error("def\nf(&)end", /^ \^/)
+ assert_syntax_error("def\nf(000)end", /(^|\| ) \^~~/)
+ assert_syntax_error("def\nf(&0)end", /(^|\| ) \^/)
end
def test_method_location_in_rescue
@@ -1042,36 +1341,68 @@ x = __ENCODING__
end;
end
+ def test_heredoc_interpolation
+ var = 1
+
+ v1 = <<~HEREDOC
+ something
+ #{"/#{var}"}
+ HEREDOC
+
+ v2 = <<~HEREDOC
+ something
+ #{_other = "/#{var}"}
+ HEREDOC
+
+ v3 = <<~HEREDOC
+ something
+ #{("/#{var}")}
+ HEREDOC
+
+ assert_equal "something\n/1\n", v1
+ assert_equal "something\n/1\n", v2
+ assert_equal "something\n/1\n", v3
+ assert_equal v1, v2
+ assert_equal v2, v3
+ assert_equal v1, v3
+ end
+
+ def test_heredoc_unterminated_interpolation
+ code = <<~'HEREDOC'
+ <<A+1
+ #{
+ HEREDOC
+
+ assert_syntax_error(code, /can't find string "A"/)
+ end
+
def test_unexpected_token_error
- assert_raise(SyntaxError) do
- eval('"x"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
- end
+ assert_syntax_error('"x"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', /unexpected/)
end
def test_unexpected_token_after_numeric
- assert_raise_with_message(SyntaxError, /^ \^~~\z/) do
- eval('0000xyz')
- end
- assert_raise_with_message(SyntaxError, /^ \^~~\z/) do
- eval('1.2i1.1')
- end
+ assert_syntax_error('0000xyz', /(^|\| ) \^~~(?!~)/)
+ assert_syntax_error('1.2i1.1', /(^|\| ) \^~~(?!~)/)
+ assert_syntax_error('1.2.3', /(^|\| ) \^~(?!~)/)
+ assert_syntax_error('1.', /unexpected end-of-input/)
+ assert_syntax_error('1e', /expecting end-of-input/)
end
def test_truncated_source_line
- e = assert_raise_with_message(SyntaxError, /unexpected tIDENTIFIER/) do
- eval("'0123456789012345678901234567890123456789' abcdefghijklmnopqrstuvwxyz0123456789 0123456789012345678901234567890123456789")
- end
+ lineno = __LINE__ + 1
+ e = assert_syntax_error("'0123456789012345678901234567890123456789' abcdefghijklmnopqrstuvwxyz0123456789 123456789012345678901234567890123456789",
+ /unexpected local variable or method/)
+
line = e.message.lines[1]
+ line.delete_prefix!("> #{lineno} | ") if line.start_with?(">")
+
assert_operator(line, :start_with?, "...")
assert_operator(line, :end_with?, "...\n")
end
def test_unterminated_regexp_error
- e = assert_raise(SyntaxError) do
- eval("/x")
- end.message
- assert_match(/unterminated regexp meets end of file/, e)
- assert_not_match(/unexpected tSTRING_END/, e)
+ e = assert_syntax_error("/x", /unterminated regexp meets end of file/)
+ assert_not_match(/unexpected tSTRING_END/, e.message)
end
def test_lparenarg
@@ -1081,7 +1412,8 @@ x = __ENCODING__
end
o.instance_eval {i (-1.3).abs}
assert_equal(1.3, o.x)
- o.instance_eval {i = 0; i (-1.3).abs}
+ o.i(nil)
+ o.instance_eval {i = 0; i (-1.3).abs; i}
assert_equal(1.3, o.x)
end
@@ -1090,19 +1422,395 @@ x = __ENCODING__
$VERBOSE = true
x = 1
eval("if false; 0 < x < 2; end")
+ x
end
end
+ def test_eof
+ assert_equal(42, eval("42\0""end"))
+ assert_equal(42, eval("42\C-d""end"))
+ assert_equal(42, eval("42\C-z""end"))
+ end
+
def test_eof_in_def
- assert_raise(SyntaxError) { eval("def m\n\0""end") }
- assert_raise(SyntaxError) { eval("def m\n\C-d""end") }
- assert_raise(SyntaxError) { eval("def m\n\C-z""end") }
+ assert_syntax_error("def m\n\0""end", /unexpected/)
+ assert_syntax_error("def m\n\C-d""end", /unexpected/)
+ assert_syntax_error("def m\n\C-z""end", /unexpected/)
+ end
+
+ def test_unexpected_eof
+ assert_syntax_error('unless', /(^|\| ) \^(?!~)/)
+ end
+
+ def test_location_of_invalid_token
+ assert_syntax_error('class xxx end', /(^|\| ) \^~~(?!~)/)
end
- def test_void_value_in_command_rhs
+ def test_whitespace_warning
+ assert_syntax_error("\\foo", /backslash/)
+ assert_syntax_error("\\ ", /escaped space/)
+ assert_syntax_error("\\\t", /escaped horizontal tab/)
+ assert_syntax_error("\\\f", /escaped form feed/)
+ assert_syntax_error("\\\r", /escaped carriage return/)
+ assert_warn(/middle of line/) {eval(" \r ")}
+ assert_syntax_error("\\\v", /escaped vertical tab/)
+ end
+
+ def test_command_def_cmdarg
+ assert_valid_syntax("\n#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ m def x(); end
+ 1.tap do end
+ end;
+ end
+
+ NONASCII_CONSTANTS = [
+ *%W"\u{00de} \u{00C0}".flat_map {|c| [c, c.encode("iso-8859-15")]},
+ "\u{1c4}", "\u{1f2}", "\u{1f88}", "\u{370}",
+ *%W"\u{391} \u{ff21}".flat_map {|c| [c, c.encode("cp932"), c.encode("euc-jp")]},
+ ]
+
+ def assert_nonascii_const
+ assert_all_assertions_foreach("NONASCII_CONSTANTS", *NONASCII_CONSTANTS) do |n|
+ m = Module.new
+ assert_not_operator(m, :const_defined?, n)
+ assert_raise_with_message(NameError, /uninitialized/) do
+ m.const_get(n)
+ end
+ assert_nil(eval("defined?(m::#{n})"))
+
+ v = yield m, n
+
+ assert_operator(m, :const_defined?, n)
+ assert_equal("constant", eval("defined?(m::#{n})"))
+ assert_same(v, m.const_get(n))
+
+ m.__send__(:remove_const, n)
+ assert_not_operator(m, :const_defined?, n)
+ assert_nil(eval("defined?(m::#{n})"))
+ end
+ end
+
+ def test_nonascii_const_set
+ assert_nonascii_const do |m, n|
+ m.const_set(n, 42)
+ end
+ end
+
+ def test_nonascii_constant
+ assert_nonascii_const do |m, n|
+ m.module_eval("class #{n}; self; end")
+ end
+ end
+
+ def test_cdmarg_after_command_args_and_tlbrace_arg
+ assert_valid_syntax('let () { m(a) do; end }')
+ end
+
+ def test_void_value_in_rhs
w = "void value expression"
- ex = assert_syntax_error("x = return 1", w)
- assert_equal(1, ex.message.scan(w).size, "same #{w.inspect} warning should be just once")
+ [
+ "x = return 1", "x = return, 1", "x = 1, return", "x, y = return",
+ "x = begin return ensure end",
+ "x = begin ensure return end",
+ "x = begin return ensure return end",
+ "x = begin return; rescue; return end",
+ "x = begin return; rescue; return; else return end",
+ ].each do |code|
+ ex = assert_syntax_error(code, w)
+ assert_equal(1, ex.message.scan(w).size, ->{"same #{w.inspect} warning should be just once\n#{w.message}"})
+ end
+ [
+ "x = begin return; rescue; end",
+ "x = begin return; rescue; return; else end",
+ ].each do |code|
+ assert_valid_syntax(code)
+ end
+ end
+
+ def eval_separately(code)
+ Class.new.class_eval(code)
+ end
+
+ def assert_raise_separately(error, message, code)
+ assert_raise_with_message(error, message) do
+ eval_separately(code)
+ end
+ end
+
+ def assert_ractor_shareable(obj)
+ assert Ractor.shareable?(obj), ->{"Expected #{mu_pp(obj)} to be ractor shareable"}
+ end
+
+ def assert_not_ractor_shareable(obj)
+ assert !Ractor.shareable?(obj), ->{"Expected #{mu_pp(obj)} not to be ractor shareable"}
+ end
+
+ def test_shareable_constant_value_invalid
+ assert_warning(/invalid value/) do
+ assert_valid_syntax("# shareable_constant_value: invalid-option", verbose: true)
+ end
+ end
+
+ def test_shareable_constant_value_ignored
+ assert_warning(/ignored/) do
+ assert_valid_syntax("nil # shareable_constant_value: true", verbose: true)
+ end
+ end
+
+ def test_shareable_constant_value_simple
+ obj = [['unshareable_value']]
+ a, b, c = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: experimental_everything
+ A = [[1]]
+ # shareable_constant_value: none
+ B = [[2]]
+ # shareable_constant_value: literal
+ C = [["shareable", "constant#{nil}"]]
+ D = A
+
+ [A, B, C]
+ end;
+ assert_ractor_shareable(a)
+ assert_not_ractor_shareable(b)
+ assert_ractor_shareable(c)
+ assert_equal([1], a[0])
+ assert_ractor_shareable(a[0])
+
+ a, obj = eval_separately(<<~'end;')
+ # shareable_constant_value: experimental_copy
+ obj = [["unshareable"]]
+ A = obj
+ [A, obj]
+ end;
+
+ assert_ractor_shareable(a)
+ assert_not_ractor_shareable(obj)
+ assert_equal obj, a
+ assert_not_same obj, a
+
+ bug_20339 = '[ruby-core:117186] [Bug #20339]'
+ bug_20341 = '[ruby-core:117197] [Bug #20341]'
+ a, b = eval_separately(<<~'end;')
+ # shareable_constant_value: literal
+ foo = 1
+ bar = 2
+ A = { foo => bar }
+ B = [foo, bar]
+ [A, B]
+ end;
+
+ assert_ractor_shareable(a)
+ assert_ractor_shareable(b)
+ assert_equal([1], a.keys, bug_20339)
+ assert_equal([2], a.values, bug_20339)
+ assert_equal(1, b[0], bug_20341)
+ assert_equal(2, b[1], bug_20341)
+ end
+
+ def test_shareable_constant_value_literal_const_refs
+ a = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ # [Bug #20668]
+ SOME_CONST = {
+ 'Object' => Object,
+ 'String' => String,
+ 'Array' => Array,
+ }
+ SOME_CONST
+ end;
+ assert_ractor_shareable(a)
+ end
+
+ def test_shareable_constant_value_nested
+ a, b = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: none
+ class X
+ # shareable_constant_value: experimental_everything
+ var = [[1]]
+ A = var
+ end
+ B = []
+ [X::A, B]
+ end;
+ assert_ractor_shareable(a)
+ assert_not_ractor_shareable(b)
+ assert_equal([1], a[0])
+ assert_ractor_shareable(a[0])
+ end
+
+ def test_shareable_constant_value_hash_with_keyword_splat
+ a, b = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: experimental_everything
+ # [Bug #20927]
+ x = { x: {} }
+ y = { y: {} }
+ A = { **x }
+ B = { x: {}, **y }
+ [A, B]
+ end;
+ assert_ractor_shareable(a)
+ assert_ractor_shareable(b)
+ assert_equal({ x: {}}, a)
+ assert_equal({ x: {}, y: {}}, b)
+ end
+
+ def test_shareable_constant_value_unshareable_literal
+ assert_raise_separately(Ractor::IsolationError, /unshareable object to C/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ C = ["Not " + "shareable"]
+ end;
+
+ assert_raise_separately(Ractor::IsolationError, /unshareable object to B::C/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ B = Class.new
+ B::C = ["Not " + "shareable"]
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::C/) do
+ # shareable_constant_value: literal
+ ::C = ["Not " + "shareable"]
+ end
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::B::C/) do
+ # shareable_constant_value: literal
+ ::B = Class.new
+ ::B::C = ["Not " + "shareable"]
+ end
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::C/) do
+ # shareable_constant_value: literal
+ ::C ||= ["Not " + "shareable"]
+ end
+ end;
+
+ assert_raise_separately(Ractor::IsolationError, /unshareable object to B::C/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ B = Class.new
+ B::C ||= ["Not " + "shareable"]
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::B::C/) do
+ # shareable_constant_value: literal
+ ::B = Class.new
+ ::B::C ||= ["Not " + "shareable"]
+ end
+ end;
+
+ assert_raise_separately(Ractor::IsolationError, /unshareable object to ...::C/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ B = Class.new
+ def self.expr; B; end
+ expr::C ||= ["Not " + "shareable"]
+ end;
+ end
+
+ def test_shareable_constant_value_nonliteral
+ assert_raise_separately(Ractor::IsolationError, /unshareable/, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ var = [:not_frozen]
+ C = var
+ end;
+
+ assert_raise_separately(Ractor::IsolationError, /unshareable/, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ D = begin [] end
+ end;
+ end
+
+ def test_shareable_constant_value_unfrozen
+ assert_raise_separately(Ractor::Error, /does not freeze object correctly/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: experimental_everything
+ o = Object.new
+ def o.freeze; self; end
+ C = [o]
+ end;
+ end
+
+ def test_shareable_constant_value_massign
+ a = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: experimental_everything
+ A, = 1
+ end;
+ assert_equal(1, a)
+ end
+
+ def test_if_after_class
+ assert_valid_syntax('module if true; Object end::Kernel; end')
+ assert_valid_syntax('module while true; break Object end::Kernel; end')
+ assert_valid_syntax('class if true; Object end::Kernel; end')
+ assert_valid_syntax('class while true; break Object end::Kernel; end')
+ end
+
+ def test_escaped_space
+ assert_syntax_error('x = \ 42', /escaped space/)
+ end
+
+ def test_label
+ expected = {:foo => 1}
+
+ code = '{"foo": 1}'
+ assert_valid_syntax(code)
+ assert_equal(expected, eval(code))
+
+ code = '{foo: 1}'
+ assert_valid_syntax(code)
+ assert_equal(expected, eval(code))
+
+ class << (obj = Object.new)
+ attr_reader :arg
+ def set(arg)
+ @arg = arg
+ end
+ end
+
+ assert_valid_syntax(code = "#{<<~"do;"}\n#{<<~'end;'}")
+ do;
+ obj.set foo:
+ 1
+ end;
+ assert_equal(expected, eval(code))
+ assert_equal(expected, obj.arg)
+
+ assert_valid_syntax(code = "#{<<~"do;"}\n#{<<~'end;'}")
+ do;
+ obj.set "foo":
+ 1
+ end;
+ assert_equal(expected, eval(code))
+ assert_equal(expected, obj.arg)
+ end
+
+ def test_ungettable_gvar
+ assert_syntax_error('$01234', /not allowed/)
+ assert_syntax_error('"#$01234"', /not allowed/)
end
=begin
@@ -1110,4 +1818,19 @@ x = __ENCODING__
assert_warning(/past scope/) {catch {|tag| eval("BEGIN{throw tag}; tap {a = 1}; a")}}
end
=end
+
+ def assert_parse(code)
+ assert_kind_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.parse(code))
+ end
+
+ def assert_parse_error(code, message)
+ assert_raise_with_message(SyntaxError, message) do
+ $VERBOSE, verbose_bak = nil, $VERBOSE
+ begin
+ RubyVM::AbstractSyntaxTree.parse(code)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ end
+ end
end
diff --git a/test/ruby/test_path.rb b/test/ruby/test_path.rb
index 6af4fb6ac0..b35e942a2a 100644
--- a/test/ruby/test_path.rb
+++ b/test/ruby/test_path.rb
@@ -236,10 +236,14 @@ class TestPath < Test::Unit::TestCase
assert_equal(ext, File.extname('.a/b/d/test.rb'))
unless /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
# trailing spaces and dots are ignored on NTFS.
- ext = ''
+ ext = '.'
end
assert_equal(ext, File.extname('a.rb.'))
- assert_equal('', File.extname('a.'))
+ if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
+ # trailing spaces and dots are ignored on NTFS.
+ ext = ''
+ end
+ assert_equal(ext, File.extname('a.'))
assert_equal('', File.extname('.x'))
assert_equal('', File.extname('..x'))
end
diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb
new file mode 100644
index 0000000000..96aa2a7fd6
--- /dev/null
+++ b/test/ruby/test_pattern_matching.rb
@@ -0,0 +1,1763 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+class TestPatternMatching < Test::Unit::TestCase
+ class NullFormatter
+ def message_for(corrections)
+ ""
+ end
+ end
+
+ def setup
+ if defined?(DidYouMean.formatter=nil)
+ @original_formatter = DidYouMean.formatter
+ DidYouMean.formatter = NullFormatter.new
+ end
+ end
+
+ def teardown
+ if defined?(DidYouMean.formatter=nil)
+ DidYouMean.formatter = @original_formatter
+ end
+ end
+
+ class C
+ class << self
+ attr_accessor :keys
+ end
+
+ def initialize(obj)
+ @obj = obj
+ end
+
+ def deconstruct
+ @obj
+ end
+
+ def deconstruct_keys(keys)
+ C.keys = keys
+ @obj
+ end
+ end
+
+ def test_basic
+ assert_block do
+ case 0
+ in 0
+ true
+ else
+ false
+ end
+ end
+
+ assert_block do
+ case 0
+ in 1
+ false
+ else
+ true
+ end
+ end
+
+ assert_raise(NoMatchingPatternError) do
+ case 0
+ in 1
+ false
+ end
+ end
+
+ begin
+ o = [0]
+ case o
+ in 1
+ false
+ end
+ rescue => e
+ assert_match o.inspect, e.message
+ end
+
+ assert_block do
+ begin
+ true
+ ensure
+ case 0
+ in 0
+ false
+ end
+ end
+ end
+
+ assert_block do
+ begin
+ true
+ ensure
+ case 0
+ in 1
+ else
+ false
+ end
+ end
+ end
+
+ assert_raise(NoMatchingPatternError) do
+ begin
+ ensure
+ case 0
+ in 1
+ end
+ end
+ end
+
+ assert_block do
+ eval(%q{
+ case true
+ in a
+ a
+ end
+ })
+ end
+
+ assert_block do
+ tap do |a|
+ tap do
+ case true
+ in a
+ a
+ end
+ end
+ end
+ end
+
+ assert_raise(NoMatchingPatternError) do
+ o = BasicObject.new
+ def o.match
+ case 0
+ in 1
+ end
+ end
+ o.match
+ end
+ end
+
+ def test_modifier
+ assert_block do
+ case 0
+ in a if a == 0
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in a if a != 0
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in a unless a != 0
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in a unless a == 0
+ else
+ true
+ end
+ end
+ end
+
+ def test_as_pattern
+ assert_block do
+ case 0
+ in 0 => a
+ a == 0
+ end
+ end
+ end
+
+ def test_alternative_pattern
+ assert_block do
+ [0, 1].all? do |i|
+ case i
+ in 0 | 1
+ true
+ end
+ end
+ end
+
+ assert_block do
+ case 0
+ in _ | _a
+ true
+ end
+ end
+
+ assert_valid_syntax(%{
+ case 0
+ in [ :a | :b, x]
+ true
+ end
+ })
+
+ assert_in_out_err(['-c'], %q{
+ case 0
+ in a | 0
+ end
+ }, [], /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
+ # NODE_DASGN_CURR
+ assert_block do
+ case 0
+ in a
+ a == 0
+ end
+ end
+
+ # NODE_DASGN
+ b = 0
+ assert_block do
+ case 1
+ in b
+ b == 1
+ end
+ end
+
+ # NODE_LASGN
+ case 0
+ in c
+ assert_equal(0, c)
+ else
+ flunk
+ end
+
+ assert_syntax_error(%q{
+ case 0
+ in ^a
+ end
+ }, /no such local variable/)
+
+ assert_syntax_error(%q{
+ case 0
+ in a, a
+ end
+ }, /duplicated variable name/)
+
+ assert_block do
+ case [0, 1, 2, 3]
+ in _, _, _a, _a
+ true
+ end
+ end
+
+ assert_syntax_error(%q{
+ case 0
+ in a, {a:}
+ end
+ }, /duplicated variable name/)
+
+ assert_syntax_error(%q{
+ case 0
+ in a, {"a":}
+ end
+ }, /duplicated variable name/)
+
+ assert_block do
+ case [0, "1"]
+ in a, "#{case 1; in a; a; end}"
+ true
+ end
+ end
+
+ assert_syntax_error(%q{
+ case [0, "1"]
+ in a, "#{case 1; in a; a; end}", a
+ end
+ }, /duplicated variable name/)
+
+ assert_block do
+ case 0
+ in a
+ assert_equal(0, a)
+ true
+ in a
+ flunk
+ end
+ end
+
+ assert_syntax_error(%q{
+ 0 => [a, a]
+ }, /duplicated variable name/)
+ end
+
+ def test_literal_value_pattern
+ assert_block do
+ case [nil, self, true, false]
+ in [nil, self, true, false]
+ true
+ end
+ end
+
+ assert_block do
+ case [0d170, 0D170, 0xaa, 0xAa, 0xAA, 0Xaa, 0XAa, 0XaA, 0252, 0o252, 0O252]
+ in [0d170, 0D170, 0xaa, 0xAa, 0xAA, 0Xaa, 0XAa, 0XaA, 0252, 0o252, 0O252]
+ true
+ end
+
+ case [0b10101010, 0B10101010, 12r, 12.3r, 1i, 12.3ri]
+ in [0b10101010, 0B10101010, 12r, 12.3r, 1i, 12.3ri]
+ true
+ end
+ end
+
+ assert_block do
+ x = 'x'
+ case ['a', 'a', x]
+ in ['a', "a", "#{x}"]
+ true
+ end
+ end
+
+ assert_block do
+ case ["a\n"]
+ in [<<END]
+a
+END
+ true
+ end
+ end
+
+ assert_block do
+ case [:a, :"a"]
+ in [:a, :"a"]
+ true
+ end
+ end
+
+ assert_block do
+ case [0, 1, 2, 3, 4, 5]
+ in [0..1, 0...2, 0.., 0..., (...5), (..5)]
+ true
+ end
+ end
+
+ assert_syntax_error(%q{
+ case 0
+ in a..b
+ end
+ }, /unexpected/)
+
+ assert_block do
+ case 'abc'
+ in /a/
+ true
+ end
+ end
+
+ assert_block do
+ a = "abc"
+ case 'abc'
+ in /#{a}/o
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in ->(i) { i == 0 }
+ true
+ end
+ end
+
+ assert_block do
+ case [%(a), %q(a), %Q(a), %w(a), %W(a), %i(a), %I(a), %s(a), %x(echo a), %(), %q(), %Q(), %w(), %W(), %i(), %I(), %s(), 'a']
+ in [%(a), %q(a), %Q(a), %w(a), %W(a), %i(a), %I(a), %s(a), %x(echo a), %(), %q(), %Q(), %w(), %W(), %i(), %I(), %s(), %r(a)]
+ true
+ end
+ end
+
+ assert_block do
+ case [__FILE__, __LINE__ + 1, __ENCODING__]
+ in [__FILE__, __LINE__, __ENCODING__]
+ true
+ end
+ end
+ end
+
+ def test_constant_value_pattern
+ assert_block do
+ case 0
+ in Integer
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in Object::Integer
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in ::Object::Integer
+ true
+ end
+ end
+ end
+
+ def test_pin_operator_value_pattern
+ assert_block do
+ a = /a/
+ case 'abc'
+ in ^a
+ true
+ end
+ end
+
+ assert_block do
+ case [0, 0]
+ in a, ^a
+ a == 0
+ end
+ end
+
+ assert_block do
+ @a = /a/
+ case 'abc'
+ in ^@a
+ true
+ end
+ end
+
+ assert_block do
+ @@TestPatternMatching = /a/
+ case 'abc'
+ in ^@@TestPatternMatching
+ true
+ end
+ end
+
+ assert_block do
+ $TestPatternMatching = /a/
+ case 'abc'
+ in ^$TestPatternMatching
+ true
+ end
+ end
+ end
+
+ def test_pin_operator_expr_pattern
+ assert_block do
+ case 'abc'
+ in ^(/a/)
+ true
+ end
+ end
+
+ assert_block do
+ case {name: '2.6', released_at: Time.new(2018, 12, 25)}
+ in {released_at: ^(Time.new(2010)..Time.new(2020))}
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in ^(0+0)
+ true
+ end
+ end
+
+ assert_valid_syntax("1 in ^(1\n)")
+ end
+
+ def test_array_pattern
+ assert_block do
+ [[0], C.new([0])].all? do |i|
+ case i
+ in 0,;
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0, 1], C.new([0, 1])].all? do |i|
+ case i
+ in 0,;
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[], C.new([])].all? do |i|
+ case i
+ in 0,;
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0, 1], C.new([0, 1])].all? do |i|
+ case i
+ in 0, 1
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0], C.new([0])].all? do |i|
+ case i
+ in 0, 1
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[], C.new([])].all? do |i|
+ case i
+ in *a
+ a == []
+ end
+ end
+ end
+
+ assert_block do
+ [[0], C.new([0])].all? do |i|
+ case i
+ in *a
+ a == [0]
+ end
+ end
+ end
+
+ assert_block do
+ [[0], C.new([0])].all? do |i|
+ case i
+ in *a, 0, 1
+ raise a # suppress "unused variable: a" warning
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0, 1], C.new([0, 1])].all? do |i|
+ case i
+ in *a, 0, 1
+ a == []
+ end
+ end
+ end
+
+ assert_block do
+ [[0, 1, 2], C.new([0, 1, 2])].all? do |i|
+ case i
+ in *a, 1, 2
+ a == [0]
+ end
+ end
+ end
+
+ assert_block do
+ [[], C.new([])].all? do |i|
+ case i
+ in *;
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0], C.new([0])].all? do |i|
+ case i
+ in *, 0, 1
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0, 1], C.new([0, 1])].all? do |i|
+ case i
+ in *, 0, 1
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0, 1, 2], C.new([0, 1, 2])].all? do |i|
+ case i
+ in *, 1, 2
+ true
+ end
+ end
+ end
+
+ assert_block do
+ case C.new([0])
+ in C(0)
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([0])
+ in Array(0)
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([])
+ in C()
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([])
+ in Array()
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([0])
+ in C[0]
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([0])
+ in Array[0]
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([])
+ in C[]
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([])
+ in Array[]
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case []
+ in []
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([])
+ in []
+ true
+ end
+ end
+
+ assert_block do
+ case [0]
+ in [0]
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([0])
+ in [0]
+ true
+ end
+ end
+
+ assert_block do
+ case [0]
+ in [0,]
+ true
+ end
+ end
+
+ assert_block do
+ case [0, 1]
+ in [0,]
+ true
+ end
+ end
+
+ assert_block do
+ case []
+ in [0, *a]
+ raise a # suppress "unused variable: a" warning
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case [0]
+ in [0, *a]
+ a == []
+ end
+ end
+
+ assert_block do
+ case [0]
+ in [0, *a, 1]
+ raise a # suppress "unused variable: a" warning
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case [0, 1]
+ in [0, *a, 1]
+ a == []
+ end
+ end
+
+ assert_block do
+ case [0, 1, 2]
+ in [0, *a, 2]
+ a == [1]
+ end
+ end
+
+ assert_block do
+ case []
+ in [0, *]
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case [0]
+ in [0, *]
+ true
+ end
+ end
+
+ assert_block do
+ case [0, 1]
+ in [0, *]
+ true
+ end
+ end
+
+ assert_block do
+ case []
+ in [0, *a]
+ raise a # suppress "unused variable: a" warning
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case [0]
+ in [0, *a]
+ a == []
+ end
+ end
+
+ assert_block do
+ case [0, 1]
+ in [0, *a]
+ a == [1]
+ end
+ end
+
+ assert_block do
+ case [0]
+ in [0, *, 1]
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case [0, 1]
+ in [0, *, 1]
+ true
+ end
+ end
+
+ assert_syntax_error(%q{
+ 0 => [a, *a]
+ }, /duplicated variable name/)
+ end
+
+ def test_find_pattern
+ [0, 1, 2] => [*, 1 => a, *]
+ assert_equal(1, a)
+
+ [0, 1, 2] => [*a, 1 => b, *c]
+ assert_equal([0], a)
+ assert_equal(1, b)
+ assert_equal([2], c)
+
+ assert_block do
+ case [0, 1, 2]
+ in [*, 9, *]
+ false
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case [0, 1, 2]
+ in [*, Integer, String, *]
+ false
+ else
+ true
+ end
+ end
+
+ [0, 1, 2] => [*a, 1 => b, 2 => c, *d]
+ assert_equal([0], a)
+ assert_equal(1, b)
+ assert_equal(2, c)
+ assert_equal([], d)
+
+ case [0, 1, 2]
+ in *, 1 => a, *;
+ assert_equal(1, a)
+ end
+
+ assert_block do
+ case [0, 1, 2]
+ in String(*, 1, *)
+ false
+ in Array(*, 1, *)
+ true
+ end
+ end
+
+ assert_block do
+ case [0, 1, 2]
+ in String[*, 1, *]
+ false
+ in Array[*, 1, *]
+ true
+ end
+ end
+
+ # https://bugs.ruby-lang.org/issues/17534
+ assert_block do
+ case [0, 1, 2]
+ in x
+ x = x # avoid a warning "assigned but unused variable - x"
+ true
+ in [*, 2, *]
+ false
+ end
+ end
+
+ assert_syntax_error(%q{
+ 0 => [*a, a, b, *b]
+ }, /duplicated variable name/)
+ end
+
+ def test_hash_pattern
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in a: 0
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in a: 0
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i|
+ case i
+ in a: 0
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in a: 0, b: 1
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i|
+ case i
+ in a: 0, b: 1
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1, c: 2}, C.new({a: 0, b: 1, c: 2})].all? do |i|
+ case i
+ in a: 0, b: 1
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in a:
+ raise a # suppress "unused variable: a" warning
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in a:
+ a == 0
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i|
+ case i
+ in a:
+ a == 0
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in "a": 0
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in "a":;
+ a == 0
+ end
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in **a
+ a == {}
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in **a
+ a == {a: 0}
+ end
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in **;
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in **;
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in a:, **b
+ raise a # suppress "unused variable: a" warning
+ raise b # suppress "unused variable: b" warning
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in a:, **b
+ a == 0 && b == {}
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i|
+ case i
+ in a:, **b
+ a == 0 && b == {b: 1}
+ end
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in **nil
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in **nil
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in a:, **nil
+ assert_equal(0, a)
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i|
+ case i
+ in a:, **nil
+ assert_equal(0, a)
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0})
+ in C(a: 0)
+ true
+ end
+ end
+
+ assert_block do
+ case {a: 0}
+ in C(a: 0)
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0})
+ in C[a: 0]
+ true
+ end
+ end
+
+ assert_block do
+ case {a: 0}
+ in C[a: 0]
+ else
+ true
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in {a: 0}
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in {a: 0}
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i|
+ case i
+ in {a: 0}
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in {}
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in {}
+ else
+ true
+ end
+ end
+ end
+
+ bug18890 = assert_warning(/(?:.*:[47]: warning: possibly useless use of a literal in void context\n){2}/) do
+ eval("#{<<~';;;'}")
+ proc do |i|
+ case i
+ in a:
+ 0 # line 4
+ a
+ in "b":
+ 0 # line 7
+ b
+ else
+ false
+ end
+ end
+ ;;;
+ end
+ [{a: 42}, {b: 42}].each do |i|
+ assert_block('newline should be significant after pattern label') do
+ bug18890.call(i)
+ end
+ end
+
+ assert_syntax_error(%q{
+ case _
+ in a:, a:
+ end
+ }, /duplicated key name/)
+
+ assert_syntax_error(%q{
+ case _
+ in a?:
+ end
+ }, /key must be valid as local variables/)
+
+ assert_block do
+ case {a?: true}
+ in a?: true
+ true
+ end
+ end
+
+ assert_block do
+ case {a: 0, b: 1}
+ in {a: 1,}
+ false
+ in {a:,}
+ _a = a
+ true
+ end
+ end
+
+ assert_block do
+ case {a: 0}
+ in {a: 1
+ }
+ false
+ in {a:
+ 2}
+ false
+ in a: {b:}, c:
+ _b = b
+ p c
+ in {a:
+ }
+ _a = a
+ true
+ end
+ end
+
+ assert_syntax_error(%q{
+ case _
+ in "a-b":
+ end
+ }, /key must be valid as local variables/)
+
+ assert_block do
+ case {"a-b": true}
+ in "a-b": true
+ true
+ end
+ end
+
+ assert_syntax_error(%q{
+ case _
+ in "#{a}": a
+ end
+ }, /symbol literal with interpolation is not allowed/)
+
+ assert_syntax_error(%q{
+ case _
+ in "#{a}":
+ end
+ }, /symbol literal with interpolation is not allowed/)
+ end
+
+ def test_paren
+ assert_block do
+ case 0
+ in (0)
+ true
+ end
+ end
+ end
+
+ def test_nomatchingpatternerror
+ assert_equal(StandardError, NoMatchingPatternError.superclass)
+ end
+
+ def test_invalid_syntax
+ assert_syntax_error(%q{
+ case 0
+ in a, b:
+ end
+ }, /unexpected/)
+
+ assert_syntax_error(%q{
+ case 0
+ in [a:]
+ end
+ }, /unexpected/)
+
+ assert_syntax_error(%q{
+ case 0
+ in {a}
+ end
+ }, /unexpected/)
+
+ assert_syntax_error(%q{
+ case 0
+ in {0 => a}
+ end
+ }, /unexpected/)
+ end
+
+ ################################################################
+
+ class CTypeError
+ def deconstruct
+ nil
+ end
+
+ def deconstruct_keys(keys)
+ nil
+ end
+ end
+
+ def test_deconstruct
+ assert_raise(TypeError) do
+ case CTypeError.new
+ in []
+ end
+ end
+ end
+
+ def test_deconstruct_keys
+ assert_raise(TypeError) do
+ case CTypeError.new
+ in {}
+ end
+ end
+
+ assert_block do
+ case C.new({})
+ in {}
+ C.keys == nil
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0, b: 0, c: 0})
+ in {a: 0, b:}
+ assert_equal(0, b)
+ C.keys == [:a, :b]
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0, b: 0, c: 0})
+ in {a: 0, b:, **}
+ assert_equal(0, b)
+ C.keys == [:a, :b]
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0, b: 0, c: 0})
+ in {a: 0, b:, **r}
+ assert_equal(0, b)
+ assert_equal({c: 0}, r)
+ C.keys == nil
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0, b: 0, c: 0})
+ in {**}
+ C.keys == []
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0, b: 0, c: 0})
+ in {**r}
+ assert_equal({a: 0, b: 0, c: 0}, r)
+ C.keys == nil
+ end
+ end
+ end
+
+ ################################################################
+
+ class CDeconstructCache
+ def initialize(v)
+ @v = v
+ end
+
+ def deconstruct
+ @v.shift
+ end
+ end
+
+ def test_deconstruct_cache
+ assert_block do
+ case CDeconstructCache.new([[0]])
+ in [1]
+ in [0]
+ true
+ end
+ end
+
+ assert_block do
+ case CDeconstructCache.new([[0, 1]])
+ in [1,]
+ in [0,]
+ true
+ end
+ end
+
+ assert_block do
+ case CDeconstructCache.new([[[0]]])
+ in [[1]]
+ in [[*a]]
+ a == [0]
+ end
+ end
+
+ assert_block do
+ case CDeconstructCache.new([[0]])
+ in [x] if x > 0
+ in [0]
+ true
+ end
+ end
+
+ assert_block do
+ case CDeconstructCache.new([[0]])
+ in []
+ in [1] | [0]
+ true
+ end
+ end
+
+ assert_block do
+ case CDeconstructCache.new([[0]])
+ in [1] => _
+ in [0] => _
+ true
+ end
+ end
+
+ assert_block do
+ case CDeconstructCache.new([[0]])
+ in C[0]
+ in CDeconstructCache[0]
+ true
+ end
+ end
+
+ assert_block do
+ case [CDeconstructCache.new([[0], [1]])]
+ in [[1]]
+ false
+ in [[1]]
+ true
+ end
+ end
+
+ assert_block do
+ case CDeconstructCache.new([[0, :a, 1]])
+ in [*, String => x, *]
+ false
+ in [*, Symbol => x, *]
+ x == :a
+ end
+ end
+ end
+
+ ################################################################
+
+ class TestPatternMatchingRefinements < Test::Unit::TestCase
+ class C1
+ def deconstruct
+ [:C1]
+ end
+ end
+
+ class C2
+ end
+
+ module M
+ refine Array do
+ def deconstruct
+ [0]
+ end
+ end
+
+ refine Hash do
+ def deconstruct_keys(_)
+ {a: 0}
+ end
+ end
+
+ refine C2.singleton_class do
+ def ===(obj)
+ obj.kind_of?(C1)
+ end
+ end
+ end
+
+ using M
+
+ def test_refinements
+ assert_block do
+ case []
+ in [0]
+ true
+ end
+ end
+
+ assert_block do
+ case {}
+ in {a: 0}
+ true
+ end
+ end
+
+ assert_block do
+ case C1.new
+ in C2(:C1)
+ true
+ end
+ end
+ end
+ end
+
+ ################################################################
+
+ def test_struct
+ assert_block do
+ s = Struct.new(:a, :b)
+ case s[0, 1]
+ in 0, 1
+ true
+ end
+ end
+
+ s = Struct.new(:a, :b, keyword_init: true)
+ assert_block do
+ case s[a: 0, b: 1]
+ in **r
+ r == {a: 0, b: 1}
+ end
+ end
+ assert_block do
+ s = Struct.new(:a, :b, keyword_init: true)
+ case s[a: 0, b: 1]
+ in a:, b:
+ a == 0 && b == 1
+ end
+ end
+ assert_block do
+ s = Struct.new(:a, :b, keyword_init: true)
+ case s[a: 0, b: 1]
+ in a:, c:
+ raise a # suppress "unused variable: a" warning
+ raise c # suppress "unused variable: c" warning
+ flunk
+ in a:, b:, c:
+ flunk
+ in b:
+ b == 1
+ end
+ end
+ end
+
+ ################################################################
+
+ def test_one_line
+ 1 => a
+ assert_equal 1, a
+ assert_raise(NoMatchingPatternError) do
+ {a: 1} => {a: 0}
+ end
+
+ [1, 2] => a, b
+ assert_equal 1, a
+ assert_equal 2, b
+
+ {a: 1} => a:
+ assert_equal 1, a
+
+ assert_equal true, (1 in 1)
+ assert_equal false, (1 in 2)
+ end
+
+ def test_bug18990
+ {a: 0} => a:
+ assert_equal 0, a
+ {a: 0} => a:
+ assert_equal 0, a
+
+ {a: 0} in a:
+ assert_equal 0, a
+ {a: 0} in a:
+ assert_equal 0, a
+ end
+
+ ################################################################
+
+ def test_single_pattern_error_value_pattern
+ assert_raise_with_message(NoMatchingPatternError, "0: 1 === 0 does not return true") do
+ 0 => 1
+ end
+ end
+
+ def test_single_pattern_error_array_pattern
+ assert_raise_with_message(NoMatchingPatternError, "[]: Hash === [] does not return true") do
+ [] => Hash[]
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "0: 0 does not respond to #deconstruct") do
+ 0 => []
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "[0]: [0] length mismatch (given 1, expected 0)") do
+ [0] => []
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "[]: [] length mismatch (given 0, expected 1+)") do
+ [] => [_, *]
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "[0, 0]: 1 === 0 does not return true") do
+ [0, 0] => [0, 1]
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "[0, 0]: 1 === 0 does not return true") do
+ [0, 0] => [*, 0, 1]
+ end
+ end
+
+ def test_single_pattern_error_find_pattern
+ assert_raise_with_message(NoMatchingPatternError, "[]: Hash === [] does not return true") do
+ [] => Hash[*, _, *]
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "0: 0 does not respond to #deconstruct") do
+ 0 => [*, _, *]
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "[]: [] length mismatch (given 0, expected 1+)") do
+ [] => [*, _, *]
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "[0]: [0] does not match to find pattern") do
+ [0] => [*, 1, *]
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "[0]: [0] does not match to find pattern") do
+ [0] => [*, {a:}, *]
+ raise a # suppress "unused variable: a" warning
+ end
+ end
+
+ def test_single_pattern_error_hash_pattern
+ assert_raise_with_message(NoMatchingPatternError, "{}: Array === {} does not return true") do
+ {} => Array[a:]
+ raise a # suppress "unused variable: a" warning
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "0: 0 does not respond to #deconstruct_keys") do
+ 0 => {a:}
+ raise a # suppress "unused variable: a" warning
+ end
+
+ assert_raise_with_message(NoMatchingPatternKeyError, "{a: 0}: key not found: :aa") do
+ {a: 0} => {aa:}
+ raise aa # suppress "unused variable: aa" warning
+ rescue NoMatchingPatternKeyError => e
+ assert_equal({a: 0}, e.matchee)
+ assert_equal(:aa, e.key)
+ raise e
+ end
+
+ assert_raise_with_message(NoMatchingPatternKeyError, "{a: {b: 0}}: key not found: :bb") do
+ {a: {b: 0}} => {a: {bb:}}
+ raise bb # suppress "unused variable: bb" warning
+ rescue NoMatchingPatternKeyError => e
+ assert_equal({b: 0}, e.matchee)
+ assert_equal(:bb, e.key)
+ raise e
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "{a: 0}: 1 === 0 does not return true") do
+ {a: 0} => {a: 1}
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "{a: 0}: {a: 0} is not empty") do
+ {a: 0} => {}
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "[{a: 0}]: rest of {a: 0} is not empty") do
+ [{a: 0}] => [{**nil}]
+ end
+ end
+
+ def test_single_pattern_error_as_pattern
+ assert_raise_with_message(NoMatchingPatternError, "[0]: 1 === 0 does not return true") do
+ case [0]
+ in [1] => _
+ end
+ end
+ end
+
+ def test_single_pattern_error_alternative_pattern
+ assert_raise_with_message(NoMatchingPatternError, "0: 2 === 0 does not return true") do
+ 0 => 1 | 2
+ end
+ end
+
+ def test_single_pattern_error_guard_clause
+ assert_raise_with_message(NoMatchingPatternError, "0: guard clause does not return true") do
+ case 0
+ in _ if false
+ end
+ end
+
+ assert_raise_with_message(NoMatchingPatternError, "0: guard clause does not return true") do
+ case 0
+ in _ unless true
+ end
+ end
+ end
+end
diff --git a/test/ruby/test_pipe.rb b/test/ruby/test_pipe.rb
index efca8f28c1..9fa42fd375 100644
--- a/test/ruby/test_pipe.rb
+++ b/test/ruby/test_pipe.rb
@@ -27,4 +27,23 @@ class TestPipe < Test::Unit::TestCase
end
end
end
+
+ def test_stdout_epipe
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ io = STDOUT
+ begin
+ save = io.dup
+ IO.popen("echo", "w", out: IO::NULL) do |f|
+ io.reopen(f)
+ Process.wait(f.pid)
+ assert_raise(Errno::EPIPE) do
+ io.print "foo\n"
+ end
+ end
+ ensure
+ io.reopen(save)
+ end
+ end;
+ end
end
diff --git a/test/ruby/test_primitive.rb b/test/ruby/test_primitive.rb
index 19af44ad32..f1db934000 100644
--- a/test/ruby/test_primitive.rb
+++ b/test/ruby/test_primitive.rb
@@ -26,24 +26,31 @@ class TestRubyPrimitive < Test::Unit::TestCase
assert_equal 4, c
end
- C = 1
- class A
- Const = 1
- class B
- Const = 2
- class C
- Const = 3
- def const
- Const
+ C_Setup = -> do
+ remove_const :C if defined? ::TestRubyPrimitive::C
+ remove_const :A if defined? ::TestRubyPrimitive::A
+
+ C = 1
+ class A
+ Const = 1
+ class B
+ Const = 2
+ class C
+ Const = 3
+ def const
+ Const
+ end
end
end
end
+ (1..2).map {
+ A::B::C::Const
+ }
end
- (1..2).map {
- A::B::C::Const
- }
def test_constant
+ C_Setup.call
+
assert_equal 1, C
assert_equal 1, C
assert_equal 1, A::Const
@@ -145,42 +152,60 @@ class TestRubyPrimitive < Test::Unit::TestCase
assert_equal 7, ($test_ruby_primitive_gvar = 7)
end
- class A7
- @@c = 1
- def m
- @@c += 1
+ A7_Setup = -> do
+ remove_const :A7 if defined? TestRubyPrimitive::A7
+
+ class A7
+ @@c = 1
+ def m
+ @@c += 1
+ end
end
end
def test_cvar_from_instance_method
+ A7_Setup.call
+
assert_equal 2, A7.new.m
assert_equal 3, A7.new.m
assert_equal 4, A7.new.m
end
- class A8
- @@c = 1
- class << self
- def m
- @@c += 1
+ A8_Setup = -> do
+ remove_const :A8 if defined? TestRubyPrimitive::A8
+
+ class A8
+ @@c = 1
+ class << self
+ def m
+ @@c += 1
+ end
end
end
end
def test_cvar_from_singleton_method
+ A8_Setup.call
+
assert_equal 2, A8.m
assert_equal 3, A8.m
assert_equal 4, A8.m
end
- class A9
- @@c = 1
- def self.m
- @@c += 1
+ A9_Setup = -> do
+ remove_const :A8 if defined? TestRubyPrimitive::A8
+
+ class A9
+ @@c = 1
+ def self.m
+ @@c += 1
+ end
end
end
def test_cvar_from_singleton_method2
+ A9_Setup.call
+
assert_equal 2, A9.m
assert_equal 3, A9.m
assert_equal 4, A9.m
@@ -199,6 +224,9 @@ class TestRubyPrimitive < Test::Unit::TestCase
@iv += 2
assert_equal 4, @iv
+ # init @@cv
+ @@cv = nil
+
@@cv ||= 1
assert_equal 1, @@cv
@@cv &&= 2
diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb
index df9bb5f549..959ea87f25 100644
--- a/test/ruby/test_proc.rb
+++ b/test/ruby/test_proc.rb
@@ -4,7 +4,6 @@ require 'test/unit'
class TestProc < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
end
def teardown
@@ -53,10 +52,14 @@ class TestProc < Test::Unit::TestCase
assert_equal(5, x)
end
- def assert_arity(n)
+ def assert_arity(n, &block)
meta = class << self; self; end
- meta.class_eval {define_method(:foo, Proc.new)}
- assert_equal(n, method(:foo).arity)
+ b = Proc.new(&block)
+ meta.class_eval {
+ remove_method(:foo_arity) if method_defined?(:foo_arity)
+ define_method(:foo_arity, b)
+ }
+ assert_equal(n, method(:foo_arity).arity)
end
def test_arity
@@ -133,6 +136,14 @@ class TestProc < Test::Unit::TestCase
lambda { x }
end
+ def m_nest0(&block)
+ block
+ end
+
+ def m_nest(&block)
+ [m_nest0(&block), m_nest0(&block)]
+ end
+
def test_eq
a = m(1)
b = m(2)
@@ -144,42 +155,74 @@ class TestProc < Test::Unit::TestCase
a = lambda {|x| lambda {} }.call(1)
b = lambda {}
assert_not_equal(a, b, "[ruby-dev:22601]")
+
+ assert_equal(*m_nest{}, "[ruby-core:84583] Feature #14627")
end
- def test_block_par
- assert_equal(10, Proc.new{|&b| b.call(10)}.call {|x| x})
- assert_equal(12, Proc.new{|a,&b| b.call(a)}.call(12) {|x| x})
+ def test_hash_equal
+ # iseq backed proc
+ p1 = proc {}
+ p2 = p1.dup
+
+ assert_equal p1.hash, p2.hash
+
+ # ifunc backed proc
+ p1 = {}.to_proc
+ p2 = p1.dup
+
+ assert_equal p1.hash, p2.hash
+
+ # symbol backed proc
+ p1 = :hello.to_proc
+ p2 = :hello.to_proc
+
+ assert_equal p1.hash, p2.hash
end
- def test_safe
- safe = $SAFE
- c = Class.new
- x = c.new
+ def test_hash_uniqueness
+ def self.capture(&block)
+ block
+ end
- p = proc {
- $SAFE += 1
- proc {$SAFE}
- }.call
- assert_equal(safe, $SAFE)
- assert_equal(safe + 1, p.call)
- assert_equal(safe, $SAFE)
+ procs = Array.new(1000){capture{:foo }}
+ assert_operator(procs.map(&:hash).uniq.size, :>=, 500)
+
+ # iseq backed proc
+ unique_hashes = 1000.times.map { proc {}.hash }.uniq
+ assert_operator(unique_hashes.size, :>=, 500)
+
+ # ifunc backed proc
+ unique_hashes = 1000.times.map { {}.to_proc.hash }.uniq
+ assert_operator(unique_hashes.size, :>=, 500)
+
+ # symbol backed proc
+ unique_hashes = 1000.times.map { |i| :"test#{i}".to_proc.hash }.uniq
+ assert_operator(unique_hashes.size, :>=, 500)
+ end
+
+ def test_hash_does_not_change_after_compaction
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
- c.class_eval {define_method(:safe, p)}
- assert_equal(safe, x.safe)
- assert_equal(safe, x.method(:safe).call)
- assert_equal(safe, x.method(:safe).to_proc.call)
+ # [Bug #20853]
+ [
+ "proc {}", # iseq backed proc
+ "{}.to_proc", # ifunc backed proc
+ ":hello.to_proc", # symbol backed proc
+ ].each do |proc|
+ assert_separately([], <<~RUBY)
+ p1 = #{proc}
+ hash = p1.hash
- p = proc {$SAFE += 1}
- assert_equal(safe + 1, p.call)
- assert_equal(safe, $SAFE)
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
- c.class_eval {define_method(:inc, p)}
- assert_equal(safe + 1, proc {x.inc; $SAFE}.call)
- assert_equal(safe, $SAFE)
- assert_equal(safe + 1, proc {x.method(:inc).call; $SAFE}.call)
- assert_equal(safe, $SAFE)
- assert_equal(safe + 1, proc {x.method(:inc).to_proc.call; $SAFE}.call)
- assert_equal(safe, $SAFE)
+ assert_equal(hash, p1.hash, "proc is `#{proc}`")
+ RUBY
+ end
+ end
+
+ def test_block_par
+ assert_equal(10, Proc.new{|&b| b.call(10)}.call {|x| x})
+ assert_equal(12, Proc.new{|a,&b| b.call(a)}.call(12) {|x| x})
end
def m2
@@ -216,18 +259,24 @@ class TestProc < Test::Unit::TestCase
end
def test_block_given_method
+ verbose_bak, $VERBOSE = $VERBOSE, nil
m = method(:m_block_given?)
assert(!m.call, "without block")
assert(m.call {}, "with block")
assert(!m.call, "without block second")
+ ensure
+ $VERBOSE = verbose_bak
end
def test_block_given_method_to_proc
+ verbose_bak, $VERBOSE = $VERBOSE, nil
bug8341 = '[Bug #8341]'
m = method(:m_block_given?).to_proc
assert(!m.call, "#{bug8341} without block")
assert(m.call {}, "#{bug8341} with block")
assert(!m.call, "#{bug8341} without block second")
+ ensure
+ $VERBOSE = verbose_bak
end
def test_block_persist_between_calls
@@ -288,7 +337,7 @@ class TestProc < Test::Unit::TestCase
def test_curry_given_blocks
b = lambda {|x, y, &blk| blk.call(x + y) }.curry
- b = b.call(2) { raise }
+ b = assert_warning(/given block not used/) {b.call(2) { raise }}
b = b.call(3) {|x| x + 4 }
assert_equal(9, b)
end
@@ -298,7 +347,6 @@ class TestProc < Test::Unit::TestCase
assert_equal(false, l.lambda?)
assert_equal(false, l.curry.lambda?, '[ruby-core:24127]')
assert_equal(false, proc(&l).lambda?)
- assert_equal(false, lambda(&l).lambda?)
assert_equal(false, Proc.new(&l).lambda?)
l = lambda {}
assert_equal(true, l.lambda?)
@@ -308,6 +356,23 @@ class TestProc < Test::Unit::TestCase
assert_equal(true, Proc.new(&l).lambda?)
end
+ def helper_test_warn_lambda_with_passed_block &b
+ lambda(&b)
+ end
+
+ def test_lambda_warning_pass_proc
+ assert_raise(ArgumentError) do
+ b = proc{}
+ lambda(&b)
+ end
+ end
+
+ def test_lambda_warning_pass_block
+ assert_raise(ArgumentError) do
+ helper_test_warn_lambda_with_passed_block{}
+ end
+ end
+
def test_curry_ski_fib
s = proc {|f, g, x| f[x][g[x]] }.curry
k = proc {|x, y| x }.curry
@@ -364,6 +429,7 @@ class TestProc < Test::Unit::TestCase
end
def test_dup_clone
+ # iseq backed proc
b = proc {|x| x + "bar" }
class << b; attr_accessor :foo; end
@@ -376,6 +442,50 @@ class TestProc < Test::Unit::TestCase
assert_equal("foobar", bc.call("foo"))
bc.foo = :foo
assert_equal(:foo, bc.foo)
+
+ # ifunc backed proc
+ b = {foo: "bar"}.to_proc
+
+ bd = b.dup
+ assert_equal("bar", bd.call(:foo))
+
+ bc = b.clone
+ assert_equal("bar", bc.call(:foo))
+
+ # symbol backed proc
+ b = :to_s.to_proc
+
+ bd = b.dup
+ assert_equal("testing", bd.call(:testing))
+
+ bc = b.clone
+ assert_equal("testing", bc.call(:testing))
+ end
+
+ def test_dup_subclass
+ c1 = Class.new(Proc)
+ assert_equal c1, c1.new{}.dup.class, '[Bug #17545]'
+ c1 = Class.new(Proc) {def initialize_dup(*) throw :initialize_dup; end}
+ assert_throw(:initialize_dup) {c1.new{}.dup}
+ end
+
+ def test_dup_ifunc_proc_bug_20950
+ assert_normal_exit(<<~RUBY, "[Bug #20950]")
+ p = { a: 1 }.to_proc
+ 100.times do
+ p = p.dup
+ GC.start
+ p.call
+ rescue ArgumentError
+ end
+ RUBY
+ end
+
+ def test_clone_subclass
+ c1 = Class.new(Proc)
+ assert_equal c1, c1.new{}.clone.class, '[Bug #17545]'
+ c1 = Class.new(Proc) {def initialize_clone(*) throw :initialize_clone; end}
+ assert_throw(:initialize_clone) {c1.new{}.clone}
end
def test_binding
@@ -397,9 +507,23 @@ class TestProc < Test::Unit::TestCase
assert_equal([1, 2, 3], b.eval("[x, y, z]"))
end
+ def test_binding_source_location
+ b, expected_location = binding, [__FILE__, __LINE__]
+ assert_equal(expected_location, b.source_location)
+
+ file, lineno = method(:source_location_test).to_proc.binding.source_location
+ assert_match(/^#{ Regexp.quote(__FILE__) }$/, file)
+ assert_equal(@@line_of_source_location_test[0], lineno, 'Bug #2427')
+ end
+
+ def test_binding_error_unless_ruby_frame
+ define_singleton_method :binding_from_c!, method(:binding).to_proc >> ->(bndg) {bndg}
+ assert_raise(RuntimeError) { binding_from_c! }
+ end
+
def test_proc_lambda
assert_raise(ArgumentError) { proc }
- assert_raise(ArgumentError) { lambda }
+ assert_raise(ArgumentError) { assert_warn(/deprecated/) {lambda} }
o = Object.new
def o.foo
@@ -407,14 +531,18 @@ class TestProc < Test::Unit::TestCase
1.times { b = lambda }
b
end
- assert_equal(:foo, o.foo { :foo }.call)
+ assert_raise(ArgumentError) do
+ assert_deprecated_warning {o.foo { :foo }}.call
+ end
- def o.foo(&b)
+ def o.bar(&b)
b = nil
1.times { b = lambda }
b
end
- assert_equal(:foo, o.foo { :foo }.call)
+ assert_raise(ArgumentError) do
+ assert_deprecated_warning {o.bar { :foo }}.call
+ end
end
def test_arity2
@@ -800,6 +928,115 @@ class TestProc < Test::Unit::TestCase
assert_equal [[1, 2], Proc, :x], (pr.call(1, 2){|x| x})
end
+ def test_proc_args_single_kw_no_autosplat
+ pr = proc {|c, a: 1| [c, a] }
+ assert_equal [nil, 1], pr.call()
+ assert_equal [1, 1], pr.call(1)
+ assert_equal [[1], 1], pr.call([1])
+ assert_equal [1, 1], pr.call(1,2)
+ assert_equal [[1, 2], 1], pr.call([1,2])
+
+ assert_equal [nil, 3], pr.call(a: 3)
+ assert_equal [1, 3], pr.call(1, a: 3)
+ assert_equal [[1], 3], pr.call([1], a: 3)
+ assert_equal [1, 3], pr.call(1,2, a: 3)
+ assert_equal [[1, 2], 3], pr.call([1,2], a: 3)
+ end
+
+ def test_proc_args_single_kwsplat_no_autosplat
+ pr = proc {|c, **kw| [c, kw] }
+ assert_equal [nil, {}], pr.call()
+ assert_equal [1, {}], pr.call(1)
+ assert_equal [[1], {}], pr.call([1])
+ assert_equal [1, {}], pr.call(1,2)
+ assert_equal [[1, 2], {}], pr.call([1,2])
+
+ assert_equal [nil, {a: 3}], pr.call(a: 3)
+ assert_equal [1, {a: 3}], pr.call(1, a: 3)
+ assert_equal [[1], {a: 3}], pr.call([1], a: 3)
+ assert_equal [1, {a: 3}], pr.call(1,2, a: 3)
+ assert_equal [[1, 2], {a: 3}], pr.call([1,2], a: 3)
+ end
+
+ def test_proc_args_multiple_kw_autosplat
+ pr = proc {|c, b, a: 1| [c, b, a] }
+ assert_equal [1, 2, 1], pr.call([1,2])
+
+ pr = proc {|c=nil, b=nil, a: 1| [c, b, a] }
+ assert_equal [nil, nil, 1], pr.call([])
+ assert_equal [1, nil, 1], pr.call([1])
+ assert_equal [1, 2, 1], pr.call([1,2])
+
+ pr = proc {|c, b=nil, a: 1| [c, b, a] }
+ assert_equal [1, nil, 1], pr.call([1])
+ assert_equal [1, 2, 1], pr.call([1,2])
+
+ pr = proc {|c=nil, b, a: 1| [c, b, a] }
+ assert_equal [nil, 1, 1], pr.call([1])
+ assert_equal [1, 2, 1], pr.call([1,2])
+
+ pr = proc {|c, *b, a: 1| [c, b, a] }
+ assert_equal [1, [], 1], pr.call([1])
+ assert_equal [1, [2], 1], pr.call([1,2])
+
+ pr = proc {|*c, b, a: 1| [c, b, a] }
+ assert_equal [[], 1, 1], pr.call([1])
+ assert_equal [[1], 2, 1], pr.call([1,2])
+ end
+
+ def test_proc_args_multiple_kwsplat_autosplat
+ pr = proc {|c, b, **kw| [c, b, kw] }
+ assert_equal [1, 2, {}], pr.call([1,2])
+
+ pr = proc {|c=nil, b=nil, **kw| [c, b, kw] }
+ assert_equal [nil, nil, {}], pr.call([])
+ assert_equal [1, nil, {}], pr.call([1])
+ assert_equal [1, 2, {}], pr.call([1,2])
+
+ pr = proc {|c, b=nil, **kw| [c, b, kw] }
+ assert_equal [1, nil, {}], pr.call([1])
+ assert_equal [1, 2, {}], pr.call([1,2])
+
+ pr = proc {|c=nil, b, **kw| [c, b, kw] }
+ assert_equal [nil, 1, {}], pr.call([1])
+ assert_equal [1, 2, {}], pr.call([1,2])
+
+ pr = proc {|c, *b, **kw| [c, b, kw] }
+ assert_equal [1, [], {}], pr.call([1])
+ assert_equal [1, [2], {}], pr.call([1,2])
+
+ pr = proc {|*c, b, **kw| [c, b, kw] }
+ assert_equal [[], 1, {}], pr.call([1])
+ assert_equal [[1], 2, {}], pr.call([1,2])
+ end
+
+ def test_proc_args_only_rest
+ pr = proc {|*c| c }
+ assert_equal [], pr.call()
+ assert_equal [1], pr.call(1)
+ assert_equal [[1]], pr.call([1])
+ assert_equal [1, 2], pr.call(1,2)
+ assert_equal [[1, 2]], pr.call([1,2])
+ end
+
+ def test_proc_args_rest_kw
+ pr = proc {|*c, a: 1| [c, a] }
+ assert_equal [[], 1], pr.call()
+ assert_equal [[1], 1], pr.call(1)
+ assert_equal [[[1]], 1], pr.call([1])
+ assert_equal [[1, 2], 1], pr.call(1,2)
+ assert_equal [[[1, 2]], 1], pr.call([1,2])
+ end
+
+ def test_proc_args_rest_kwsplat
+ pr = proc {|*c, **kw| [c, kw] }
+ assert_equal [[], {}], pr.call()
+ assert_equal [[1], {}], pr.call(1)
+ assert_equal [[[1]], {}], pr.call([1])
+ assert_equal [[1, 2], {}], pr.call(1,2)
+ assert_equal [[[1, 2]], {}], pr.call([1,2])
+ end
+
def test_proc_args_pos_rest_post_block
pr = proc {|a,b,*c,d,e,&f|
[a, b, c, d, e, f.class, f&&f.call(:x)]
@@ -1103,6 +1340,36 @@ class TestProc < Test::Unit::TestCase
assert_equal([1,2,[3],4,5], r, "[ruby-core:19485]")
end
+ def test_proc_autosplat
+ def self.a(arg, kw)
+ yield arg
+ yield arg, **kw
+ yield arg, kw
+ end
+
+ arr = []
+ a([1,2,3], {}) do |arg1, arg2=0|
+ arr << [arg1, arg2]
+ end
+ assert_equal([[1, 2], [[1, 2, 3], 0], [[1, 2, 3], {}]], arr)
+
+ arr = []
+ a([1,2,3], a: 1) do |arg1, arg2=0|
+ arr << [arg1, arg2]
+ end
+ assert_equal([[1, 2], [[1, 2, 3], {a: 1}], [[1, 2, 3], {a: 1}]], arr)
+ end
+
+ def test_proc_single_arg_with_keywords_accepted_and_yielded
+ def self.a
+ yield [], **{a: 1}
+ end
+ res = a do |arg, **opts|
+ [arg, opts]
+ end
+ assert_equal([[], {a: 1}], res)
+ end
+
def test_parameters
assert_equal([], proc {}.parameters)
assert_equal([], proc {||}.parameters)
@@ -1115,7 +1382,8 @@ class TestProc < Test::Unit::TestCase
assert_equal([[:opt, :a], [:rest, :b], [:opt, :c]], proc {|a, *b, c|}.parameters)
assert_equal([[:opt, :a], [:rest, :b], [:opt, :c], [:block, :d]], proc {|a, *b, c, &d|}.parameters)
assert_equal([[:opt, :a], [:opt, :b], [:rest, :c], [:opt, :d], [:block, :e]], proc {|a, b=:b, *c, d, &e|}.parameters)
- assert_equal([[:opt, nil], [:block, :b]], proc {|(a), &b|a}.parameters)
+ assert_equal([[:opt], [:block, :b]], proc {|(a), &b|a}.parameters)
+ assert_equal([[:opt], [:rest, :_], [:opt]], proc {|(a_), *_, (b_)|}.parameters)
assert_equal([[:opt, :a], [:opt, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:opt, :f], [:opt, :g], [:block, :h]], proc {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters)
assert_equal([[:req]], method(:putc).parameters)
@@ -1123,6 +1391,59 @@ class TestProc < Test::Unit::TestCase
pr = eval("proc{|"+"(_),"*30+"|}")
assert_empty(pr.parameters.map{|_,n|n}.compact)
+
+ assert_equal([[:opt]], proc { it }.parameters)
+ end
+
+ def test_proc_autosplat_with_multiple_args_with_ruby2_keywords_splat_bug_19759
+ def self.yielder_ab(splat)
+ yield([:a, :b], *splat)
+ end
+
+ res = yielder_ab([[:aa, :bb], Hash.ruby2_keywords_hash({k: :k})]) do |a, b, k:|
+ [a, b, k]
+ end
+ assert_equal([[:a, :b], [:aa, :bb], :k], res)
+
+ def self.yielder(splat)
+ yield(*splat)
+ end
+ res = yielder([ [:a, :b] ]){|a, b, **| [a, b]}
+ assert_equal([:a, :b], res)
+
+ res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({}) ]){|a, b, **| [a, b]}
+ assert_equal([[:a, :b], nil], res)
+
+ res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({c: 1}) ]){|a, b, **| [a, b]}
+ assert_equal([[:a, :b], nil], res)
+
+ res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({}) ]){|a, b, **nil| [a, b]}
+ assert_equal([[:a, :b], nil], res)
+ end
+
+ def test_parameters_lambda
+ assert_equal([], proc {}.parameters(lambda: true))
+ assert_equal([], proc {||}.parameters(lambda: true))
+ assert_equal([[:req, :a]], proc {|a|}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:req, :b]], proc {|a, b|}.parameters(lambda: true))
+ assert_equal([[:opt, :a], [:block, :b]], proc {|a=:a, &b|}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:opt, :b]], proc {|a, b=:b|}.parameters(lambda: true))
+ assert_equal([[:rest, :a]], proc {|*a|}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:rest, :b], [:block, :c]], proc {|a, *b, &c|}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:rest, :b], [:req, :c]], proc {|a, *b, c|}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], proc {|a, *b, c, &d|}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], proc {|a, b=:b, *c, d, &e|}.parameters(lambda: true))
+ assert_equal([[:req], [:block, :b]], proc {|(a), &b|a}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:req, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:req, :f], [:req, :g], [:block, :h]], proc {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters(lambda: true))
+
+ pr = eval("proc{|"+"(_),"*30+"|}")
+ assert_empty(pr.parameters(lambda: true).map{|_,n|n}.compact)
+
+ assert_equal([[:opt, :a]], lambda {|a|}.parameters(lambda: false))
+ assert_equal([[:opt, :a], [:opt, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:opt, :f], [:opt, :g], [:block, :h]], lambda {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters(lambda: false))
+
+ assert_equal([[:req]], proc { it }.parameters(lambda: true))
+ assert_equal([[:opt]], lambda { it }.parameters(lambda: false))
end
def pm0() end
@@ -1157,7 +1478,7 @@ class TestProc < Test::Unit::TestCase
assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:pmo6).to_proc.parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:pmo7).to_proc.parameters)
assert_equal([[:req], [:block, :b]], method(:pma1).to_proc.parameters)
- assert_equal([[:keyrest]], method(:pmk1).to_proc.parameters)
+ assert_equal([[:keyrest, :**]], method(:pmk1).to_proc.parameters)
assert_equal([[:keyrest, :o]], method(:pmk2).to_proc.parameters)
assert_equal([[:req, :a], [:keyrest, :o]], method(:pmk3).to_proc.parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], method(:pmk4).to_proc.parameters)
@@ -1171,25 +1492,26 @@ class TestProc < Test::Unit::TestCase
end
def test_to_s
- assert_match(/^#<Proc:0x\h+@#{ Regexp.quote(__FILE__) }:\d+>$/, proc {}.to_s)
- assert_match(/^#<Proc:0x\h+@#{ Regexp.quote(__FILE__) }:\d+ \(lambda\)>$/, lambda {}.to_s)
+ assert_match(/^#<Proc:0x\h+ #{ Regexp.quote(__FILE__) }:\d+>$/, proc {}.to_s)
+ assert_match(/^#<Proc:0x\h+ #{ Regexp.quote(__FILE__) }:\d+ \(lambda\)>$/, lambda {}.to_s)
assert_match(/^#<Proc:0x\h+ \(lambda\)>$/, method(:p).to_proc.to_s)
- x = proc {}
- x.taint
- assert_predicate(x.to_s, :tainted?)
name = "Proc\u{1f37b}"
assert_include(EnvUtil.labeled_class(name, Proc).new {}.to_s, name)
end
- @@line_of_source_location_test = __LINE__ + 1
+ @@line_of_source_location_test = [__LINE__ + 1, 2, __LINE__ + 3, 5]
def source_location_test a=1,
b=2
end
def test_source_location
- file, lineno = method(:source_location_test).source_location
+ file, *loc = method(:source_location_test).source_location
+ assert_match(/^#{ Regexp.quote(__FILE__) }$/, file)
+ assert_equal(@@line_of_source_location_test, loc, 'Bug #2427')
+
+ file, *loc = self.class.instance_method(:source_location_test).source_location
assert_match(/^#{ Regexp.quote(__FILE__) }$/, file)
- assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427')
+ assert_equal(@@line_of_source_location_test, loc, 'Bug #2427')
end
@@line_of_attr_reader_source_location_test = __LINE__ + 3
@@ -1222,13 +1544,13 @@ class TestProc < Test::Unit::TestCase
end
def test_block_source_location
- exp_lineno = __LINE__ + 3
- file, lineno = block_source_location_test(1,
+ exp_loc = [__LINE__ + 3, 49, __LINE__ + 4, 49]
+ file, *loc = block_source_location_test(1,
2,
3) do
end
assert_match(/^#{ Regexp.quote(__FILE__) }$/, file)
- assert_equal(exp_lineno, lineno)
+ assert_equal(exp_loc, loc)
end
def test_splat_without_respond_to
@@ -1285,7 +1607,7 @@ class TestProc < Test::Unit::TestCase
def test_local_variables
b = get_binding
assert_equal(%i'if case when begin end a', b.local_variables)
- a = tap {|;x, y| x = y; break binding.local_variables}
+ a = tap {|;x, y| x = y = x; break binding.local_variables}
assert_equal(%i[a b x y], a.sort)
end
@@ -1315,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
@@ -1327,8 +1653,276 @@ class TestProc < Test::Unit::TestCase
assert_equal(20, b.eval("b"))
end
+ def test_numparam_is_not_local_variables
+ "foo".tap do
+ _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 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
+
+ def test_it_is_not_local_variable
+ "foo".tap do
+ 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
- assert_ruby_status([], <<-'end;', '[Bug #13605]', timeout: 15)
+ assert_ruby_status([], <<-'end;', '[Bug #13605]', timeout: 30)
b = binding
n = 20_000
@@ -1376,4 +1970,250 @@ class TestProc < Test::Unit::TestCase
e.each {}
EOS
end
+
+ def test_prepended_call
+ assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", ["call"])
+ begin;
+ Proc.prepend Module.new {def call() puts "call"; super; end}
+ def m(&blk) blk.call; end
+ m {}
+ end;
+ end
+
+ def test_refined_call
+ assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", ["call"])
+ begin;
+ using Module.new {refine(Proc) {def call() puts "call"; super; end}}
+ def m(&blk) blk.call; end
+ m {}
+ end;
+ end
+
+ def test_compose
+ f = proc {|x| x * 2}
+ g = proc {|x| x + 1}
+
+ assert_equal(6, (f << g).call(2))
+ assert_equal(6, (g >> f).call(2))
+ end
+
+ def test_compose_with_multiple_args
+ f = proc {|x| x * 2}
+ g = proc {|x, y| x + y}
+
+ assert_equal(6, (f << g).call(1, 2))
+ assert_equal(6, (g >> f).call(1, 2))
+ end
+
+ def test_compose_with_block
+ f = proc {|x| x * 2}
+ g = proc {|&blk| blk.call(1) }
+
+ assert_equal(8, (f << g).call { |x| x + 3 })
+ assert_equal(8, (g >> f).call { |x| x + 3 })
+ end
+
+ def test_compose_with_lambda
+ f = lambda {|x| x * 2}
+ g = lambda {|x| x}
+ not_lambda = proc {|x| x}
+
+ assert_predicate((f << g), :lambda?)
+ assert_predicate((g >> f), :lambda?)
+ assert_predicate((not_lambda << f), :lambda?)
+ assert_not_predicate((f << not_lambda), :lambda?)
+ assert_not_predicate((not_lambda >> f), :lambda?)
+ end
+
+ def test_compose_with_method
+ f = proc {|x| x * 2}
+ c = Class.new {
+ def g(x) x + 1 end
+ }
+ g = c.new.method(:g)
+
+ assert_equal(6, (f << g).call(2))
+ assert_equal(5, (f >> g).call(2))
+ assert_predicate((f << g), :lambda?)
+ end
+
+ def test_compose_with_callable
+ f = proc {|x| x * 2}
+ c = Class.new {
+ def call(x) x + 1 end
+ }
+ g = c.new
+
+ assert_equal(6, (f << g).call(2))
+ assert_equal(5, (f >> g).call(2))
+ assert_predicate((f << g), :lambda?)
+ end
+
+ def test_compose_with_noncallable
+ f = proc {|x| x * 2}
+
+ assert_raise(TypeError) {
+ (f << 5).call(2)
+ }
+ assert_raise(TypeError) {
+ (f >> 5).call(2)
+ }
+ end
+
+ def test_orphan_return
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b.call end; def m2(); m1 { return 42 } end }.m2)
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b end; def m2(); m1 { return 42 }.call end }.m2)
+ assert_raise(LocalJumpError) { Module.new { extend self
+ def m1(&b) b end; def m2(); m1 { return 42 } end }.m2.call }
+ end
+
+ def test_orphan_break
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b.call end; def m2(); m1 { break 42 } end }.m2 )
+ assert_raise(LocalJumpError) { Module.new { extend self
+ def m1(&b) b end; def m2(); m1 { break 42 }.call end }.m2 }
+ assert_raise(LocalJumpError) { Module.new { extend self
+ def m1(&b) b end; def m2(); m1 { break 42 } end }.m2.call }
+ end
+
+ def test_not_orphan_next
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b.call end; def m2(); m1 { next 42 } end }.m2)
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b end; def m2(); m1 { next 42 }.call end }.m2)
+ assert_equal(42, Module.new { extend self
+ def m1(&b) b end; def m2(); m1 { next 42 } end }.m2.call)
+ end
+
+ def test_isolate
+ assert_raise_with_message ArgumentError, /\(a\)/ do
+ a = :a
+ Proc.new{p a}.isolate
+ end
+
+ assert_raise_with_message ArgumentError, /\(a\)/ do
+ a = :a
+ 1.times{
+ Proc.new{p a}.isolate
+ }
+ end
+
+ assert_raise_with_message ArgumentError, /yield/ do
+ Proc.new{yield}.isolate
+ end
+
+
+ name = "\u{2603 26a1}"
+ assert_raise_with_message(ArgumentError, /\(#{name}\)/) do
+ eval("#{name} = :#{name}; Proc.new {p #{name}}").isolate
+ end
+
+ # binding
+
+ :a.tap{|a|
+ :b.tap{|b|
+ Proc.new{
+ :c.tap{|c|
+ assert_equal :c, eval('c')
+
+ assert_raise_with_message SyntaxError, /\`a\'/ do
+ eval('p a')
+ end
+
+ assert_raise_with_message SyntaxError, /\`b\'/ do
+ eval('p b')
+ end
+
+ assert_raise_with_message SyntaxError, /can not yield from isolated Proc/ do
+ eval('p yield')
+ end
+
+ assert_equal :c, binding.local_variable_get(:c)
+
+ assert_raise_with_message NameError, /local variable \`a\' is not defined/ do
+ binding.local_variable_get(:a)
+ end
+
+ assert_equal [:c], local_variables
+ assert_equal [:c], binding.local_variables
+ }
+ }.isolate.call
+ }
+ }
+ end if proc{}.respond_to? :isolate
+end
+
+class TestProcKeywords < Test::Unit::TestCase
+ def test_compose_keywords
+ f = ->(**kw) { kw.merge(:a=>1) }
+ g = ->(kw) { kw.merge(:a=>2) }
+
+ assert_equal(2, (f >> g).call(a: 3)[:a])
+ assert_raise(ArgumentError) { (f << g).call(a: 3)[:a] }
+ assert_equal(2, (f >> g).call(a: 3)[:a])
+ assert_raise(ArgumentError) { (f << g).call({a: 3})[:a] }
+ assert_raise(ArgumentError) { (f >> g).call({a: 3})[:a] }
+ assert_equal(2, (g << f).call(a: 3)[:a])
+ assert_raise(ArgumentError) { (g >> f).call(a: 3)[:a] }
+ assert_raise(ArgumentError) { (g << f).call({a: 3})[:a] }
+ assert_raise(ArgumentError) { (g >> f).call({a: 3})[:a] }
+ assert_raise(ArgumentError) { (f << g).call(**{})[:a] }
+ assert_equal(2, (f >> g).call(**{})[:a])
+ end
+
+ def test_compose_keywords_method
+ f = ->(**kw) { kw.merge(:a=>1) }.method(:call)
+ g = ->(kw) { kw.merge(:a=>2) }.method(:call)
+
+ assert_raise(ArgumentError) { (f << g).call(a: 3)[:a] }
+ assert_equal(2, (f >> g).call(a: 3)[:a])
+ assert_raise(ArgumentError) { (f << g).call({a: 3})[:a] }
+ assert_raise(ArgumentError) { (f >> g).call({a: 3})[:a] }
+ assert_equal(2, (g << f).call(a: 3)[:a])
+ assert_raise(ArgumentError) { (g >> f).call(a: 3)[:a] }
+ assert_raise(ArgumentError) { (g << f).call({a: 3})[:a] }
+ assert_raise(ArgumentError) { (g >> f).call({a: 3})[:a] }
+ assert_raise(ArgumentError) { (f << g).call(**{})[:a] }
+ assert_equal(2, (f >> g).call(**{})[:a])
+ end
+
+ def test_compose_keywords_non_proc
+ f = ->(**kw) { kw.merge(:a=>1) }
+ g = Object.new
+ def g.call(kw) kw.merge(:a=>2) end
+ def g.to_proc; method(:call).to_proc; end
+ def g.<<(f) to_proc << f end
+ def g.>>(f) to_proc >> f end
+
+ assert_raise(ArgumentError) { (f << g).call(a: 3)[:a] }
+ assert_equal(2, (f >> g).call(a: 3)[:a])
+ assert_raise(ArgumentError) { (f << g).call({a: 3})[:a] }
+ assert_raise(ArgumentError) { (f >> g).call({a: 3})[:a] }
+ assert_equal(2, (g << f).call(a: 3)[:a])
+ assert_raise(ArgumentError) { (g >> f).call(a: 3)[:a] }
+ assert_raise(ArgumentError) { (g << f).call({a: 3})[:a] }
+ assert_raise(ArgumentError) { (g >> f).call({a: 3})[:a] }
+ assert_raise(ArgumentError) { (f << g).call(**{})[:a] }
+ assert_equal(2, (f >> g).call(**{})[:a])
+
+ f = ->(kw) { kw.merge(:a=>1) }
+ g = Object.new
+ def g.call(**kw) kw.merge(:a=>2) end
+ def g.to_proc; method(:call).to_proc; end
+ def g.<<(f) to_proc << f end
+ def g.>>(f) to_proc >> f end
+
+ assert_equal(1, (f << g).call(a: 3)[:a])
+ assert_raise(ArgumentError) { (f >> g).call(a: 3)[:a] }
+ assert_raise(ArgumentError) { (f << g).call({a: 3})[:a] }
+ assert_raise(ArgumentError) { (f >> g).call({a: 3})[:a] }
+ assert_raise(ArgumentError) { (g << f).call(a: 3)[:a] }
+ assert_equal(1, (g >> f).call(a: 3)[:a])
+ assert_raise(ArgumentError) { (g << f).call({a: 3})[:a] }
+ assert_raise(ArgumentError) { (g >> f).call({a: 3})[:a] }
+ assert_equal(1, (f << g).call(**{})[:a])
+ assert_raise(ArgumentError) { (f >> g).call(**{})[:a] }
+ end
end
diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb
index ba7b0f1177..b3a88b664c 100644
--- a/test/ruby/test_process.rb
+++ b/test/ruby/test_process.rb
@@ -3,7 +3,6 @@
require 'test/unit'
require 'tempfile'
require 'timeout'
-require 'io/wait'
require 'rbconfig'
class TestProcess < Test::Unit::TestCase
@@ -24,12 +23,6 @@ class TestProcess < Test::Unit::TestCase
return /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
end
- def write_file(filename, content)
- File.open(filename, "w") {|f|
- f << content
- }
- end
-
def with_tmpchdir
Dir.mktmpdir {|d|
d = File.realpath(d)
@@ -40,7 +33,7 @@ class TestProcess < Test::Unit::TestCase
end
def run_in_child(str) # should be called in a temporary directory
- write_file("test-script", str)
+ File.write("test-script", str)
Process.wait spawn(RUBY, "test-script")
$?
end
@@ -65,8 +58,10 @@ 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 {
- write_file 's', <<-"End"
+ File.write 's', <<-"End"
# Too small RLIMIT_NOFILE, such as zero, causes problems.
# [OpenBSD] Setting to zero freezes this test.
# [GNU/Linux] EINVAL on poll(). EINVAL on ruby's internal poll() ruby with "[ASYNC BUG] thread_timer: select".
@@ -121,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')
@@ -169,7 +169,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_pgroup
- skip "system(:pgroup) is not supported" if windows?
+ omit "system(:pgroup) is not supported" if windows?
assert_nothing_raised { system(*TRUECOMMAND, :pgroup=>false) }
io = IO.popen([RUBY, "-e", "print Process.getpgrp"])
@@ -206,72 +206,98 @@ class TestProcess < Test::Unit::TestCase
max = Process.getrlimit(:CORE).last
+ # When running under ASAN, we need to set disable_coredump=0 for this test; by default
+ # the ASAN runtime library sets RLIMIT_CORE to 0, "to avoid dumping a 16T+ core file", and
+ # that inteferes with this test.
+ asan_options = ENV['ASAN_OPTIONS'] || ''
+ asan_options << ':' unless asan_options.empty?
+ env = {
+ 'ASAN_OPTIONS' => "#{asan_options}disable_coredump=0"
+ }
+
n = max
- IO.popen([RUBY, "-e",
- "p Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
- assert_equal("[#{n}, #{n}]\n", io.read)
+ IO.popen([env, RUBY, "-e",
+ "puts Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
+ assert_equal("#{n}\n#{n}\n", io.read)
}
n = 0
- IO.popen([RUBY, "-e",
- "p Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
- assert_equal("[#{n}, #{n}]\n", io.read)
+ IO.popen([env, RUBY, "-e",
+ "puts Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
+ assert_equal("#{n}\n#{n}\n", io.read)
}
n = max
- IO.popen([RUBY, "-e",
- "p Process.getrlimit(:CORE)", :rlimit_core=>[n]]) {|io|
- assert_equal("[#{n}, #{n}]", io.read.chomp)
+ IO.popen([env, RUBY, "-e",
+ "puts Process.getrlimit(:CORE)", :rlimit_core=>[n]]) {|io|
+ assert_equal("#{n}\n#{n}\n", io.read)
}
m, n = 0, max
- IO.popen([RUBY, "-e",
- "p Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
- assert_equal("[#{m}, #{n}]", io.read.chomp)
+ IO.popen([env, RUBY, "-e",
+ "puts Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
+ assert_equal("#{m}\n#{n}\n", io.read)
}
m, n = 0, 0
- IO.popen([RUBY, "-e",
- "p Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
- assert_equal("[#{m}, #{n}]", io.read.chomp)
+ IO.popen([env, RUBY, "-e",
+ "puts Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
+ assert_equal("#{m}\n#{n}\n", io.read)
}
n = max
- IO.popen([RUBY, "-e",
- "p Process.getrlimit(:CORE), Process.getrlimit(:CPU)",
+ IO.popen([env, RUBY, "-e",
+ "puts Process.getrlimit(:CORE), Process.getrlimit(:CPU)",
:rlimit_core=>n, :rlimit_cpu=>3600]) {|io|
- assert_equal("[#{n}, #{n}]\n[3600, 3600]", io.read.chomp)
+ assert_equal("#{n}\n#{n}\n""3600\n3600\n", io.read)
}
assert_raise(ArgumentError) do
- system(RUBY, '-e', 'exit', 'rlimit_bogus'.to_sym => 123)
+ system(env, RUBY, '-e', 'exit', 'rlimit_bogus'.to_sym => 123)
end
- assert_separately([],<<-"end;") # [ruby-core:82033] [Bug #13744]
- assert(system("#{RUBY}", "-e",
- "exit([3600,3600] == Process.getrlimit(:CPU))",
- 'rlimit_cpu'.to_sym => 3600))
- assert_raise(ArgumentError) do
- system("#{RUBY}", '-e', 'exit', :rlimit_bogus => 123)
- end
+ assert_separately([env],"#{<<~"begin;"}\n#{<<~'end;'}", 'rlimit_cpu'.to_sym => 3600)
+ BUG = "[ruby-core:82033] [Bug #13744]"
+ begin;
+ assert_equal([3600,3600], Process.getrlimit(:CPU), BUG)
end;
- assert_raise(ArgumentError, /rlimit_cpu/) {
- system(RUBY, '-e', 'exit', "rlimit_cpu\0".to_sym => 3600)
+ assert_raise_with_message(ArgumentError, /bogus/) do
+ system(env, RUBY, '-e', 'exit', :rlimit_bogus => 123)
+ end
+
+ assert_raise_with_message(ArgumentError, /rlimit_cpu/) {
+ system(env, RUBY, '-e', 'exit', "rlimit_cpu\0".to_sym => 3600)
}
end
- MANDATORY_ENVS = %w[RUBYLIB]
- case RbConfig::CONFIG['target_os']
- when /linux/
- MANDATORY_ENVS << 'LD_PRELOAD'
- when /mswin|mingw/
- MANDATORY_ENVS.concat(%w[HOME USER TMPDIR])
- when /darwin/
- MANDATORY_ENVS.concat(ENV.keys.grep(/\A__CF_/))
+ def test_overwrite_ENV
+ assert_separately([],"#{<<~"begin;"}\n#{<<~"end;"}")
+ BUG = "[ruby-core:105223] [Bug #18164]"
+ begin;
+ $VERBOSE = nil
+ ENV = {}
+ pid = spawn({}, *#{TRUECOMMAND.inspect})
+ ENV.replace({})
+ assert_kind_of(Integer, pid, BUG)
+ end;
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)
@@ -334,12 +360,19 @@ class TestProcess < Test::Unit::TestCase
ensure
ENV["hmm"] = old
end
+
+ assert_raise_with_message(ArgumentError, /fo=fo/) {
+ system({"fo=fo"=>"ha"}, *ENVCOMMAND)
+ }
+ assert_raise_with_message(ArgumentError, /\u{30c0}=\u{30e1}/) {
+ system({"\u{30c0}=\u{30e1}"=>"ha"}, *ENVCOMMAND)
+ }
end
def test_execopt_env_path
bug8004 = '[ruby-core:53103] [Bug #8004]'
Dir.mktmpdir do |d|
- open("#{d}/tmp_script.cmd", "w") {|f| f.puts ": ;"; f.chmod(0755)}
+ File.write("#{d}/tmp_script.cmd", ": ;\n", perm: 0o755)
assert_not_nil(pid = Process.spawn({"PATH" => d}, "tmp_script.cmd"), bug8004)
wpid, st = Process.waitpid2(pid)
assert_equal([pid, true], [wpid, st.success?], bug8004)
@@ -377,7 +410,7 @@ class TestProcess < Test::Unit::TestCase
def test_execopts_env_popen_string
with_tmpchdir do |d|
- open('test-script', 'w') do |f|
+ File.open('test-script', 'w') do |f|
ENVCOMMAND.each_with_index do |cmd, i|
next if i.zero? or cmd == "-e"
f.puts cmd
@@ -389,16 +422,14 @@ class TestProcess < Test::Unit::TestCase
def test_execopts_preserve_env_on_exec_failure
with_tmpchdir {|d|
- write_file 's', <<-"End"
+ File.write 's', <<-"End"
ENV["mgg"] = nil
prog = "./nonexistent"
begin
Process.exec({"mgg" => "mggoo"}, [prog, prog])
rescue Errno::ENOENT
end
- open('out', 'w') {|f|
- f.print ENV["mgg"].inspect
- }
+ File.write('out', ENV["mgg"].inspect)
End
system(RUBY, 's')
assert_equal(nil.inspect, File.read('out'),
@@ -408,9 +439,7 @@ class TestProcess < Test::Unit::TestCase
def test_execopts_env_single_word
with_tmpchdir {|d|
- open("test_execopts_env_single_word.rb", "w") {|f|
- f.puts "print ENV['hgga']"
- }
+ File.write("test_execopts_env_single_word.rb", "print ENV['hgga']\n")
system({"hgga"=>"ugu"}, RUBY,
:in => 'test_execopts_env_single_word.rb',
:out => 'test_execopts_env_single_word.out')
@@ -488,7 +517,7 @@ class TestProcess < Test::Unit::TestCase
UMASK = [RUBY, '-e', 'printf "%04o\n", File.umask']
def test_execopts_umask
- skip "umask is not supported" if windows?
+ omit "umask is not supported" if windows?
IO.popen([*UMASK, :umask => 0]) {|io|
assert_equal("0000", io.read.chomp)
}
@@ -532,7 +561,7 @@ class TestProcess < Test::Unit::TestCase
assert_equal("a", File.read("out").chomp)
if windows?
# currently telling to child the file modes is not supported.
- open("out", "a") {|f| f.write "0\n"}
+ File.write("out", "0\n", mode: "a")
else
Process.wait Process.spawn(*ECHO["0"], STDOUT=>["out", File::WRONLY|File::CREAT|File::APPEND, 0644])
assert_equal("a\n0\n", File.read("out"))
@@ -630,7 +659,7 @@ class TestProcess < Test::Unit::TestCase
rescue NotImplementedError
return
end
- assert(FileTest.pipe?("fifo"), "should be pipe")
+ assert_file.pipe?("fifo")
t1 = Thread.new {
system(*ECHO["output to fifo"], :out=>"fifo")
}
@@ -643,6 +672,7 @@ class TestProcess < Test::Unit::TestCase
end unless windows? # does not support fifo
def test_execopts_redirect_open_fifo_interrupt_raise
+ pid = nil
with_tmpchdir {|d|
begin
File.mkfifo("fifo")
@@ -660,15 +690,21 @@ class TestProcess < Test::Unit::TestCase
puts "ok"
end
EOS
+ pid = io.pid
assert_equal("start\n", io.gets)
sleep 0.5
Process.kill(:USR1, io.pid)
assert_equal("ok\n", io.read)
}
+ assert_equal(pid, $?.pid)
+ assert_predicate($?, :success?)
}
+ ensure
+ assert_raise(Errno::ESRCH) {Process.kill(:KILL, pid)} if pid
end unless windows? # does not support fifo
def test_execopts_redirect_open_fifo_interrupt_print
+ pid = nil
with_tmpchdir {|d|
begin
File.mkfifo("fifo")
@@ -676,16 +712,30 @@ class TestProcess < Test::Unit::TestCase
return
end
IO.popen([RUBY, '-e', <<-'EOS']) {|io|
+ STDOUT.sync = true
trap(:USR1) { print "trap\n" }
+ puts "start"
system("cat", :in => "fifo")
EOS
- sleep 1
+ pid = io.pid
+ assert_equal("start\n", io.gets)
+ sleep 0.2 # wait for the child to stop at opening "fifo"
Process.kill(:USR1, io.pid)
- sleep 1
+ assert_equal("trap\n", io.readpartial(8))
+ sleep 0.2 # wait for the child to return to opening "fifo".
+ # On arm64-darwin22, often deadlocks while the child is
+ # opening "fifo". Not sure to where "ok" line being written
+ # at the next has gone.
File.write("fifo", "ok\n")
- assert_equal("trap\nok\n", io.read)
+ assert_equal("ok\n", io.read)
}
+ assert_equal(pid, $?.pid)
+ assert_predicate($?, :success?)
}
+ ensure
+ if pid
+ assert_raise(Errno::ESRCH) {Process.kill(:KILL, pid)}
+ end
end unless windows? # does not support fifo
def test_execopts_redirect_pipe
@@ -756,6 +806,15 @@ class TestProcess < Test::Unit::TestCase
Process.wait pid
end
}
+
+ # ensure standard FDs we redirect to are blocking for compatibility
+ with_pipes(3) do |pipes|
+ src = 'p [STDIN,STDOUT,STDERR].map(&:nonblock?)'
+ rdr = { 0 => pipes[0][0], 1 => pipes[1][1], 2 => pipes[2][1] }
+ pid = spawn(RUBY, '-rio/nonblock', '-e', src, rdr)
+ assert_equal("[false, false, false]\n", pipes[1][0].gets)
+ Process.wait pid
+ end
end
end
@@ -802,7 +861,7 @@ class TestProcess < Test::Unit::TestCase
STDERR=>"out", STDOUT=>[:child, STDERR])
assert_equal("errout", File.read("out"))
- skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
+ omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'",
STDOUT=>"out",
STDERR=>[:child, 3],
@@ -824,7 +883,7 @@ class TestProcess < Test::Unit::TestCase
def test_execopts_exec
with_tmpchdir {|d|
- write_file("s", 'exec "echo aaa", STDOUT=>"foo"')
+ File.write("s", 'exec "echo aaa", STDOUT=>"foo"')
pid = spawn RUBY, 's'
Process.wait pid
assert_equal("aaa\n", File.read("foo"))
@@ -854,7 +913,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_popen_extra_fd
- skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
+ omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
with_tmpchdir {|d|
with_pipe {|r, w|
IO.popen([RUBY, '-e', 'IO.new(3, "w").puts("a"); puts "b"', 3=>w]) {|io|
@@ -871,19 +930,33 @@ class TestProcess < Test::Unit::TestCase
}
end
- def test_popen_fork
- IO.popen("-") {|io|
- if !io
- puts "fooo"
- else
- assert_equal("fooo\n", io.read)
+ if Process.respond_to?(:fork)
+ def test_popen_fork
+ IO.popen("-") do |io|
+ if !io
+ puts "fooo"
+ else
+ assert_equal("fooo\n", io.read)
+ end
end
- }
- rescue NotImplementedError
+ end
+
+ def test_popen_fork_ensure
+ IO.popen("-") do |io|
+ if !io
+ STDERR.reopen(STDOUT)
+ raise "fooo"
+ else
+ assert_empty io.read
+ end
+ end
+ rescue RuntimeError
+ abort "[Bug #20995] should not reach here"
+ end
end
def test_fd_inheritance
- skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
+ omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
with_pipe {|r, w|
system(RUBY, '-e', 'IO.new(ARGV[0].to_i, "w").puts(:ba)', w.fileno.to_s, w=>w)
w.close
@@ -898,7 +971,7 @@ class TestProcess < Test::Unit::TestCase
}
with_pipe {|r, w|
with_tmpchdir {|d|
- write_file("s", <<-"End")
+ File.write("s", <<-"End")
exec(#{RUBY.dump}, '-e',
'IO.new(ARGV[0].to_i, "w").puts("bu") rescue nil',
#{w.fileno.to_s.dump}, :close_others=>false)
@@ -929,7 +1002,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_close_others
- skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
+ omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
with_tmpchdir {|d|
with_pipe {|r, w|
system(RUBY, '-e', 'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("ma")', w.fileno.to_s, :close_others=>true)
@@ -952,7 +1025,7 @@ class TestProcess < Test::Unit::TestCase
assert_equal("bi\n", r.read)
}
with_pipe {|r, w|
- write_file("s", <<-"End")
+ File.write("s", <<-"End")
exec(#{RUBY.dump}, '-e',
'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("mu")',
#{w.fileno.to_s.dump},
@@ -1003,6 +1076,15 @@ class TestProcess < Test::Unit::TestCase
}
end
+ def test_close_others_default_false
+ IO.pipe do |r,w|
+ w.close_on_exec = false
+ src = "IO.new(#{w.fileno}).puts(:hi)"
+ assert_equal true, system(*%W(#{RUBY} --disable=gems -e #{src}))
+ assert_equal "hi\n", r.gets
+ end
+ end unless windows? # passing non-stdio fds is not supported on Windows
+
def test_execopts_redirect_self
begin
with_pipe {|r, w|
@@ -1014,7 +1096,7 @@ class TestProcess < Test::Unit::TestCase
}
}
rescue NotImplementedError
- skip "IO#close_on_exec= is not supported"
+ omit "IO#close_on_exec= is not supported"
end
end unless windows? # passing non-stdio fds is not supported on Windows
@@ -1069,7 +1151,7 @@ class TestProcess < Test::Unit::TestCase
def test_exec_noshell
with_tmpchdir {|d|
- write_file("s", <<-"End")
+ File.write("s", <<-"End")
str = "echo non existing command name which contains spaces"
STDERR.reopen(STDOUT)
begin
@@ -1085,7 +1167,7 @@ class TestProcess < Test::Unit::TestCase
def test_system_wordsplit
with_tmpchdir {|d|
- write_file("script", <<-'End')
+ File.write("script", <<-'End')
File.open("result", "w") {|t| t << "haha pid=#{$$} ppid=#{Process.ppid}" }
exit 5
End
@@ -1101,7 +1183,7 @@ class TestProcess < Test::Unit::TestCase
def test_spawn_wordsplit
with_tmpchdir {|d|
- write_file("script", <<-'End')
+ File.write("script", <<-'End')
File.open("result", "w") {|t| t << "hihi pid=#{$$} ppid=#{Process.ppid}" }
exit 6
End
@@ -1118,7 +1200,7 @@ class TestProcess < Test::Unit::TestCase
def test_popen_wordsplit
with_tmpchdir {|d|
- write_file("script", <<-'End')
+ File.write("script", <<-'End')
print "fufu pid=#{$$} ppid=#{Process.ppid}"
exit 7
End
@@ -1137,7 +1219,7 @@ class TestProcess < Test::Unit::TestCase
def test_popen_wordsplit_beginning_and_trailing_spaces
with_tmpchdir {|d|
- write_file("script", <<-'End')
+ File.write("script", <<-'End')
print "fufumm pid=#{$$} ppid=#{Process.ppid}"
exit 7
End
@@ -1156,7 +1238,7 @@ class TestProcess < Test::Unit::TestCase
def test_exec_wordsplit
with_tmpchdir {|d|
- write_file("script", <<-'End')
+ File.write("script", <<-'End')
File.open("result", "w") {|t|
if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
t << "hehe ppid=#{Process.ppid}"
@@ -1166,7 +1248,7 @@ class TestProcess < Test::Unit::TestCase
}
exit 6
End
- write_file("s", <<-"End")
+ File.write("s", <<-"End")
ruby = #{RUBY.dump}
exec "\#{ruby} script"
End
@@ -1187,11 +1269,11 @@ class TestProcess < Test::Unit::TestCase
def test_system_shell
with_tmpchdir {|d|
- write_file("script1", <<-'End')
+ File.write("script1", <<-'End')
File.open("result1", "w") {|t| t << "taka pid=#{$$} ppid=#{Process.ppid}" }
exit 7
End
- write_file("script2", <<-'End')
+ File.write("script2", <<-'End')
File.open("result2", "w") {|t| t << "taki pid=#{$$} ppid=#{Process.ppid}" }
exit 8
End
@@ -1207,7 +1289,7 @@ class TestProcess < Test::Unit::TestCase
if windows?
Dir.mkdir(path = "path with space")
- write_file(bat = path + "/bat test.bat", "@echo %1>out")
+ File.write(bat = path + "/bat test.bat", "@echo %1>out")
system(bat, "foo 'bar'")
assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]')
system(%[#{bat.dump} "foo 'bar'"])
@@ -1218,11 +1300,11 @@ class TestProcess < Test::Unit::TestCase
def test_spawn_shell
with_tmpchdir {|d|
- write_file("script1", <<-'End')
+ File.write("script1", <<-'End')
File.open("result1", "w") {|t| t << "taku pid=#{$$} ppid=#{Process.ppid}" }
exit 7
End
- write_file("script2", <<-'End')
+ File.write("script2", <<-'End')
File.open("result2", "w") {|t| t << "take pid=#{$$} ppid=#{Process.ppid}" }
exit 8
End
@@ -1239,7 +1321,7 @@ class TestProcess < Test::Unit::TestCase
if windows?
Dir.mkdir(path = "path with space")
- write_file(bat = path + "/bat test.bat", "@echo %1>out")
+ File.write(bat = path + "/bat test.bat", "@echo %1>out")
pid = spawn(bat, "foo 'bar'")
Process.wait pid
status = $?
@@ -1258,11 +1340,11 @@ class TestProcess < Test::Unit::TestCase
def test_popen_shell
with_tmpchdir {|d|
- write_file("script1", <<-'End')
+ File.write("script1", <<-'End')
puts "tako pid=#{$$} ppid=#{Process.ppid}"
exit 7
End
- write_file("script2", <<-'End')
+ File.write("script2", <<-'End')
puts "tika pid=#{$$} ppid=#{Process.ppid}"
exit 8
End
@@ -1277,7 +1359,7 @@ class TestProcess < Test::Unit::TestCase
if windows?
Dir.mkdir(path = "path with space")
- write_file(bat = path + "/bat test.bat", "@echo %1")
+ File.write(bat = path + "/bat test.bat", "@echo %1")
r = IO.popen([bat, "foo 'bar'"]) {|f| f.read}
assert_equal(%["foo 'bar'"\n], r, '[ruby-core:22960]')
r = IO.popen(%[#{bat.dump} "foo 'bar'"]) {|f| f.read}
@@ -1288,15 +1370,15 @@ class TestProcess < Test::Unit::TestCase
def test_exec_shell
with_tmpchdir {|d|
- write_file("script1", <<-'End')
+ File.write("script1", <<-'End')
File.open("result1", "w") {|t| t << "tiki pid=#{$$} ppid=#{Process.ppid}" }
exit 7
End
- write_file("script2", <<-'End')
+ File.write("script2", <<-'End')
File.open("result2", "w") {|t| t << "tiku pid=#{$$} ppid=#{Process.ppid}" }
exit 8
End
- write_file("s", <<-"End")
+ File.write("s", <<-"End")
ruby = #{RUBY.dump}
exec("\#{ruby} script1 || \#{ruby} script2")
End
@@ -1323,7 +1405,7 @@ class TestProcess < Test::Unit::TestCase
assert_equal("1", IO.popen([[RUBY, "qwerty"], "-e", "print 1"]) {|f| f.read })
- write_file("s", <<-"End")
+ File.write("s", <<-"End")
exec([#{RUBY.dump}, "lkjh"], "-e", "exit 5")
End
pid = spawn RUBY, "s"
@@ -1333,7 +1415,7 @@ class TestProcess < Test::Unit::TestCase
end
def with_stdin(filename)
- open(filename) {|f|
+ File.open(filename) {|f|
begin
old = STDIN.dup
begin
@@ -1350,8 +1432,8 @@ class TestProcess < Test::Unit::TestCase
def test_argv0_noarg
with_tmpchdir {|d|
- open("t", "w") {|f| f.print "exit true" }
- open("f", "w") {|f| f.print "exit false" }
+ File.write("t", "exit true")
+ File.write("f", "exit false")
with_stdin("t") { assert_equal(true, system([RUBY, "qaz"])) }
with_stdin("f") { assert_equal(false, system([RUBY, "wsx"])) }
@@ -1381,6 +1463,11 @@ class TestProcess < Test::Unit::TestCase
REPRO
end
+ def test_argv0_frozen
+ assert_predicate Process.argv0, :frozen?
+ assert_predicate $0, :frozen?
+ end
+
def test_status
with_tmpchdir do
s = run_in_child("exit 1")
@@ -1389,10 +1476,10 @@ class TestProcess < Test::Unit::TestCase
assert_equal(s, s)
assert_equal(s, s.to_i)
- assert_equal(s.to_i & 0x55555555, s & 0x55555555)
- assert_equal(s.to_i >> 1, s >> 1)
assert_equal(false, s.stopped?)
assert_equal(nil, s.stopsig)
+
+ assert_equal(s, Marshal.load(Marshal.dump(s)))
end
end
@@ -1404,12 +1491,14 @@ class TestProcess < Test::Unit::TestCase
expected = Signal.list.include?("QUIT") ? [false, true, false, nil] : [true, false, false, true]
with_tmpchdir do
- write_file("foo", "Process.kill(:KILL, $$); exit(42)")
+ File.write("foo", "Process.kill(:KILL, $$); exit(42)")
system(RUBY, "foo")
s = $?
assert_equal(expected,
[s.exited?, s.signaled?, s.stopped?, s.success?],
"[s.exited?, s.signaled?, s.stopped?, s.success?]")
+
+ assert_equal(s, Marshal.load(Marshal.dump(s)))
end
end
@@ -1424,12 +1513,33 @@ class TestProcess < Test::Unit::TestCase
"[s.exited?, s.signaled?, s.stopped?, s.success?]")
assert_equal("#<Process::Status: pid #{ s.pid } SIGQUIT (signal #{ s.termsig })>",
s.inspect.sub(/ \(core dumped\)(?=>\z)/, ''))
+
+ assert_equal(s, Marshal.load(Marshal.dump(s)))
+ end
+ end
+
+ def test_status_fail
+ ret = Process::Status.wait($$)
+ assert_instance_of(Process::Status, ret)
+ assert_equal(-1, ret.pid)
+ end
+
+
+ def test_status_wait
+ IO.popen([RUBY, "-e", "gets"], "w") do |io|
+ pid = io.pid
+ assert_nil(Process::Status.wait(pid, Process::WNOHANG))
+ io.puts
+ ret = Process::Status.wait(pid)
+ assert_instance_of(Process::Status, ret)
+ assert_equal(pid, ret.pid)
+ assert_predicate(ret, :exited?)
end
end
def test_wait_without_arg
with_tmpchdir do
- write_file("foo", "sleep 0.1")
+ File.write("foo", "sleep 0.1")
pid = spawn(RUBY, "foo")
assert_equal(pid, Process.wait)
end
@@ -1437,7 +1547,7 @@ class TestProcess < Test::Unit::TestCase
def test_wait2
with_tmpchdir do
- write_file("foo", "sleep 0.1")
+ File.write("foo", "sleep 0.1")
pid = spawn(RUBY, "foo")
assert_equal([pid, 0], Process.wait2)
end
@@ -1445,7 +1555,7 @@ class TestProcess < Test::Unit::TestCase
def test_waitall
with_tmpchdir do
- write_file("foo", "sleep 0.1")
+ File.write("foo", "sleep 0.1")
ps = (0...3).map { spawn(RUBY, "foo") }.sort
ss = Process.waitall.sort
ps.zip(ss) do |p1, (p2, s)|
@@ -1458,7 +1568,9 @@ class TestProcess < Test::Unit::TestCase
def test_wait_exception
bug11340 = '[ruby-dev:49176] [Bug #11340]'
t0 = t1 = nil
- IO.popen([RUBY, '-e', 'puts;STDOUT.flush;Thread.start{gets;exit};sleep(3)'], 'r+') do |f|
+ sec = 3
+ code = "puts;STDOUT.flush;Thread.start{gets;exit};sleep(#{sec})"
+ IO.popen([RUBY, '-e', code], 'r+') do |f|
pid = f.pid
f.gets
t0 = Time.now
@@ -1472,21 +1584,37 @@ class TestProcess < Test::Unit::TestCase
th.kill.join
end
t1 = Time.now
+ diff = t1 - t0
+ assert_operator(diff, :<, sec,
+ ->{"#{bug11340}: #{diff} seconds to interrupt Process.wait"})
f.puts
+ rescue Errno::EPIPE
+ omit "child process exited already in #{diff} seconds"
end
- assert_operator(t1 - t0, :<, 3,
- ->{"#{bug11340}: #{t1-t0} seconds to interrupt Process.wait"})
end
def test_abort
with_tmpchdir do
s = run_in_child("abort")
- assert_not_equal(0, s.exitstatus)
+ assert_not_predicate(s, :success?)
+ File.write("test-script", "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ STDERR.reopen(STDOUT)
+ begin
+ raise "[Bug #16424]"
+ rescue
+ abort
+ end
+ end;
+ assert_include(IO.popen([RUBY, "test-script"], &:read), "[Bug #16424]")
end
end
def test_sleep
assert_raise(ArgumentError) { sleep(1, 1) }
+ [-1, -1.0, -1r].each do |sec|
+ assert_raise_with_message(ArgumentError, /not.*negative/) { sleep(sec) }
+ end
end
def test_getpgid
@@ -1524,6 +1652,8 @@ class TestProcess < Test::Unit::TestCase
rescue NotImplementedError
else
assert_kind_of(Integer, max)
+ assert_predicate(max, :positive?)
+ omit "not limited to NGROUPS_MAX" if /darwin/ =~ RUBY_PLATFORM
gs = Process.groups
assert_operator(gs.size, :<=, max)
gs[0] ||= 0
@@ -1540,7 +1670,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_seteuid_name
- user = ENV["USER"] or return
+ user = (Etc.getpwuid(Process.euid).name rescue ENV["USER"]) or return
assert_nothing_raised(TypeError) {Process.euid = user}
rescue NotImplementedError
end
@@ -1550,10 +1680,35 @@ class TestProcess < Test::Unit::TestCase
end
def test_setegid
+ omit "root can use Process.egid on Android platform" if RUBY_PLATFORM =~ /android/
assert_nothing_raised(TypeError) {Process.egid += 0}
rescue NotImplementedError
end
+ if Process::UID.respond_to?(:from_name)
+ def test_uid_from_name
+ if u = Etc.getpwuid(Process.uid)
+ assert_equal(Process.uid, Process::UID.from_name(u.name), u.name)
+ end
+ 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
+
+ if Process::GID.respond_to?(:from_name) && !RUBY_PLATFORM.include?("android")
+ def test_gid_from_name
+ if g = Etc.getgrgid(Process.gid)
+ assert_equal(Process.gid, Process::GID.from_name(g.name), g.name)
+ end
+ exc = assert_raise_kind_of(ArgumentError, SystemCallError) do
+ Process::GID.from_name("\u{4e0d 5b58 5728}") # fu son zai ("absent" in Kanji)
+ end
+ assert_match(/\u{4e0d 5b58 5728}/, exc.message) if exc.is_a?(ArgumentError)
+ end
+ end
+
def test_uid_re_exchangeable_p
r = Process::UID.re_exchangeable?
assert_include([true, false], r)
@@ -1579,28 +1734,30 @@ class TestProcess < Test::Unit::TestCase
end
def test_wait_and_sigchild
- if /freebsd|openbsd/ =~ RUBY_PLATFORM
- # this relates #4173
- # When ruby can use 2 cores, signal and wait4 may miss the signal.
- skip "this fails on FreeBSD and OpenBSD on multithreaded environment"
- end
signal_received = []
- Signal.trap(:CHLD) { signal_received << true }
- pid = nil
- IO.pipe do |r, w|
- pid = fork { r.read(1); exit }
- Thread.start {
- Thread.current.report_on_exception = false
- raise
- }
- w.puts
- end
- Process.wait pid
- 10.times do
- break unless signal_received.empty?
- sleep 0.01
+ IO.pipe do |sig_r, sig_w|
+ Signal.trap(:CHLD) do
+ signal_received << true
+ sig_w.write('?')
+ end
+ pid = nil
+ th = nil
+ IO.pipe do |r, w|
+ pid = fork { r.read(1); exit }
+ th = Thread.start {
+ Thread.current.report_on_exception = false
+ raise
+ }
+ w.puts
+ end
+ Process.wait pid
+ begin
+ th.join
+ rescue Exception
+ end
+ assert_send [sig_r, :wait_readable, 5], 'self-pipe not readable'
end
- assert_equal [true], signal_received, " [ruby-core:19744]"
+ assert_equal [true], signal_received, "[ruby-core:19744]"
rescue NotImplementedError, ArgumentError
ensure
begin
@@ -1615,7 +1772,7 @@ class TestProcess < Test::Unit::TestCase
status = nil
Dir.chdir("vd") {
dir = "#{d}/vd"
- # OpenSolaris cannot remove the current directory.
+ # Windows cannot remove the current directory with permission issues.
system(RUBY, "--disable-gems", "-e", "Dir.chdir '..'; Dir.rmdir #{dir.dump}", err: File::NULL)
system({"RUBYLIB"=>nil}, RUBY, "--disable-gems", "-e", "exit true")
status = $?
@@ -1627,16 +1784,16 @@ class TestProcess < Test::Unit::TestCase
def test_fallback_to_sh
feature = '[ruby-core:32745]'
with_tmpchdir do |d|
- open("tmp_script.#{$$}", "w") {|f| f.puts ": ;"; f.chmod(0755)}
+ File.write("tmp_script.#{$$}", ": ;\n", perm: 0o755)
assert_not_nil(pid = Process.spawn("./tmp_script.#{$$}"), feature)
wpid, st = Process.waitpid2(pid)
assert_equal([pid, true], [wpid, st.success?], feature)
- open("tmp_script.#{$$}", "w") {|f| f.puts "echo $#: $@"; f.chmod(0755)}
+ File.write("tmp_script.#{$$}", "echo $#: $@", perm: 0o755)
result = IO.popen(["./tmp_script.#{$$}", "a b", "c"]) {|f| f.read}
assert_equal("2: a b c\n", result, feature)
- open("tmp_script.#{$$}", "w") {|f| f.puts "echo $hghg"; f.chmod(0755)}
+ File.write("tmp_script.#{$$}", "echo $hghg", perm: 0o755)
result = IO.popen([{"hghg" => "mogomogo"}, "./tmp_script.#{$$}", "a b", "c"]) {|f| f.read}
assert_equal("mogomogo\n", result, feature)
@@ -1649,9 +1806,6 @@ class TestProcess < Test::Unit::TestCase
end
def test_aspawn_too_long_path
- if /solaris/i =~ RUBY_PLATFORM && !defined?(Process::RLIMIT_NPROC)
- skip "Too exhaustive test on platforms without Process::RLIMIT_NPROC such as Solaris 10"
- end
bug4315 = '[ruby-core:34833] #7904 [ruby-core:52628] #11613'
assert_fail_too_long_path(%w"echo |", bug4315)
end
@@ -1661,16 +1815,23 @@ class TestProcess < Test::Unit::TestCase
min = 1_000 / (cmd.size + sep.size)
cmds = Array.new(min, cmd)
exs = [Errno::ENOENT]
+ exs << Errno::EINVAL if windows?
exs << Errno::E2BIG if defined?(Errno::E2BIG)
opts = {[STDOUT, STDERR]=>File::NULL}
- opts[:rlimit_nproc] = 128 if defined?(Process::RLIMIT_NPROC)
+ if defined?(Process::RLIMIT_NPROC)
+ opts[:rlimit_nproc] = /openbsd/i =~ RUBY_PLATFORM ? 64 : 128
+ end
EnvUtil.suppress_warning do
assert_raise(*exs, mesg) do
begin
loop do
Process.spawn(cmds.join(sep), opts)
min = [cmds.size, min].max
- cmds *= 100
+ begin
+ cmds *= 100
+ rescue ArgumentError
+ raise NoMemoryError
+ end
end
rescue NoMemoryError
size = cmds.size
@@ -1691,7 +1852,7 @@ class TestProcess < Test::Unit::TestCase
with_tmpchdir do
assert_nothing_raised('[ruby-dev:12261]') do
- Timeout.timeout(3) do
+ EnvUtil.timeout(10) do
pid = spawn('yes | ls')
Process.waitpid pid
end
@@ -1750,6 +1911,28 @@ class TestProcess < Test::Unit::TestCase
assert_not_equal(cpid, dpid)
end
+ def test_daemon_detached
+ IO.popen("-", "r+") do |f|
+ if f
+ assert_equal(f.pid, Process.wait(f.pid))
+
+ dpid, ppid, dsid = 3.times.map {Integer(f.gets)}
+
+ message = "daemon #{dpid} should be detached"
+ assert_not_equal($$, ppid, message) # would be 1 almost always
+ assert_raise(Errno::ECHILD, message) {Process.wait(dpid)}
+ assert_kind_of(Integer, Process.kill(0, dpid), message)
+ assert_equal(dpid, dsid)
+
+ break # close f, and let the daemon resume and exit
+ end
+ Process.setsid rescue nil
+ Process.daemon(false, true)
+ puts $$, Process.ppid, Process.getsid
+ $stdin.gets # wait for the above assertions using signals
+ end
+ end
+
if File.directory?("/proc/self/task") && /netbsd[a-z]*[1-6]/ !~ RUBY_PLATFORM
def test_daemon_no_threads
pid, data = IO.popen("-", "r+") do |f|
@@ -1758,12 +1941,12 @@ class TestProcess < Test::Unit::TestCase
puts Dir.entries("/proc/self/task") - %W[. ..]
end
bug4920 = '[ruby-dev:43873]'
- assert_equal(2, data.size, bug4920)
+ assert_include(1..2, data.size, bug4920)
assert_not_include(data.map(&:to_i), pid)
end
else # darwin
def test_daemon_no_threads
- data = Timeout.timeout(3) do
+ data = EnvUtil.timeout(3) do
IO.popen("-") do |f|
break f.readlines.map(&:chomp) if f
th = Thread.start {sleep 3}
@@ -1812,6 +1995,16 @@ class TestProcess < Test::Unit::TestCase
end
end
+ def test_popen_reopen
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ io = File.open(IO::NULL)
+ io2 = io.dup
+ IO.popen("echo") {|f| io.reopen(f)}
+ io.reopen(io2)
+ end;
+ end
+
def test_execopts_new_pgroup
return unless windows?
@@ -1822,22 +2015,23 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_uid
+ omit "root can use uid option of Kernel#system on Android platform" if RUBY_PLATFORM =~ /android/
feature6975 = '[ruby-core:47414]'
[30000, [Process.uid, ENV["USER"]]].each do |uid, user|
if user
assert_nothing_raised(feature6975) do
begin
- system(*TRUECOMMAND, uid: user)
- rescue Errno::EPERM, NotImplementedError
+ system(*TRUECOMMAND, uid: user, exception: true)
+ rescue Errno::EPERM, Errno::EACCES, NotImplementedError
end
end
end
assert_nothing_raised(feature6975) do
begin
- system(*TRUECOMMAND, uid: uid)
- rescue Errno::EPERM, NotImplementedError
+ system(*TRUECOMMAND, uid: uid, exception: true)
+ rescue Errno::EPERM, Errno::EACCES, NotImplementedError
end
end
@@ -1845,17 +2039,23 @@ class TestProcess < Test::Unit::TestCase
begin
u = IO.popen([RUBY, "-e", "print Process.uid", uid: user||uid], &:read)
assert_equal(uid.to_s, u, feature6975)
- rescue Errno::EPERM, NotImplementedError
+ rescue Errno::EPERM, Errno::EACCES, NotImplementedError
end
end
end
end
def test_execopts_gid
- skip "Process.groups not implemented on Windows platform" if windows?
+ omit "Process.groups not implemented on Windows platform" if windows?
+ omit "root can use Process.groups on Android platform" if RUBY_PLATFORM =~ /android/
feature6975 = '[ruby-core:47414]'
- [30000, *Process.groups.map {|g| g = Etc.getgrgid(g); [g.name, g.gid]}].each do |group, gid|
+ groups = Process.groups.map do |g|
+ g = Etc.getgrgid(g) rescue next
+ [g.name, g.gid]
+ end
+ groups.compact!
+ [30000, *groups].each do |group, gid|
assert_nothing_raised(feature6975) do
begin
system(*TRUECOMMAND, gid: group)
@@ -1973,7 +2173,7 @@ EOS
"c\u{1EE7}a",
].each do |arg|
begin
- arg = arg.encode(Encoding.find("locale"))
+ arg = arg.encode(Encoding.local_charmap)
rescue
else
assert_in_out_err([], "#{<<-"begin;"}\n#{<<-"end;"}", [arg], [], bug12841)
@@ -1991,7 +2191,9 @@ EOS
t3 = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
assert_operator(t1, :<=, t2)
assert_operator(t2, :<=, t3)
- assert_raise(Errno::EINVAL) { Process.clock_gettime(:foo) }
+ assert_raise_with_message(Errno::EINVAL, /:foo/) do
+ Process.clock_gettime(:foo)
+ end
end
def test_clock_gettime_unit
@@ -2031,7 +2233,11 @@ EOS
def test_clock_gettime_GETTIMEOFDAY_BASED_CLOCK_REALTIME
n = :GETTIMEOFDAY_BASED_CLOCK_REALTIME
- t = Process.clock_gettime(n)
+ begin
+ t = Process.clock_gettime(n)
+ rescue Errno::EINVAL
+ return
+ end
assert_kind_of(Float, t, "Process.clock_gettime(:#{n})")
end
@@ -2092,7 +2298,9 @@ EOS
rescue Errno::EINVAL
else
assert_kind_of(Integer, r)
- assert_raise(Errno::EINVAL) { Process.clock_getres(:foo) }
+ assert_raise_with_message(Errno::EINVAL, /:foo/) do
+ Process.clock_getres(:foo)
+ end
end
def test_clock_getres_constants
@@ -2109,7 +2317,11 @@ EOS
def test_clock_getres_GETTIMEOFDAY_BASED_CLOCK_REALTIME
n = :GETTIMEOFDAY_BASED_CLOCK_REALTIME
- t = Process.clock_getres(n)
+ begin
+ t = Process.clock_getres(n)
+ rescue Errno::EINVAL
+ return
+ end
assert_kind_of(Float, t, "Process.clock_getres(:#{n})")
assert_equal(1000, Process.clock_getres(n, :nanosecond))
end
@@ -2175,7 +2387,7 @@ EOS
end
def test_deadlock_by_signal_at_forking
- assert_separately(["-", RUBY], <<-INPUT, timeout: 80)
+ assert_ruby_status(%W(- #{RUBY}), <<-INPUT, timeout: 100)
ruby = ARGV.shift
GC.start # reduce garbage
GC.disable # avoid triggering CoW after forks
@@ -2183,10 +2395,8 @@ EOS
parent = $$
100.times do |i|
pid = fork {Process.kill(:QUIT, parent)}
- IO.popen(ruby, 'r+'){}
+ IO.popen([ruby, -'--disable=gems'], -'r+'){}
Process.wait(pid)
- $stdout.puts
- $stdout.flush
end
INPUT
end if defined?(fork)
@@ -2196,7 +2406,7 @@ EOS
th = Process.detach(pid)
assert_equal pid, th.pid
status = th.value
- assert status.success?, status.inspect
+ assert_predicate status, :success?
end if defined?(fork)
def test_kill_at_spawn_failure
@@ -2204,7 +2414,9 @@ EOS
th = nil
x = with_tmpchdir {|d|
prog = "#{d}/notexist"
- th = Thread.start {system(prog);sleep}
+ q = Thread::Queue.new
+ th = Thread.start {system(prog);q.push(nil);sleep}
+ q.pop
th.kill
th.join(0.1)
}
@@ -2255,7 +2467,7 @@ EOS
def test_signals_work_after_exec_fail
r, w = IO.pipe
pid = status = nil
- Timeout.timeout(30) do
+ EnvUtil.timeout(30) do
pid = fork do
r.close
begin
@@ -2289,7 +2501,7 @@ EOS
def test_threading_works_after_exec_fail
r, w = IO.pipe
pid = status = nil
- Timeout.timeout(90) do
+ EnvUtil.timeout(90) do
pid = fork do
r.close
begin
@@ -2297,8 +2509,7 @@ EOS
rescue SystemCallError
w.syswrite("exec failed\n")
end
- q = Queue.new
- run = true
+ q = Thread::Queue.new
th1 = Thread.new { i = 0; i += 1 while q.empty?; i }
th2 = Thread.new { j = 0; j += 1 while q.empty? && Thread.pass.nil?; j }
sleep 0.5
@@ -2307,7 +2518,7 @@ EOS
end
w.close
assert_equal "exec failed\n", r.gets
- vals = r.gets.chomp.split.map!(&:to_i)
+ vals = r.gets.split.map!(&:to_i)
assert_operator vals[0], :>, vals[1], vals.inspect
_, status = Process.waitpid2(pid)
end
@@ -2323,6 +2534,15 @@ EOS
r.close if r
end if defined?(fork)
+ def test_rescue_exec_fail
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise(Errno::ENOENT) do
+ exec("", in: "")
+ end
+ end;
+ end
+
def test_many_args
bug11418 = '[ruby-core:70251] [Bug #11418]'
assert_in_out_err([], <<-"end;", ["x"]*256, [], bug11418, timeout: 60)
@@ -2352,7 +2572,7 @@ EOS
end
def test_forked_child_handles_signal
- skip "fork not supported" unless Process.respond_to?(:fork)
+ omit "fork not supported" unless Process.respond_to?(:fork)
assert_normal_exit(<<-"end;", '[ruby-core:82883] [Bug #13916]')
require 'timeout'
pid = fork { sleep }
@@ -2373,4 +2593,272 @@ EOS
Process.wait spawn(RUBY, "-e", "exit 13")
assert_same(Process.last_status, $?)
end
+
+ def test_last_status_failure
+ assert_nil system("sad")
+ assert_not_predicate $?, :success?
+ assert_equal $?.exitstatus, 127
+ end
+
+ def test_exec_failure_leaves_no_child
+ assert_raise(Errno::ENOENT) do
+ spawn('inexistent_command')
+ end
+ assert_empty(Process.waitall)
+ end
+
+ def test__fork
+ r, w = IO.pipe
+ pid = Process._fork
+ if pid == 0
+ begin
+ r.close
+ w << "ok: #$$"
+ w.close
+ ensure
+ exit!
+ end
+ else
+ w.close
+ assert_equal("ok: #{pid}", r.read)
+ r.close
+ Process.waitpid(pid)
+ end
+ end if Process.respond_to?(:_fork)
+
+ def test__fork_pid_cache
+ _parent_pid = Process.pid
+ r, w = IO.pipe
+ pid = Process._fork
+ if pid == 0
+ begin
+ r.close
+ w << "ok: #{Process.pid}"
+ w.close
+ ensure
+ exit!
+ end
+ else
+ w.close
+ assert_equal("ok: #{pid}", r.read)
+ r.close
+ Process.waitpid(pid)
+ end
+ end if Process.respond_to?(:_fork)
+
+ def test__fork_hook
+ %w(fork Process.fork).each do |method|
+ feature17795 = '[ruby-core:103400] [Feature #17795]'
+ assert_in_out_err([], <<-"end;", [], [], feature17795, timeout: 60) do |r, e|
+ module ForkHook
+ def _fork
+ p :before
+ ret = super
+ p :after
+ ret
+ end
+ end
+
+ Process.singleton_class.prepend(ForkHook)
+
+ pid = #{ method }
+ p pid
+ Process.waitpid(pid) if pid
+ end;
+ assert_equal([], e)
+ assert_equal(":before", r.shift)
+ assert_equal(":after", r.shift)
+ s = r.map {|s| s.chomp }.sort #=> [pid, ":after", "nil"]
+ assert_match(/^\d+$/, s[0]) # pid
+ assert_equal(":after", s[1])
+ assert_equal("nil", s[2])
+ end
+ end
+ end if Process.respond_to?(:_fork)
+
+ def test__fork_hook_popen
+ feature17795 = '[ruby-core:103400] [Feature #17795]'
+ assert_in_out_err([], <<-"end;", %w(:before :after :after foo bar), [], feature17795, timeout: 60)
+ module ForkHook
+ def _fork
+ p :before
+ ret = super
+ p :after
+ ret
+ end
+ end
+
+ Process.singleton_class.prepend(ForkHook)
+
+ IO.popen("-") {|io|
+ if !io
+ puts "foo"
+ else
+ puts io.read + "bar"
+ end
+ }
+ end;
+ end if Process.respond_to?(:_fork)
+
+ def test__fork_wrong_type_hook
+ feature17795 = '[ruby-core:103400] [Feature #17795]'
+ assert_in_out_err([], <<-"end;", ["OK"], [], feature17795, timeout: 60)
+ module ForkHook
+ def _fork
+ "BOO"
+ end
+ end
+
+ Process.singleton_class.prepend(ForkHook)
+
+ begin
+ fork
+ rescue TypeError
+ puts "OK"
+ end
+ end;
+ end if Process.respond_to?(:_fork)
+
+ def test_warmup_promote_all_objects_to_oldgen
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ require 'objspace'
+ begin;
+ obj = Object.new
+
+ assert_not_include(ObjectSpace.dump(obj), '"old":true')
+ Process.warmup
+ assert_include(ObjectSpace.dump(obj), '"old":true')
+ end;
+ end
+
+ def test_warmup_run_major_gc_and_compact
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # Run a GC to ensure that we are not in the middle of a GC run
+ GC.start
+
+ major_gc_count = GC.stat(:major_gc_count)
+ compact_count = GC.stat(:compact_count)
+ Process.warmup
+ assert_equal major_gc_count + 1, GC.stat(:major_gc_count)
+ assert_equal compact_count + 1, GC.stat(:compact_count)
+ end;
+ end
+
+ def test_warmup_precompute_string_coderange
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ require 'objspace'
+ begin;
+ obj = "a" * 12
+ obj.force_encoding(Encoding::UTF_16LE)
+ obj.force_encoding(Encoding::BINARY)
+ assert_include(ObjectSpace.dump(obj), '"coderange":"unknown"')
+ Process.warmup
+ assert_include(ObjectSpace.dump(obj), '"coderange":"7bit"')
+ end;
+ end
+
+ def test_warmup_frees_pages
+ assert_separately([{"RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO" => "1.0"}, "-W0"], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ GC.start
+
+ TIMES = 100_000
+ ary = Array.new(TIMES)
+ TIMES.times do |i|
+ ary[i] = Object.new
+ end
+ ary.clear
+ ary = nil
+
+ # Disable GC so we can make sure GC only runs in Process.warmup
+ GC.disable
+
+ total_slots_before = GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_slots)
+
+ Process.warmup
+
+ # 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;
+ end
+
+ def test_concurrent_group_and_pid_wait
+ # Use a pair of pipes that will make long_pid exit when this test exits, to avoid
+ # leaking temp processes.
+ long_rpipe, long_wpipe = IO.pipe
+ short_rpipe, short_wpipe = IO.pipe
+ # This process should run forever
+ long_pid = fork do
+ [short_rpipe, short_wpipe, long_wpipe].each(&:close)
+ long_rpipe.read
+ end
+ # This process will exit
+ short_pid = fork do
+ [long_rpipe, long_wpipe, short_wpipe].each(&:close)
+ short_rpipe.read
+ end
+ t1, t2, t3 = nil
+ EnvUtil.timeout(5) do
+ t1 = Thread.new do
+ Process.waitpid long_pid
+ end
+ # Wait for us to be blocking in a call to waitpid2
+ Thread.pass until t1.stop?
+ short_wpipe.close # Make short_pid exit
+
+ # The short pid has exited, so -1 should pick that up.
+ assert_equal short_pid, Process.waitpid(-1)
+
+ # Terminate t1 for the next phase of the test.
+ t1.kill
+ t1.join
+
+ t2 = Thread.new do
+ Process.waitpid(-1)
+ rescue Errno::ECHILD
+ nil
+ end
+ Thread.pass until t2.stop?
+ t3 = Thread.new do
+ Process.waitpid long_pid
+ rescue Errno::ECHILD
+ nil
+ end
+ Thread.pass until t3.stop?
+
+ # it's actually nondeterministic which of t2 or t3 will receive the wait (this
+ # nondeterminism comes from the behaviour of the underlying system calls)
+ long_wpipe.close
+ assert_equal [long_pid], [t2, t3].map(&:value).compact
+ end
+ ensure
+ [t1, t2, t3].each { _1&.kill rescue nil }
+ [t1, t2, t3].each { _1&.join rescue nil }
+ [long_rpipe, long_wpipe, short_rpipe, short_wpipe].each { _1&.close rescue nil }
+ end if defined?(fork)
+
+ def test_handle_interrupt_with_fork
+ Thread.handle_interrupt(RuntimeError => :never) do
+ Thread.current.raise(RuntimeError, "Queued error")
+
+ assert_predicate Thread, :pending_interrupt?
+
+ pid = Process.fork do
+ if Thread.pending_interrupt?
+ exit 1
+ end
+ end
+
+ _, status = Process.waitpid2(pid)
+ assert_predicate status, :success?
+
+ assert_predicate Thread, :pending_interrupt?
+ end
+ rescue RuntimeError
+ # Ignore.
+ end if defined?(fork)
end
diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb
new file mode 100644
index 0000000000..6ae511217a
--- /dev/null
+++ b/test/ruby/test_ractor.rb
@@ -0,0 +1,229 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestRactor < Test::Unit::TestCase
+ def test_shareability_of_iseq_proc
+ assert_raise Ractor::IsolationError do
+ foo = []
+ Ractor.shareable_proc{ foo }
+ end
+ 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/)
+
+ x = str.instance_exec { method(:to_s) }
+ assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
+
+ x = str.instance_exec { method(:to_s).to_proc }
+ assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
+
+ x = str.instance_exec { method(:itself).to_proc }
+ assert_unshareable(x, "can not make shareable object for #<Method: String(Kernel)#itself()>", exception: Ractor::Error)
+
+ str.freeze
+
+ x = str.instance_exec { proc { to_s } }
+ assert_make_shareable(x)
+
+ x = str.instance_exec { method(:to_s) }
+ assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
+
+ x = str.instance_exec { method(:to_s).to_proc }
+ assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
+
+ 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
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ Warning[:experimental] = false
+
+ main_ractor_id = Thread.current.group.object_id
+ 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)
+ assert Ractor.shareable?(obj), "object didn't become shareable"
+ end
+
+ def assert_unshareable(obj, msg=nil, exception: Ractor::IsolationError)
+ refute Ractor.shareable?(obj), "object is already shareable"
+ assert_raise_with_message(exception, msg) do
+ Ractor.make_shareable(obj)
+ end
+ refute Ractor.shareable?(obj), "despite raising, object became shareable"
+ end
+end
diff --git a/test/ruby/test_rand.rb b/test/ruby/test_rand.rb
index 882d33fb40..f177664943 100644
--- a/test/ruby/test_rand.rb
+++ b/test/ruby/test_rand.rb
@@ -2,14 +2,13 @@
require 'test/unit'
class TestRand < Test::Unit::TestCase
- def assert_random_int(ws, m, init = 0)
+ def assert_random_int(m, init = 0, iterate: 5)
srand(init)
rnds = [Random.new(init)]
rnds2 = [rnds[0].dup]
rnds3 = [rnds[0].dup]
- ws.each_with_index do |w, i|
- w = w.to_i
- assert_equal(w, rand(m))
+ iterate.times do |i|
+ w = rand(m)
rnds.each do |rnd|
assert_equal(w, rnd.rand(m))
end
@@ -27,133 +26,97 @@ class TestRand < Test::Unit::TestCase
end
def test_mt
- assert_random_int(%w(1067595299 955945823 477289528 4107218783 4228976476),
- 0x100000000, 0x00000456_00000345_00000234_00000123)
+ assert_random_int(0x100000000, 0x00000456_00000345_00000234_00000123)
end
def test_0x3fffffff
- assert_random_int(%w(209652396 398764591 924231285 404868288 441365315),
- 0x3fffffff)
+ assert_random_int(0x3fffffff)
end
def test_0x40000000
- assert_random_int(%w(209652396 398764591 924231285 404868288 441365315),
- 0x40000000)
+ assert_random_int(0x40000000)
end
def test_0x40000001
- assert_random_int(%w(209652396 398764591 924231285 441365315 192771779),
- 0x40000001)
+ assert_random_int(0x40000001)
end
def test_0xffffffff
- assert_random_int(%w(2357136044 2546248239 3071714933 3626093760 2588848963),
- 0xffffffff)
+ assert_random_int(0xffffffff)
end
def test_0x100000000
- assert_random_int(%w(2357136044 2546248239 3071714933 3626093760 2588848963),
- 0x100000000)
+ assert_random_int(0x100000000)
end
def test_0x100000001
- assert_random_int(%w(2546248239 1277901399 243580376 1171049868 2051556033),
- 0x100000001)
+ assert_random_int(0x100000001)
end
def test_rand_0x100000000
- assert_random_int(%w(4119812344 3870378946 80324654 4294967296 410016213),
- 0x100000001, 311702798)
+ assert_random_int(0x100000001, 311702798)
end
def test_0x1000000000000
- assert_random_int(%w(11736396900911
- 183025067478208
- 197104029029115
- 130583529618791
- 180361239846611),
- 0x1000000000000)
+ assert_random_int(0x1000000000000)
end
def test_0x1000000000001
- assert_random_int(%w(187121911899765
- 197104029029115
- 180361239846611
- 236336749852452
- 208739549485656),
- 0x1000000000001)
+ assert_random_int(0x1000000000001)
end
def test_0x3fffffffffffffff
- assert_random_int(%w(900450186894289455
- 3969543146641149120
- 1895649597198586619
- 827948490035658087
- 3203365596207111891),
- 0x3fffffffffffffff)
+ assert_random_int(0x3fffffffffffffff)
end
def test_0x4000000000000000
- assert_random_int(%w(900450186894289455
- 3969543146641149120
- 1895649597198586619
- 827948490035658087
- 3203365596207111891),
- 0x4000000000000000)
+ assert_random_int(0x4000000000000000)
end
def test_0x4000000000000001
- assert_random_int(%w(900450186894289455
- 3969543146641149120
- 1895649597198586619
- 827948490035658087
- 2279347887019741461),
- 0x4000000000000001)
+ assert_random_int(0x4000000000000001)
end
def test_0x10000000000
- ws = %w(455570294424 1073054410371 790795084744 2445173525 1088503892627)
- assert_random_int(ws, 0x10000000000, 3)
+ assert_random_int(0x10000000000, 3)
end
def test_0x10000
- ws = %w(2732 43567 42613 52416 45891)
- assert_random_int(ws, 0x10000)
+ assert_random_int(0x10000)
+ end
+
+ def assert_same_numbers(type, *nums)
+ nums.each do |n|
+ assert_instance_of(type, n)
+ end
+ x = nums.shift
+ nums.each do |n|
+ assert_equal(x, n)
+ end
+ x
end
def test_types
- srand(0)
- rnd = Random.new(0)
- assert_equal(44, rand(100.0))
- assert_equal(44, rnd.rand(100))
- assert_equal(1245085576965981900420779258691, rand((2**100).to_f))
- assert_equal(1245085576965981900420779258691, rnd.rand(2**100))
- assert_equal(914679880601515615685077935113, rand(-(2**100).to_f))
+ o = Object.new
+ class << o
+ def to_int; 100; end
+ def class; Integer; end
+ end
srand(0)
- rnd = Random.new(0)
- assert_equal(997707939797331598305742933184, rand(2**100))
- assert_equal(997707939797331598305742933184, rnd.rand(2**100))
- assert_in_delta(0.602763376071644, rand((2**100).coerce(0).first),
- 0.000000000000001)
- assert_raise(ArgumentError) {rnd.rand((2**100).coerce(0).first)}
+ nums = [100.0, (2**100).to_f, (2**100), o, o, o].map do |m|
+ k = Integer
+ assert_kind_of(k, x = rand(m), m.inspect)
+ [m, k, x]
+ end
+ assert_kind_of(Integer, rand(-(2**100).to_f))
srand(0)
rnd = Random.new(0)
- assert_in_delta(0.548813503927325, rand(nil),
- 0.000000000000001)
- assert_in_delta(0.548813503927325, rnd.rand(),
- 0.000000000000001)
- srand(0)
- rnd = Random.new(0)
- o = Object.new
- def o.to_int; 100; end
- assert_equal(44, rand(o))
- assert_equal(44, rnd.rand(o))
- assert_equal(47, rand(o))
- assert_equal(47, rnd.rand(o))
- assert_equal(64, rand(o))
- assert_equal(64, rnd.rand(o))
+ rnd2 = Random.new(0)
+ nums.each do |m, k, x|
+ assert_same_numbers(m.class, Random.rand(m), rnd.rand(m), rnd2.rand(m))
+ end
end
def test_srand
@@ -163,10 +126,13 @@ class TestRand < Test::Unit::TestCase
srand(2**100)
rnd = Random.new(2**100)
- %w(3258412053).each {|w|
- assert_equal(w.to_i, rand(0x100000000))
- assert_equal(w.to_i, rnd.rand(0x100000000))
- }
+ r = 3.times.map do
+ assert_same_numbers(Integer, rand(0x100000000), rnd.rand(0x100000000))
+ end
+ srand(2**100)
+ r.each do |n|
+ assert_same_numbers(Integer, n, rand(0x100000000))
+ end
end
def test_shuffle
@@ -177,17 +143,17 @@ class TestRand < Test::Unit::TestCase
end
def test_big_seed
- assert_random_int(%w(2757555016), 0x100000000, 2**1000000-1)
+ assert_random_int(0x100000000, 2**1000000-1)
end
def test_random_gc
r = Random.new(0)
- %w(2357136044 2546248239 3071714933).each do |w|
- assert_equal(w.to_i, r.rand(0x100000000))
+ 3.times do
+ assert_kind_of(Integer, r.rand(0x100000000))
end
GC.start
- %w(3626093760 2588848963 3684848379).each do |w|
- assert_equal(w.to_i, r.rand(0x100000000))
+ 3.times do
+ assert_kind_of(Integer, r.rand(0x100000000))
end
end
@@ -223,183 +189,68 @@ class TestRand < Test::Unit::TestCase
def test_random_dup
r1 = Random.new(0)
r2 = r1.dup
- %w(2357136044 2546248239 3071714933).each do |w|
- assert_equal(w.to_i, r1.rand(0x100000000))
- end
- %w(2357136044 2546248239 3071714933).each do |w|
- assert_equal(w.to_i, r2.rand(0x100000000))
+ 3.times do
+ assert_same_numbers(Integer, r1.rand(0x100000000), r2.rand(0x100000000))
end
r2 = r1.dup
- %w(3626093760 2588848963 3684848379).each do |w|
- assert_equal(w.to_i, r1.rand(0x100000000))
- end
- %w(3626093760 2588848963 3684848379).each do |w|
- assert_equal(w.to_i, r2.rand(0x100000000))
+ 3.times do
+ assert_same_numbers(Integer, r1.rand(0x100000000), r2.rand(0x100000000))
end
end
- def test_random_state
- state = <<END
-3877134065023083674777481835852171977222677629000095857864323111193832400974413
-4782302161934463784850675209112299537259006497924090422596764895633625964527441
-6943943249411681406395713106007661119327771293929504639878577616749110507385924
-0173026285378896836022134086386136835407107422834685854738117043791709411958489
-3504364936306163473541948635570644161010981140452515307286926529085424765299100
-1255453260115310687580777474046203049197643434654645011966794531914127596390825
-0832232869378617194193100828000236737535657699356156021286278281306055217995213
-8911536025132779573429499813926910299964681785069915877910855089314686097947757
-2621451199734871158015842198110309034467412292693435515184023707918034746119728
-8223459645048255809852819129671833854560563104716892857257229121211527031509280
-2390605053896565646658122125171846129817536096211475312518457776328637574563312
-8113489216547503743508184872149896518488714209752552442327273883060730945969461
-6568672445225657265545983966820639165285082194907591432296265618266901318398982
-0560425129536975583916120558652408261759226803976460322062347123360444839683204
-9868507788028894111577023917218846128348302845774997500569465902983227180328307
-3735301552935104196244116381766459468172162284042207680945316590536094294865648
-5953156978630954893391701383648157037914019502853776972615500142898763385846315
-8457790690531675205213829055442306187692107777193071680668153335688203945049935
-3404449910419303330872435985327845889440458370416464132629866593538629877042969
-7589948685901343135964343582727302330074331803900821801076139161904900333497836
-6627028345784450211920229170280462474456504317367706849373957309797251052447898
-8436235456995644876515032202483449807136139063937892187299239634252391529822163
-9187055268750679730919937006195967184206072757082920250075756273182004964087790
-3812024063897424219316687828337007888030994779068081666133751070479581394604974
-6022215489604777611774245181115126041390161592199230774968475944753915533936834
-4740049163514318351045644344358598159463605453475585370226041981040238023241538
-4958436364776113598408428801867643946791659645708540669432995503575075657406359
-8086928867900590554805639837071298576728564946552163206007997000988745940681607
-4542883814997403673656291618517107133421335645430345871041730410669209035640945
-5024601618318371192091626092482640364669969307766919645222516407626038616667754
-5781148898846306894862390724358039251444333889446128074209417936830253204064223
-3424784857908022314095011879203259864909560830176189727132432100010493659154644
-8407326292884826469503093409465946018496358514999175268200846200025235441426140
-7783386235191526371372655894290440356560751680752191224383460972099834655086068
-9989413443881686756951804138910911737670495391762470293978321414964443502180391
-4665982575919524372985773336921990352313629822677022891307943536442258282401255
-5387646898976193134193506239982621725093291970351083631367582570375381334759004
-1784150668048523676387894646666460369896619585113435743180899362844070393586212
-5023920017185866399742380706352739465848708746963693663004068892056705603018655
-8686663087894205699555906146534549176352859823832196938386172810274748624517052
-8356758650653040545267425513047130342286119889879774951060662713807133125543465
-5104086026298674827575216701372513525846650644773241437066782037334367982012148
-7987782004646896468089089826267467005660035604553432197455616078731159778086155
-9443250946037119223468305483694093795324036812927501783593256716590840500905291
-2096608538205270323065573109227029887731553399324547696295234105140157179430410
-4003109602564833086703863221058381556776789018351726488797845637981974580864082
-1630093543020854542240690897858757985640869209737744458407777584279553258261599
-0246922348101147034463235613998979344685018577901996218099622190722307356620796
-5137485271371502385527388080824050288371607602101805675021790116223360483508538
-8832149997794718410946818866375912486788005950091851067237358294899771385995876
-7088239104394332452501033090159333224995108984871426750597513314521294001864578
-2353528356752869732412552685554334966798888534847483030947310518891788722418172
-6008607577773612004956373863580996793809969715725508939568919714424871639667201
-7922255031431159347210833846575355772055570279673262115911154370983086189948124
-4653677615895887099814174914248255026619941911735341818489822197472499295786997
-7728418516719104857455960900092226749725407204388193002835497055305427730656889
-1508308778869166073740855838213112709306743479676740893150000714099064468263284
-1873435518542972182497755500300784177067568586395485329021157235696300013490087
-2866571034916258390528533374944905429089028336079264760836949419754851422499614
-5732326011260304142074554782259843903215064144396140106592193961703288125005023
-5334375212799817540775536847622032852415253966587517800661605905489339306359573
-2234947905196298436841723673626428243649931398749552780311877734063703985375067
-1239508613417041942487245370152912391885566432830659640677893488723724763120121
-4111855277511356759926232894062814360449757490961653026194107761340614059045172
-1123363102660719217740126157997033682099769790976313166682432732518101889210276
-9574144065390305904944821051736021310524344626348851573631697771556587859836330
-6997324121866564283654784470215100159122764509197570402997911258816526554863326
-9877535269005418736225944874608987238997316999444215865249840762640949599725696
-0773083894168959823152054508672272612355108904098579447774398451678239199426513
-3439507737424049578587487505080347686371029156845461151278198605267053408259090
-3158676794894709281917034995611352710898103415304769654883981727681820369090169
-9295163908214854813365413456264812190842699054830709079275249714169405719140093
-1347572458245530016346604698682269779841803667099480215265926316505737171177810
-9969036572310084022695109125200937135540995157279354438704321290061646592229860
-0156566013602344870223183295508278359111174872740360473845615437106413256386849
-2286259982118315248148847764929974917157683083659364623458927512616369119194574
-2254080
-END
- state = state.split.join.to_i
- r = Random.new(0)
+ def test_random_bytes
srand(0)
- assert_equal(state, r.instance_eval { state })
- assert_equal(state, Random.instance_eval { state })
- r.rand(0x100)
- assert_equal(state, r.instance_eval { state })
- end
-
- def test_random_left
r = Random.new(0)
- assert_equal(1, r.instance_eval { left })
- r.rand(0x100)
- assert_equal(624, r.instance_eval { left })
- r.rand(0x100)
- assert_equal(623, r.instance_eval { left })
- srand(0)
- assert_equal(1, Random.instance_eval { left })
- rand(0x100)
- assert_equal(624, Random.instance_eval { left })
- rand(0x100)
- assert_equal(623, Random.instance_eval { left })
- end
-
- def test_random_bytes
- assert_random_bytes(Random.new(0))
- end
- def assert_random_bytes(r)
assert_equal("", r.bytes(0))
- assert_equal("\xAC".force_encoding("ASCII-8BIT"), r.bytes(1))
- assert_equal("/\xAA\xC4\x97u\xA6\x16\xB7\xC0\xCC".force_encoding("ASCII-8BIT"),
- r.bytes(10))
+ assert_equal("", Random.bytes(0))
+
+ x = r.bytes(1)
+ assert_equal(1, x.bytesize)
+ assert_equal(x, Random.bytes(1))
+
+ x = r.bytes(10)
+ assert_equal(10, x.bytesize)
+ assert_equal(x, Random.bytes(10))
end
def test_random_range
srand(0)
r = Random.new(0)
- %w(9 5 8).each {|w|
- assert_equal(w.to_i, rand(5..9))
- assert_equal(w.to_i, r.rand(5..9))
- }
- %w(-237 731 383).each {|w|
- assert_equal(w.to_i, rand(-1000..1000))
- assert_equal(w.to_i, r.rand(-1000..1000))
- }
- %w(1267650600228229401496703205382
- 1267650600228229401496703205384
- 1267650600228229401496703205383).each do |w|
- assert_equal(w.to_i, rand(2**100+5..2**100+9))
- assert_equal(w.to_i, r.rand(2**100+5..2**100+9))
- end
-
- v = rand(3.1..4)
- assert_instance_of(Float, v, '[ruby-core:24679]')
- assert_include(3.1..4, v)
-
- v = r.rand(3.1..4)
- assert_instance_of(Float, v, '[ruby-core:24679]')
- assert_include(3.1..4, v)
-
now = Time.now
- assert_equal(now, rand(now..now))
- assert_equal(now, r.rand(now..now))
+ [5..9, -1000..1000, 2**100+5..2**100+9, 3.1..4, now..(now+2)].each do |range|
+ 3.times do
+ x = rand(range)
+ assert_instance_of(range.first.class, x)
+ assert_equal(x, r.rand(range))
+ assert_include(range, x)
+ end
+ end
end
def test_random_float
r = Random.new(0)
- assert_in_delta(0.5488135039273248, r.rand, 0.0001)
- assert_in_delta(0.7151893663724195, r.rand, 0.0001)
- assert_in_delta(0.6027633760716439, r.rand, 0.0001)
- assert_in_delta(1.0897663659937937, r.rand(2.0), 0.0001)
- assert_in_delta(5.3704626067153264e+29, r.rand((2**100).to_f), 10**25)
+ 3.times do
+ assert_include(0...1.0, r.rand)
+ end
+ [2.0, (2**100).to_f].each do |x|
+ range = 0...x
+ 3.times do
+ assert_include(range, r.rand(x), "rand(#{x})")
+ end
+ end
assert_raise(Errno::EDOM, Errno::ERANGE) { r.rand(1.0 / 0.0) }
assert_raise(Errno::EDOM, Errno::ERANGE) { r.rand(0.0 / 0.0) }
+ assert_raise(Errno::EDOM) {r.rand(1..)}
+ assert_raise(Errno::EDOM) {r.rand(..1)}
r = Random.new(0)
- assert_in_delta(1.5488135039273248, r.rand(1.0...2.0), 0.0001, '[ruby-core:24655]')
- assert_in_delta(1.7151893663724195, r.rand(1.0...2.0), 0.0001, '[ruby-core:24655]')
- assert_in_delta(7.027633760716439, r.rand(1.0...11.0), 0.0001, '[ruby-core:24655]')
- assert_in_delta(3.0897663659937937, r.rand(2.0...4.0), 0.0001, '[ruby-core:24655]')
+ [1.0...2.0, 1.0...11.0, 2.0...4.0].each do |range|
+ 3.times do
+ assert_include(range, r.rand(range), "[ruby-core:24655] rand(#{range})")
+ end
+ end
assert_nothing_raised {r.rand(-Float::MAX..Float::MAX)}
end
@@ -466,7 +317,7 @@ END
assert_equal(r1, r2, bug5661)
assert_fork_status(1, '[ruby-core:82100] [Bug #13753]') do
- Random::DEFAULT.rand(4)
+ Random.rand(4)
end
rescue NotImplementedError
end
@@ -485,15 +336,11 @@ END
}
end
- def test_default
- r1 = Random::DEFAULT.dup
- r2 = Random::DEFAULT.dup
- 3.times do
- x0 = rand
- x1 = r1.rand
- x2 = r2.rand
- assert_equal(x0, x1)
- assert_equal(x0, x2)
+ def test_seed_leading_zero_guard
+ guard = 1<<32
+ range = 0...(1<<32)
+ all_assertions_foreach(nil, 0, 1, 2) do |i|
+ assert_not_equal(Random.new(i).rand(range), Random.new(i+guard).rand(range))
end
end
@@ -553,16 +400,19 @@ END
end
def test_default_seed
- assert_separately([], <<-End)
- seed = Random::DEFAULT::seed
- rand1 = Random::DEFAULT::rand
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ verbose, $VERBOSE = $VERBOSE, nil
+ seed = Random.seed
+ rand1 = Random.rand
+ $VERBOSE = verbose
rand2 = Random.new(seed).rand
assert_equal(rand1, rand2)
srand seed
rand3 = rand
assert_equal(rand1, rand3)
- End
+ end;
end
def test_urandom
@@ -584,4 +434,9 @@ END
# probability of failure <= 1/256**8
assert_operator(size.fdiv(n), :>, 15)
end
+
+ def test_broken_marshal
+ assert_raise(ArgumentError) { Marshal.load("\x04\bU:\vRandom" + Marshal.dump([1,0,1])[2..]) }
+ assert_raise(ArgumentError) { Marshal.load("\x04\bU:\vRandom" + Marshal.dump([1,-1,1])[2..]) }
+ end
end
diff --git a/test/ruby/test_random_formatter.rb b/test/ruby/test_random_formatter.rb
new file mode 100644
index 0000000000..784fbfc2b8
--- /dev/null
+++ b/test/ruby/test_random_formatter.rb
@@ -0,0 +1,183 @@
+require 'test/unit'
+require 'random/formatter'
+
+module Random::Formatter
+ module FormatterTest
+ def test_random_bytes
+ assert_equal(16, @it.random_bytes.size)
+ assert_equal(Encoding::ASCII_8BIT, @it.random_bytes.encoding)
+ 65.times do |idx|
+ assert_equal(idx, @it.random_bytes(idx).size)
+ end
+ end
+
+ def test_hex
+ s = @it.hex
+ assert_equal(16 * 2, s.size)
+ assert_match(/\A\h+\z/, s)
+ 33.times do |idx|
+ s = @it.hex(idx)
+ assert_equal(idx * 2, s.size)
+ assert_match(/\A\h*\z/, s)
+ end
+ end
+
+ def test_hex_encoding
+ assert_equal(Encoding::US_ASCII, @it.hex.encoding)
+ end
+
+ def test_base64
+ assert_equal(16, @it.base64.unpack1('m*').size)
+ 17.times do |idx|
+ assert_equal(idx, @it.base64(idx).unpack1('m*').size)
+ end
+ end
+
+ def test_urlsafe_base64
+ safe = /[\n+\/]/
+ 65.times do |idx|
+ assert_not_match(safe, @it.urlsafe_base64(idx))
+ end
+ # base64 can include unsafe byte
+ assert((0..10000).any? {|idx| safe =~ @it.base64(idx)}, "None of base64(0..10000) is url-safe")
+ end
+
+ def test_random_number_float
+ 101.times do
+ v = @it.random_number
+ assert_in_range(0.0...1.0, v)
+ end
+ end
+
+ def test_random_number_float_by_zero
+ 101.times do
+ v = @it.random_number(0)
+ assert_in_range(0.0...1.0, v)
+ end
+ end
+
+ def test_random_number_int
+ 101.times do |idx|
+ next if idx.zero?
+ v = @it.random_number(idx)
+ assert_in_range(0...idx, v)
+ end
+ end
+
+ def test_uuid
+ uuid = @it.uuid
+ assert_equal(36, uuid.size)
+
+ # Check time_hi_and_version and clock_seq_hi_res bits (RFC 4122 4.4)
+ assert_equal('4', uuid[14])
+ assert_include(%w'8 9 a b', uuid[19])
+
+ assert_match(/\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/, uuid)
+ end
+
+ def assert_uuid_v7(**opts)
+ t1 = current_uuid7_time(**opts)
+ uuid = @it.uuid_v7(**opts)
+ t3 = current_uuid7_time(**opts)
+
+ assert_match(/\A\h{8}-\h{4}-7\h{3}-[89ab]\h{3}-\h{12}\z/, uuid)
+
+ t2 = get_uuid7_time(uuid, **opts)
+ assert_operator(t1, :<=, t2)
+ assert_operator(t2, :<=, t3)
+ end
+
+ def test_uuid_v7
+ assert_uuid_v7
+ 0.upto(12) do |extra_timestamp_bits|
+ assert_uuid_v7 extra_timestamp_bits: extra_timestamp_bits
+ end
+ end
+
+ # It would be nice to simply use Time#floor here. But that is problematic
+ # due to the difference between decimal vs binary fractions.
+ def current_uuid7_time(extra_timestamp_bits: 0)
+ denominator = (1 << extra_timestamp_bits).to_r
+ Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
+ .then {|ns| ((ns / 1_000_000r) * denominator).floor / denominator }
+ .then {|ms| Time.at(ms / 1000r, in: "+00:00") }
+ end
+
+ def get_uuid7_time(uuid, extra_timestamp_bits: 0)
+ denominator = (1 << extra_timestamp_bits) * 1000r
+ extra_chars = extra_timestamp_bits / 4
+ last_char_bits = extra_timestamp_bits % 4
+ extra_chars += 1 if last_char_bits != 0
+ timestamp_re = /\A(\h{8})-(\h{4})-7(\h{#{extra_chars}})/
+ timestamp_chars = uuid.match(timestamp_re).captures.join
+ timestamp = timestamp_chars.to_i(16)
+ timestamp >>= 4 - last_char_bits unless last_char_bits == 0
+ timestamp /= denominator
+ Time.at timestamp, in: "+00:00"
+ end
+
+ def test_alphanumeric
+ 65.times do |n|
+ an = @it.alphanumeric(n)
+ assert_match(/\A[0-9a-zA-Z]*\z/, an)
+ assert_equal(n, an.length)
+ end
+ end
+
+ def test_alphanumeric_chars
+ [
+ [[*"0".."9"], /\A\d*\z/],
+ [[*"a".."t"], /\A[a-t]*\z/],
+ ["一二三四五六七八ä¹å".chars, /\A[一二三四五六七八ä¹å]*\z/],
+ ].each do |chars, pattern|
+ 10.times do |n|
+ an = @it.alphanumeric(n, chars: chars)
+ assert_match(pattern, an)
+ assert_equal(n, an.length)
+ end
+ end
+ end
+
+ def assert_in_range(range, result, mesg = nil)
+ assert(range.cover?(result), build_message(mesg, "Expected #{result} to be in #{range}"))
+ end
+ end
+
+ module NotDefaultTest
+ def test_random_number_not_default
+ msg = "random_number should not be affected by srand"
+ seed = srand(0)
+ x = @it.random_number(1000)
+ 10.times do|i|
+ srand(0)
+ return unless @it.random_number(1000) == x
+ end
+ srand(0)
+ assert_not_equal(x, @it.random_number(1000), msg)
+ ensure
+ srand(seed) if seed
+ end
+ end
+
+ class TestClassMethods < Test::Unit::TestCase
+ include FormatterTest
+
+ def setup
+ @it = Random
+ end
+
+ def test_alphanumeric_frozen
+ assert_predicate @it::Formatter::ALPHANUMERIC, :frozen?
+ assert @it::Formatter::ALPHANUMERIC.all?(&:frozen?)
+ end
+ end
+
+ class TestInstanceMethods < Test::Unit::TestCase
+ include FormatterTest
+ include NotDefaultTest
+
+ def setup
+ @it = Random.new
+ end
+ end
+end
diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb
index da883767bc..ff17dca69e 100644
--- a/test/ruby/test_range.rb
+++ b/test/ruby/test_range.rb
@@ -2,7 +2,8 @@
require 'test/unit'
require 'delegate'
require 'timeout'
-require 'bigdecimal'
+require 'date'
+require 'rbconfig/sizeof'
class TestRange < Test::Unit::TestCase
def test_new
@@ -12,6 +13,9 @@ class TestRange < Test::Unit::TestCase
assert_raise(ArgumentError) { (1.."3") }
+ assert_equal((0..nil), Range.new(0, nil, false))
+ assert_equal((0...nil), Range.new(0, nil, true))
+
obj = Object.new
def obj.<=>(other)
raise RuntimeError, "cmp"
@@ -31,14 +35,18 @@ class TestRange < Test::Unit::TestCase
assert_equal(["a"], ("a" .. "a").to_a)
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
assert_equal(["6", "7", "8"], ("6".."8").to_a, "[ruby-talk:343187]")
assert_equal(["6", "7"], ("6"..."8").to_a)
assert_equal(["9", "10"], ("9".."10").to_a)
+ assert_equal(["9", "10"], ("9"..).take(2))
assert_equal(["09", "10"], ("09".."10").to_a, "[ruby-dev:39361]")
assert_equal(["9", "10"], (SimpleDelegator.new("9").."10").to_a)
+ assert_equal(["9", "10"], (SimpleDelegator.new("9")..).take(2))
assert_equal(["9", "10"], ("9"..SimpleDelegator.new("10")).to_a)
end
@@ -73,22 +81,33 @@ class TestRange < Test::Unit::TestCase
assert_equal(1, (1..2).min)
assert_equal(nil, (2..1).min)
assert_equal(1, (1...2).min)
+ assert_equal(1, (1..).min)
+ assert_raise(RangeError) { (..1).min }
+ assert_raise(RangeError) { (...1).min }
assert_equal(1.0, (1.0..2.0).min)
assert_equal(nil, (2.0..1.0).min)
assert_equal(1, (1.0...2.0).min)
+ assert_equal(1, (1.0..).min)
assert_equal(0, (0..0).min)
assert_equal(nil, (0...0).min)
assert_equal([0,1,2], (0..10).min(3))
assert_equal([0,1], (0..1).min(3))
+ assert_equal([0,1,2], (0..).min(3))
+ assert_raise(RangeError) { (..1).min(3) }
+ assert_raise(RangeError) { (...1).min(3) }
+
+ assert_raise(RangeError) { (0..).min {|a, b| a <=> b } }
end
def test_max
assert_equal(2, (1..2).max)
assert_equal(nil, (2..1).max)
assert_equal(1, (1...2).max)
+ assert_raise(RangeError) { (1..).max }
+ assert_raise(RangeError) { (1...).max }
assert_equal(2.0, (1.0..2.0).max)
assert_equal(nil, (2.0..1.0).max)
@@ -103,12 +122,52 @@ class TestRange < Test::Unit::TestCase
assert_equal([10,9,8], (0..10).max(3))
assert_equal([9,8,7], (0...10).max(3))
+ assert_equal([10,9,8], (..10).max(3))
+ assert_equal([9,8,7], (...10).max(3))
+ assert_raise(RangeError) { (1..).max(3) }
+ assert_raise(RangeError) { (1...).max(3) }
+
+ assert_raise(RangeError) { (..0).min {|a, b| a <=> b } }
+
+ assert_equal(2, (..2).max)
+ assert_equal(1, (...2).max)
+ assert_raise(TypeError) { (...2.0).max }
+
+ assert_equal(Float::INFINITY, (1..Float::INFINITY).max)
+ assert_nil((1..-Float::INFINITY).max)
+ end
+
+ def test_minmax
+ assert_equal([1, 2], (1..2).minmax)
+ assert_equal([nil, nil], (2..1).minmax)
+ assert_equal([1, 1], (1...2).minmax)
+ assert_raise(RangeError) { (1..).minmax }
+ assert_raise(RangeError) { (1...).minmax }
+
+ assert_equal([1.0, 2.0], (1.0..2.0).minmax)
+ assert_equal([nil, nil], (2.0..1.0).minmax)
+ assert_raise(TypeError) { (1.0...2.0).minmax }
+ assert_raise(TypeError) { (1...1.5).minmax }
+ assert_raise(TypeError) { (1.5...2).minmax }
+
+ assert_equal([-0x80000002, -0x80000002], ((-0x80000002)...(-0x80000001)).minmax)
+
+ assert_equal([0, 0], (0..0).minmax)
+ assert_equal([nil, nil], (0...0).minmax)
+
+ assert_equal([2, 1], (1..2).minmax{|a, b| b <=> a})
+
+ assert_equal(['a', 'c'], ('a'..'c').minmax)
+ assert_equal(['a', 'b'], ('a'...'c').minmax)
+
+ assert_equal([1, Float::INFINITY], (1..Float::INFINITY).minmax)
+ assert_equal([nil, nil], (1..-Float::INFINITY).minmax)
end
def test_initialize_twice
r = eval("1..2")
- assert_raise(NameError) { r.instance_eval { initialize 3, 4 } }
- assert_raise(NameError) { r.instance_eval { initialize_copy 3..4 } }
+ assert_raise(FrozenError) { r.instance_eval { initialize 3, 4 } }
+ assert_raise(FrozenError) { r.instance_eval { initialize_copy 3..4 } }
end
def test_uninitialized_range
@@ -123,9 +182,10 @@ class TestRange < Test::Unit::TestCase
assert_equal(r, Marshal.load(Marshal.dump(r)))
r = 1...2
assert_equal(r, Marshal.load(Marshal.dump(r)))
- s = Marshal.dump(r)
- s.sub!(/endi./n, 'end0')
- assert_raise(ArgumentError) {Marshal.load(s)}
+ r = (1..)
+ assert_equal(r, Marshal.load(Marshal.dump(r)))
+ r = (1...)
+ assert_equal(r, Marshal.load(Marshal.dump(r)))
end
def test_bad_value
@@ -135,6 +195,8 @@ class TestRange < Test::Unit::TestCase
def test_exclude_end
assert_not_predicate(0..1, :exclude_end?)
assert_predicate(0...1, :exclude_end?)
+ assert_not_predicate(0.., :exclude_end?)
+ assert_predicate(0..., :exclude_end?)
end
def test_eq
@@ -145,8 +207,17 @@ class TestRange < Test::Unit::TestCase
assert_not_equal(r, (1..2))
assert_not_equal(r, (0..2))
assert_not_equal(r, (0...1))
+ assert_not_equal(r, (0..nil))
subclass = Class.new(Range)
assert_equal(r, subclass.new(0,1))
+
+ r = (0..nil)
+ assert_equal(r, r)
+ assert_equal(r, (0..nil))
+ assert_not_equal(r, 0)
+ assert_not_equal(r, (0...nil))
+ subclass = Class.new(Range)
+ assert_equal(r, subclass.new(0,nil))
end
def test_eql
@@ -159,71 +230,362 @@ class TestRange < Test::Unit::TestCase
assert_not_operator(r, :eql?, 0...1)
subclass = Class.new(Range)
assert_operator(r, :eql?, subclass.new(0,1))
+
+ r = (0..nil)
+ assert_operator(r, :eql?, r)
+ assert_operator(r, :eql?, 0..nil)
+ assert_not_operator(r, :eql?, 0)
+ assert_not_operator(r, :eql?, 0...nil)
+ subclass = Class.new(Range)
+ assert_operator(r, :eql?, subclass.new(0,nil))
end
def test_hash
assert_kind_of(Integer, (0..1).hash)
assert_equal((0..1).hash, (0..1).hash)
assert_not_equal((0..1).hash, (0...1).hash)
+ assert_equal((0..nil).hash, (0..nil).hash)
+ assert_not_equal((0..nil).hash, (0...nil).hash)
assert_kind_of(String, (0..1).hash.to_s)
end
- def test_step
- a = []
- (0..10).step {|x| a << x }
- assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a)
+ def test_step_numeric_range
+ # Fixnums, floats and all other numbers (like rationals) should behave exactly the same,
+ # but the behavior is implemented independently in 3 different branches of code,
+ # so we need to test each of them.
+ %i[to_i to_r to_f].each do |type|
+ conv = type.to_proc
- a = []
- (0..10).step(2) {|x| a << x }
- assert_equal([0, 2, 4, 6, 8, 10], a)
+ from = conv.(0)
+ to = conv.(10)
+ step = conv.(2)
- assert_raise(ArgumentError) { (0..10).step(-1) { } }
- assert_raise(ArgumentError) { (0..10).step(0) { } }
+ # finite
+ a = []
+ (from..to).step(step) {|x| a << x }
+ assert_equal([0, 2, 4, 6, 8, 10].map(&conv), a)
- a = []
- ("a" .. "z").step(2) {|x| a << x }
- assert_equal(%w(a c e g i k m o q s u w y), a)
+ a = []
+ (from...to).step(step) {|x| a << x }
+ assert_equal([0, 2, 4, 6, 8].map(&conv), a)
- a = []
- ("a" .. "z").step(2**32) {|x| a << x }
- assert_equal(["a"], a)
+ # Note: ArithmeticSequence behavior tested in its own test, but we also put it here
+ # to demonstrate the result is the same
+ assert_kind_of(Enumerator::ArithmeticSequence, (from..to).step(step))
+ assert_equal([0, 2, 4, 6, 8, 10].map(&conv), (from..to).step(step).to_a)
+ assert_kind_of(Enumerator::ArithmeticSequence, (from...to).step(step))
+ assert_equal([0, 2, 4, 6, 8].map(&conv), (from...to).step(step).to_a)
+
+ # endless
+ a = []
+ (from..).step(step) {|x| a << x; break if a.size == 5 }
+ assert_equal([0, 2, 4, 6, 8].map(&conv), a)
+
+ assert_kind_of(Enumerator::ArithmeticSequence, (from..).step(step))
+ assert_equal([0, 2, 4, 6, 8].map(&conv), (from..).step(step).take(5))
+
+ # beginless
+ assert_raise(ArgumentError) { (..to).step(step) {} }
+ assert_kind_of(Enumerator::ArithmeticSequence, (..to).step(step))
+ # This is inconsistent, but so it is implemented by ArithmeticSequence
+ assert_raise(TypeError) { (..to).step(step).to_a }
+
+ # negative step
+
+ a = []
+ (from..to).step(-step) {|x| a << x }
+ assert_equal([], a)
+ a = []
+ (from..-to).step(-step) {|x| a << x }
+ assert_equal([0, -2, -4, -6, -8, -10].map(&conv), a)
+
+ a = []
+ (from...-to).step(-step) {|x| a << x }
+ assert_equal([0, -2, -4, -6, -8].map(&conv), a)
+
+ a = []
+ (from...).step(-step) {|x| a << x; break if a.size == 5 }
+ assert_equal([0, -2, -4, -6, -8].map(&conv), a)
+
+ assert_kind_of(Enumerator::ArithmeticSequence, (from..to).step(-step))
+ assert_equal([], (from..to).step(-step).to_a)
+
+ assert_kind_of(Enumerator::ArithmeticSequence, (from..-to).step(-step))
+ assert_equal([0, -2, -4, -6, -8, -10].map(&conv), (from..-to).step(-step).to_a)
+
+ assert_kind_of(Enumerator::ArithmeticSequence, (from...-to).step(-step))
+ assert_equal([0, -2, -4, -6, -8].map(&conv), (from...-to).step(-step).to_a)
+
+ assert_kind_of(Enumerator::ArithmeticSequence, (from...).step(-step))
+ assert_equal([0, -2, -4, -6, -8].map(&conv), (from...).step(-step).take(5))
+
+ # zero step
+
+ assert_raise(ArgumentError) { (from..to).step(0) {} }
+ assert_raise(ArgumentError) { (from..to).step(0) }
+
+ # default step
+
+ a = []
+ (from..to).step {|x| a << x }
+ assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(&conv), a)
+
+ assert_kind_of(Enumerator::ArithmeticSequence, (from..to).step)
+ assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(&conv), (from..to).step.to_a)
+
+ # default + endless range
+ a = []
+ (from..).step {|x| a << x; break if a.size == 5 }
+ assert_equal([0, 1, 2, 3, 4].map(&conv), a)
+
+ assert_kind_of(Enumerator::ArithmeticSequence, (from..).step)
+ assert_equal([0, 1, 2, 3, 4].map(&conv), (from..).step.take(5))
+
+ # default + beginless range
+ assert_kind_of(Enumerator::ArithmeticSequence, (..to).step)
+
+ # step is not numeric
+
+ to = conv.(5)
+
+ val = Struct.new(:val)
+
+ a = []
+ assert_raise(TypeError) { (from..to).step(val.new(step)) {|x| a << x } }
+ assert_kind_of(Enumerator, (from..to).step(val.new(step)))
+ assert_raise(TypeError) { (from..to).step(val.new(step)).to_a }
+
+ # step is not numeric, but coercible
+ val = Struct.new(:val) do
+ def coerce(num) = [self.class.new(num), self]
+ def +(other) = self.class.new(val + other.val)
+ def <=>(other) = other.is_a?(self.class) ? val <=> other.val : val <=> other
+ end
+
+ a = []
+ (from..to).step(val.new(step)) {|x| a << x }
+ assert_equal([from, val.new(conv.(2)), val.new(conv.(4))], a)
+
+ assert_kind_of(Enumerator, (from..to).step(val.new(step)))
+ assert_equal([from, val.new(conv.(2)), val.new(conv.(4))], (from..to).step(val.new(step)).to_a)
+ end
+ end
+
+ def test_step_numeric_fixnum_boundary
a = []
(2**32-1 .. 2**32+1).step(2) {|x| a << x }
assert_equal([4294967295, 4294967297], a)
+
zero = (2**32).coerce(0).first
+ assert_raise(ArgumentError) { (2**32-1 .. 2**32+1).step(zero) }
assert_raise(ArgumentError) { (2**32-1 .. 2**32+1).step(zero) { } }
- o1 = Object.new
- o2 = Object.new
- def o1.<=>(x); -1; end
- def o2.<=>(x); 0; end
- assert_raise(TypeError) { (o1..o2).step(1) { } }
-
- class << o1; self; end.class_eval do
- define_method(:succ) { o2 }
- end
a = []
- (o1..o2).step(1) {|x| a << x }
- assert_equal([o1, o2], a)
+ (2**32-1 .. ).step(2) {|x| a << x; break if a.size == 2 }
+ assert_equal([4294967295, 4294967297], a)
+ max = RbConfig::LIMITS["FIXNUM_MAX"]
a = []
- (o1...o2).step(1) {|x| a << x }
- assert_equal([o1], a)
-
- assert_nothing_raised("[ruby-dev:34557]") { (0..2).step(0.5) {|x| } }
+ (max..).step {|x| a << x; break if a.size == 2 }
+ assert_equal([max, max+1], a)
a = []
- (0..2).step(0.5) {|x| a << x }
- assert_equal([0, 0.5, 1.0, 1.5, 2.0], a)
+ (max..).step(max) {|x| a << x; break if a.size == 4 }
+ assert_equal([max, 2*max, 3*max, 4*max], a)
+ end
+ def test_step_big_float
a = []
(0x40000000..0x40000002).step(0.5) {|x| a << x }
assert_equal([1073741824, 1073741824.5, 1073741825.0, 1073741825.5, 1073741826], a)
+ end
- o = Object.new
- def o.to_int() 1 end
- assert_nothing_raised("[ruby-dev:34558]") { (0..2).step(o) {|x| } }
+ def test_step_non_numeric_range
+ # finite
+ a = []
+ ('a'..'aaaa').step('a') { a << _1 }
+ assert_equal(%w[a aa aaa aaaa], a)
+
+ assert_kind_of(Enumerator, ('a'..'aaaa').step('a'))
+ assert_equal(%w[a aa aaa aaaa], ('a'..'aaaa').step('a').to_a)
+
+ a = []
+ ('a'...'aaaa').step('a') { a << _1 }
+ assert_equal(%w[a aa aaa], a)
+
+ assert_kind_of(Enumerator, ('a'...'aaaa').step('a'))
+ assert_equal(%w[a aa aaa], ('a'...'aaaa').step('a').to_a)
+
+ # endless
+ a = []
+ ('a'...).step('a') { a << _1; break if a.size == 3 }
+ assert_equal(%w[a aa aaa], a)
+
+ assert_kind_of(Enumerator, ('a'...).step('a'))
+ assert_equal(%w[a aa aaa], ('a'...).step('a').take(3))
+
+ # beginless
+ assert_raise(ArgumentError) { (...'aaa').step('a') {} }
+ assert_raise(ArgumentError) { (...'aaa').step('a') }
+
+ # step is not provided
+ assert_raise(ArgumentError) { (Time.new(2022)...Time.new(2023)).step }
+
+ # step is incompatible
+ assert_raise(TypeError) { (Time.new(2022)...Time.new(2023)).step('a') {} }
+ assert_raise(TypeError) { (Time.new(2022)...Time.new(2023)).step('a').to_a }
+
+ # step is compatible, but shouldn't convert into numeric domain:
+ a = []
+ (Time.utc(2022, 2, 24)...).step(1) { a << _1; break if a.size == 2 }
+ assert_equal([Time.utc(2022, 2, 24), Time.utc(2022, 2, 24, 0, 0, 1)], a)
+
+ a = []
+ (Time.utc(2022, 2, 24)...).step(1.0) { a << _1; break if a.size == 2 }
+ assert_equal([Time.utc(2022, 2, 24), Time.utc(2022, 2, 24, 0, 0, 1)], a)
+
+ a = []
+ (Time.utc(2022, 2, 24)...).step(1r) { a << _1; break if a.size == 2 }
+ assert_equal([Time.utc(2022, 2, 24), Time.utc(2022, 2, 24, 0, 0, 1)], a)
+
+ # step decreases the value
+ a = []
+ (Time.utc(2022, 2, 24)...).step(-1) { a << _1; break if a.size == 2 }
+ assert_equal([Time.utc(2022, 2, 24), Time.utc(2022, 2, 23, 23, 59, 59)], a)
+
+ a = []
+ (Time.utc(2022, 2, 24)...Time.utc(2022, 2, 23, 23, 59, 57)).step(-1) { a << _1 }
+ assert_equal([Time.utc(2022, 2, 24), Time.utc(2022, 2, 23, 23, 59, 59),
+ Time.utc(2022, 2, 23, 23, 59, 58)], a)
+
+ a = []
+ (Time.utc(2022, 2, 24)..Time.utc(2022, 2, 23, 23, 59, 57)).step(-1) { a << _1 }
+ assert_equal([Time.utc(2022, 2, 24), Time.utc(2022, 2, 23, 23, 59, 59),
+ Time.utc(2022, 2, 23, 23, 59, 58), Time.utc(2022, 2, 23, 23, 59, 57)], a)
+
+ # step decreases, but the range is forward-directed:
+ a = []
+ (Time.utc(2022, 2, 24)...Time.utc(2022, 2, 24, 01, 01, 03)).step(-1) { a << _1 }
+ assert_equal([], a)
+ end
+
+ def test_step_string_legacy
+ # finite
+ a = []
+ ('a'..'g').step(2) { a << _1 }
+ assert_equal(%w[a c e g], a)
+
+ assert_kind_of(Enumerator, ('a'..'g').step(2))
+ assert_equal(%w[a c e g], ('a'..'g').step(2).to_a)
+
+ a = []
+ ('a'...'g').step(2) { a << _1 }
+ assert_equal(%w[a c e], a)
+
+ assert_kind_of(Enumerator, ('a'...'g').step(2))
+ assert_equal(%w[a c e], ('a'...'g').step(2).to_a)
+
+ # endless
+ a = []
+ ('a'...).step(2) { a << _1; break if a.size == 3 }
+ assert_equal(%w[a c e], a)
+
+ assert_kind_of(Enumerator, ('a'...).step(2))
+ assert_equal(%w[a c e], ('a'...).step(2).take(3))
+
+ # beginless
+ assert_raise(ArgumentError) { (...'g').step(2) {} }
+ assert_raise(ArgumentError) { (...'g').step(2) }
+
+ # step is not provided
+ a = []
+ ('a'..'d').step { a << _1 }
+ assert_equal(%w[a b c d], a)
+
+ assert_kind_of(Enumerator, ('a'..'d').step)
+ assert_equal(%w[a b c d], ('a'..'d').step.to_a)
+
+ a = []
+ ('a'...'d').step { a << _1 }
+ assert_equal(%w[a b c], a)
+
+ assert_kind_of(Enumerator, ('a'...'d').step)
+ assert_equal(%w[a b c], ('a'...'d').step.to_a)
+
+ # endless
+ a = []
+ ('a'...).step { a << _1; break if a.size == 3 }
+ assert_equal(%w[a b c], a)
+
+ assert_kind_of(Enumerator, ('a'...).step)
+ assert_equal(%w[a b c], ('a'...).step.take(3))
+ end
+
+ def test_step_symbol_legacy
+ # finite
+ a = []
+ (:a..:g).step(2) { a << _1 }
+ assert_equal(%i[a c e g], a)
+
+ assert_kind_of(Enumerator, (:a..:g).step(2))
+ assert_equal(%i[a c e g], (:a..:g).step(2).to_a)
+
+ a = []
+ (:a...:g).step(2) { a << _1 }
+ assert_equal(%i[a c e], a)
+
+ assert_kind_of(Enumerator, (:a...:g).step(2))
+ assert_equal(%i[a c e], (:a...:g).step(2).to_a)
+
+ # endless
+ a = []
+ (:a...).step(2) { a << _1; break if a.size == 3 }
+ assert_equal(%i[a c e], a)
+
+ assert_kind_of(Enumerator, (:a...).step(2))
+ assert_equal(%i[a c e], (:a...).step(2).take(3))
+
+ # beginless
+ assert_raise(ArgumentError) { (...:g).step(2) {} }
+ assert_raise(ArgumentError) { (...:g).step(2) }
+
+ # step is not provided
+ a = []
+ (:a..:d).step { a << _1 }
+ assert_equal(%i[a b c d], a)
+
+ assert_kind_of(Enumerator, (:a..:d).step)
+ assert_equal(%i[a b c d], (:a..:d).step.to_a)
+
+ a = []
+ (:a...:d).step { a << _1 }
+ assert_equal(%i[a b c], a)
+
+ assert_kind_of(Enumerator, (:a...:d).step)
+ assert_equal(%i[a b c], (:a...:d).step.to_a)
+
+ # endless
+ a = []
+ (:a...).step { a << _1; break if a.size == 3 }
+ assert_equal(%i[a b c], a)
+
+ assert_kind_of(Enumerator, (:a...).step)
+ assert_equal(%i[a b c], (:a...).step.take(3))
+ end
+
+ def test_step_bug15537
+ assert_equal([10.0, 9.0, 8.0, 7.0], (10 ..).step(-1.0).take(4))
+ assert_equal([10.0, 9.0, 8.0, 7.0], (10.0 ..).step(-1).take(4))
+ end
+
+ def test_percent_step
+ aseq = (1..10) % 2
+ assert_equal(Enumerator::ArithmeticSequence, aseq.class)
+ assert_equal(1, aseq.begin)
+ assert_equal(10, aseq.end)
+ assert_equal(2, aseq.step)
+ assert_equal([1, 3, 5, 7, 9], aseq.to_a)
end
def test_step_ruby_core_35753
@@ -235,11 +597,31 @@ class TestRange < Test::Unit::TestCase
assert_equal(4, (1.0...5.6).step(1.5).to_a.size)
end
+ def test_step_with_nonnumeric_endpoint
+ num = Data.define(:value) do
+ def coerce(o); [o, 100]; end
+ def <=>(o) value<=>o; end
+ def +(o) with(value: value + o) end
+ end
+ i = num.new(100)
+
+ assert_equal([100], (100..100).step(10).to_a)
+ assert_equal([], (100...100).step(10).to_a)
+ assert_equal([100], (100..i).step(10).to_a)
+ assert_equal([i], (i..100).step(10).to_a)
+ assert_equal([], (100...i).step(10).to_a)
+ assert_equal([], (i...100).step(10).to_a)
+ end
+
def test_each
a = []
(0..10).each {|x| a << x }
assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a)
+ a = []
+ (0..).each {|x| a << x; break if a.size == 10 }
+ assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], a)
+
o1 = Object.new
o2 = Object.new
def o1.setcmp(v) @cmpresult = v end
@@ -280,55 +662,304 @@ class TestRange < Test::Unit::TestCase
a = []
r2.each {|x| a << x }
assert_equal([], a)
+
+ o = Object.new
+ class << o
+ def to_str() "a" end
+ def <=>(other) to_str <=> other end
+ end
+
+ a = []
+ (o.."c").each {|x| a << x}
+ assert_equal(["a", "b", "c"], a)
+ a = []
+ (o..).each {|x| a << x; break if a.size >= 3}
+ assert_equal(["a", "b", "c"], a)
+ end
+
+ def test_each_with_succ
+ c = Struct.new(:i) do
+ def succ; self.class.new(i+1); end
+ def <=>(other) i <=> other.i;end
+ end.new(0)
+
+ result = []
+ (c..c.succ).each do |d|
+ result << d.i
+ end
+ assert_equal([0, 1], result)
+
+ result = []
+ (c..).each do |d|
+ result << d.i
+ break if d.i >= 4
+ end
+ assert_equal([0, 1, 2, 3, 4], result)
+ end
+
+ def test_reverse_each
+ a = []
+ (1..3).reverse_each {|x| a << x }
+ assert_equal([3, 2, 1], a)
+
+ a = []
+ (1...3).reverse_each {|x| a << x }
+ assert_equal([2, 1], a)
+
+ fmax = RbConfig::LIMITS['FIXNUM_MAX']
+ fmin = RbConfig::LIMITS['FIXNUM_MIN']
+
+ a = []
+ (fmax+1..fmax+3).reverse_each {|x| a << x }
+ assert_equal([fmax+3, fmax+2, fmax+1], a)
+
+ a = []
+ (fmax+1...fmax+3).reverse_each {|x| a << x }
+ assert_equal([fmax+2, fmax+1], a)
+
+ a = []
+ (fmax-1..fmax+1).reverse_each {|x| a << x }
+ assert_equal([fmax+1, fmax, fmax-1], a)
+
+ a = []
+ (fmax-1...fmax+1).reverse_each {|x| a << x }
+ assert_equal([fmax, fmax-1], a)
+
+ a = []
+ (fmin-1..fmin+1).reverse_each{|x| a << x }
+ assert_equal([fmin+1, fmin, fmin-1], a)
+
+ a = []
+ (fmin-1...fmin+1).reverse_each{|x| a << x }
+ assert_equal([fmin, fmin-1], a)
+
+ a = []
+ (fmin-3..fmin-1).reverse_each{|x| a << x }
+ assert_equal([fmin-1, fmin-2, fmin-3], a)
+
+ a = []
+ (fmin-3...fmin-1).reverse_each{|x| a << x }
+ assert_equal([fmin-2, fmin-3], a)
+
+ a = []
+ ("a".."c").reverse_each {|x| a << x }
+ assert_equal(["c", "b", "a"], a)
+ end
+
+ def test_reverse_each_for_beginless_range
+ fmax = RbConfig::LIMITS['FIXNUM_MAX']
+ fmin = RbConfig::LIMITS['FIXNUM_MIN']
+
+ a = []
+ (..3).reverse_each {|x| a << x; break if x <= 0 }
+ assert_equal([3, 2, 1, 0], a)
+
+ a = []
+ (...3).reverse_each {|x| a << x; break if x <= 0 }
+ assert_equal([2, 1, 0], a)
+
+ a = []
+ (..fmax+1).reverse_each {|x| a << x; break if x <= fmax-1 }
+ assert_equal([fmax+1, fmax, fmax-1], a)
+
+ a = []
+ (...fmax+1).reverse_each {|x| a << x; break if x <= fmax-1 }
+ assert_equal([fmax, fmax-1], a)
+
+ a = []
+ (..fmin+1).reverse_each {|x| a << x; break if x <= fmin-1 }
+ assert_equal([fmin+1, fmin, fmin-1], a)
+
+ a = []
+ (...fmin+1).reverse_each {|x| a << x; break if x <= fmin-1 }
+ assert_equal([fmin, fmin-1], a)
+
+ a = []
+ (..fmin-1).reverse_each {|x| a << x; break if x <= fmin-3 }
+ assert_equal([fmin-1, fmin-2, fmin-3], a)
+
+ a = []
+ (...fmin-1).reverse_each {|x| a << x; break if x <= fmin-3 }
+ assert_equal([fmin-2, fmin-3], a)
+ end
+
+ def test_reverse_each_for_endless_range
+ assert_raise(TypeError) { (1..).reverse_each {} }
+
+ enum = nil
+ assert_nothing_raised { enum = (1..).reverse_each }
+ assert_raise(TypeError) { enum.each {} }
+ end
+
+ def test_reverse_each_for_single_point_range
+ fmin = RbConfig::LIMITS['FIXNUM_MIN']
+ fmax = RbConfig::LIMITS['FIXNUM_MAX']
+
+ values = [fmin*2, fmin-1, fmin, 0, fmax, fmax+1, fmax*2]
+
+ values.each do |b|
+ r = b..b
+ a = []
+ r.reverse_each {|x| a << x }
+ assert_equal([b], a, "failed on #{r}")
+
+ r = b...b+1
+ a = []
+ r.reverse_each {|x| a << x }
+ assert_equal([b], a, "failed on #{r}")
+ end
+ end
+
+ def test_reverse_each_for_empty_range
+ fmin = RbConfig::LIMITS['FIXNUM_MIN']
+ fmax = RbConfig::LIMITS['FIXNUM_MAX']
+
+ values = [fmin*2, fmin-1, fmin, 0, fmax, fmax+1, fmax*2]
+
+ values.each do |b|
+ r = b..b-1
+ a = []
+ r.reverse_each {|x| a << x }
+ assert_equal([], a, "failed on #{r}")
+ end
+
+ values.repeated_permutation(2).to_a.product([true, false]).each do |(b, e), excl|
+ next unless b > e || (b == e && excl)
+
+ r = Range.new(b, e, excl)
+ a = []
+ r.reverse_each {|x| a << x }
+ assert_equal([], a, "failed on #{r}")
+ end
+ end
+
+ def test_reverse_each_with_no_block
+ enum = (1..5).reverse_each
+ assert_equal 5, enum.size
+
+ a = []
+ enum.each {|x| a << x }
+ assert_equal [5, 4, 3, 2, 1], a
+ end
+
+ def test_reverse_each_size
+ assert_equal(3, (1..3).reverse_each.size)
+ assert_equal(3, (1..3.3).reverse_each.size)
+ assert_raise(TypeError) { (1..nil).reverse_each.size }
+ assert_raise(TypeError) { (1.1..3).reverse_each.size }
+ assert_raise(TypeError) { (1.1..3.3).reverse_each.size }
+ assert_raise(TypeError) { (1.1..nil).reverse_each.size }
+ assert_equal(Float::INFINITY, (..3).reverse_each.size)
+ assert_raise(TypeError) { (nil..3.3).reverse_each.size }
+ assert_raise(TypeError) { (nil..nil).reverse_each.size }
+
+ assert_equal(2, (1...3).reverse_each.size)
+ assert_equal(3, (1...3.3).reverse_each.size)
+
+ assert_equal(nil, ('a'..'z').reverse_each.size)
+ assert_raise(TypeError) { ('a'..).reverse_each.size }
+ assert_raise(TypeError) { (..'z').reverse_each.size }
end
def test_begin_end
assert_equal(0, (0..1).begin)
assert_equal(1, (0..1).end)
assert_equal(1, (0...1).end)
+ assert_equal(0, (0..nil).begin)
+ assert_equal(nil, (0..nil).end)
+ assert_equal(nil, (0...nil).end)
end
def test_first_last
assert_equal([0, 1, 2], (0..10).first(3))
assert_equal([8, 9, 10], (0..10).last(3))
+ assert_equal([8, 9, 10], (nil..10).last(3))
assert_equal(0, (0..10).first)
assert_equal(10, (0..10).last)
+ assert_equal(10, (nil..10).last)
assert_equal("a", ("a".."c").first)
assert_equal("c", ("a".."c").last)
assert_equal(0, (2..0).last)
assert_equal([0, 1, 2], (0...10).first(3))
assert_equal([7, 8, 9], (0...10).last(3))
+ assert_equal([7, 8, 9], (nil...10).last(3))
assert_equal(0, (0...10).first)
assert_equal(10, (0...10).last)
+ assert_equal(10, (nil...10).last)
assert_equal("a", ("a"..."c").first)
assert_equal("c", ("a"..."c").last)
assert_equal(0, (2...0).last)
+
+ assert_equal([0, 1, 2], (0..nil).first(3))
+ assert_equal(0, (0..nil).first)
+ assert_equal("a", ("a"..nil).first)
+ assert_raise(RangeError) { (0..nil).last }
+ assert_raise(RangeError) { (0..nil).last(3) }
+ assert_raise(RangeError) { (nil..0).first }
+ assert_raise(RangeError) { (nil..0).first(3) }
+
+ assert_equal([0, 1, 2], (0..10).first(3.0))
+ assert_equal([8, 9, 10], (0..10).last(3.0))
+ assert_raise(TypeError) { (0..10).first("3") }
+ assert_raise(TypeError) { (0..10).last("3") }
+ class << (o = Object.new)
+ def to_int; 3; end
+ end
+ assert_equal([0, 1, 2], (0..10).first(o))
+ assert_equal([8, 9, 10], (0..10).last(o))
+
+ assert_raise(ArgumentError) { (0..10).first(-1) }
+ assert_raise(ArgumentError) { (0..10).last(-1) }
+ end
+
+ def test_last_with_redefine_each
+ assert_in_out_err([], <<-'end;', ['true'], [])
+ class Range
+ remove_method :each
+ def each(&b)
+ [1, 2, 3, 4, 5].each(&b)
+ end
+ end
+ puts [3, 4, 5] == (1..10).last(3)
+ end;
end
def test_to_s
assert_equal("0..1", (0..1).to_s)
assert_equal("0...1", (0...1).to_s)
-
- bug11767 = '[ruby-core:71811] [Bug #11767]'
- assert_predicate(("0".taint.."1").to_s, :tainted?, bug11767)
- assert_predicate(("0".."1".taint).to_s, :tainted?, bug11767)
- assert_predicate(("0".."1").taint.to_s, :tainted?, bug11767)
+ assert_equal("0..", (0..nil).to_s)
+ assert_equal("0...", (0...nil).to_s)
end
def test_inspect
assert_equal("0..1", (0..1).inspect)
assert_equal("0...1", (0...1).inspect)
-
- bug11767 = '[ruby-core:71811] [Bug #11767]'
- assert_predicate(("0".taint.."1").inspect, :tainted?, bug11767)
- assert_predicate(("0".."1".taint).inspect, :tainted?, bug11767)
- assert_predicate(("0".."1").taint.inspect, :tainted?, bug11767)
+ assert_equal("0..", (0..nil).inspect)
+ assert_equal("0...", (0...nil).inspect)
+ assert_equal("..1", (nil..1).inspect)
+ assert_equal("...1", (nil...1).inspect)
+ assert_equal("nil..nil", (nil..nil).inspect)
+ assert_equal("nil...nil", (nil...nil).inspect)
end
def test_eqq
assert_operator(0..10, :===, 5)
assert_not_operator(0..10, :===, 11)
+ assert_operator(5..nil, :===, 11)
+ assert_not_operator(5..nil, :===, 0)
+ assert_operator(nil..10, :===, 0)
+ assert_operator(nil..nil, :===, 0)
+ assert_operator(nil..nil, :===, Object.new)
+ assert_not_operator(0..10, :===, 0..10)
+ end
+
+ def test_eqq_string
+ assert_operator('A'..'Z', :===, 'ANA')
+ assert_not_operator('A'..'Z', :===, 'ana')
+ assert_operator('A'.., :===, 'ANA')
+ assert_operator(..'Z', :===, 'ANA')
+ assert_operator(nil..nil, :===, 'ANA')
end
def test_eqq_time
@@ -336,6 +967,8 @@ class TestRange < Test::Unit::TestCase
t = Time.now
assert_nothing_raised(TypeError, bug11113) {
assert_operator(t..(t+10), :===, t+5)
+ assert_operator(t.., :===, t+5)
+ assert_not_operator(t.., :===, t-5)
}
end
@@ -363,13 +996,54 @@ class TestRange < Test::Unit::TestCase
assert_operator(c.new(0)..c.new(10), :===, c.new(5), bug12003)
end
+ def test_eqq_unbounded_ruby_bug_19864
+ t1 = Date.today
+ t2 = t1 + 1
+ assert_equal(true, (..t1) === t1)
+ assert_equal(false, (..t1) === t2)
+ assert_equal(true, (..t2) === t1)
+ assert_equal(true, (..t2) === t2)
+ assert_equal(false, (...t1) === t1)
+ assert_equal(false, (...t1) === t2)
+ assert_equal(true, (...t2) === t1)
+ assert_equal(false, (...t2) === t2)
+
+ assert_equal(true, (t1..) === t1)
+ assert_equal(true, (t1..) === t2)
+ assert_equal(false, (t2..) === t1)
+ assert_equal(true, (t2..) === t2)
+ assert_equal(true, (t1...) === t1)
+ assert_equal(true, (t1...) === t2)
+ assert_equal(false, (t2...) === t1)
+ assert_equal(true, (t2...) === t2)
+ end
+
+ def test_eqq_non_iteratable
+ k = Class.new do
+ include Comparable
+ attr_reader :i
+ def initialize(i) @i = i; end
+ def <=>(o); i <=> o.i; end
+ end
+ assert_operator(k.new(0)..k.new(2), :===, k.new(1))
+ end
+
def test_include
assert_include("a".."z", "c")
assert_not_include("a".."z", "5")
assert_include("a"..."z", "y")
assert_not_include("a"..."z", "z")
assert_not_include("a".."z", "cc")
+ assert_raise(TypeError) {("a"..).include?("c")}
+ assert_raise(TypeError) {("a"..).include?("5")}
+
assert_include(0...10, 5)
+ assert_include(5..., 10)
+ assert_not_include(5..., 0)
+ assert_raise(TypeError) {(.."z").include?("z")}
+ assert_raise(TypeError) {(..."z").include?("z")}
+ assert_include(..10, 10)
+ assert_not_include(...10, 10)
end
def test_cover
@@ -378,6 +1052,89 @@ class TestRange < Test::Unit::TestCase
assert_operator("a"..."z", :cover?, "y")
assert_not_operator("a"..."z", :cover?, "z")
assert_operator("a".."z", :cover?, "cc")
+ assert_not_operator(5..., :cover?, 0)
+ assert_not_operator(5..., :cover?, "a")
+ assert_operator(5.., :cover?, 10)
+
+ assert_operator(2..5, :cover?, 2..5)
+ assert_operator(2...6, :cover?, 2...6)
+ assert_operator(2...6, :cover?, 2..5)
+ assert_operator(2..5, :cover?, 2...6)
+ assert_operator(2..5, :cover?, 2..4)
+ assert_operator(2..5, :cover?, 2...4)
+ assert_operator(2..5, :cover?, 2...5)
+ assert_operator(2..5, :cover?, 3..5)
+ assert_operator(2..5, :cover?, 3..4)
+ assert_operator(2..5, :cover?, 3...6)
+ assert_operator(2...6, :cover?, 2...5)
+ assert_operator(2...6, :cover?, 2..5)
+ assert_operator(2..6, :cover?, 2...6)
+ assert_operator(2.., :cover?, 2..)
+ assert_operator(2.., :cover?, 3..)
+ assert_operator(1.., :cover?, 1..10)
+ assert_operator(..2, :cover?, ..2)
+ assert_operator(..2, :cover?, ..1)
+ assert_operator(..2, :cover?, 0..1)
+ assert_operator(2.0..5.0, :cover?, 2..3)
+ assert_operator(2..5, :cover?, 2.0..3.0)
+ assert_operator(2..5, :cover?, 2.0...3.0)
+ assert_operator(2..5, :cover?, 2.0...5.0)
+ assert_operator(2.0..5.0, :cover?, 2.0...3.0)
+ assert_operator(2.0..5.0, :cover?, 2.0...5.0)
+ assert_operator('aa'..'zz', :cover?, 'aa'...'bb')
+
+ assert_not_operator(2..5, :cover?, 1..5)
+ assert_not_operator(2...6, :cover?, 1..5)
+ assert_not_operator(2..5, :cover?, 1...6)
+ assert_not_operator(1..3, :cover?, 1...6)
+ assert_not_operator(2..5, :cover?, 2..6)
+ assert_not_operator(2...6, :cover?, 2..6)
+ assert_not_operator(2...6, :cover?, 2...7)
+ assert_not_operator(2..3, :cover?, 1..4)
+ assert_not_operator(1..2, :cover?, 1.0..3.0)
+ assert_not_operator(1.0..2.9, :cover?, 1.0..3.0)
+ assert_not_operator(1..2, :cover?, 4..3)
+ assert_not_operator(2..1, :cover?, 1..2)
+ assert_not_operator(1...2, :cover?, 1...3)
+ assert_not_operator(2.., :cover?, 1..)
+ assert_not_operator(2.., :cover?, 1..10)
+ assert_not_operator(2.., :cover?, ..10)
+ assert_not_operator(1..10, :cover?, 1..)
+ assert_not_operator(1..10, :cover?, ..1)
+ assert_not_operator(1..5, :cover?, 3..2)
+ assert_not_operator(1..10, :cover?, 3...2)
+ assert_not_operator(1..10, :cover?, 3...3)
+ assert_not_operator('aa'..'zz', :cover?, 'aa'...'zzz')
+ assert_not_operator(1..10, :cover?, 1...10.1)
+
+ assert_operator(..2, :cover?, 1)
+ assert_operator(..2, :cover?, 2)
+ assert_not_operator(..2, :cover?, 3)
+ assert_not_operator(...2, :cover?, 2)
+ assert_not_operator(..2, :cover?, "2")
+ assert_operator(..2, :cover?, ..2)
+ assert_operator(..2, :cover?, ...2)
+ assert_not_operator(..2, :cover?, .."2")
+ assert_not_operator(...2, :cover?, ..2)
+
+ assert_not_operator(2.., :cover?, 1)
+ assert_operator(2.., :cover?, 2)
+ assert_operator(2..., :cover?, 3)
+ assert_operator(2.., :cover?, 2)
+ assert_not_operator(2.., :cover?, "2")
+ assert_operator(2.., :cover?, 2..)
+ assert_operator(2.., :cover?, 2...)
+ assert_not_operator(2.., :cover?, "2"..)
+ assert_not_operator(2..., :cover?, 2..)
+ assert_operator(2..., :cover?, 3...)
+ assert_not_operator(2..., :cover?, 3..)
+ assert_not_operator(3.., :cover?, 2..)
+
+ assert_operator(nil..., :cover?, Object.new)
+ assert_operator(nil..., :cover?, nil...)
+ assert_operator(nil.., :cover?, nil...)
+ assert_not_operator(nil..., :cover?, nil..)
+ assert_not_operator(nil..., :cover?, 1..)
end
def test_beg_len
@@ -450,11 +1207,38 @@ class TestRange < Test::Unit::TestCase
end
def test_size
- assert_equal 42, (1..42).size
- assert_equal 41, (1...42).size
- assert_equal 6, (1...6.3).size
- assert_equal 5, (1.1...6).size
- assert_equal 42, (1..42).each.size
+ Enumerator.product([:to_i, :to_f, :to_r].repeated_permutation(2), [1, 10], [5, 5.5], [true, false]) do |(m1, m2), beg, ende, exclude_end|
+ r = Range.new(beg.send(m1), ende.send(m2), exclude_end)
+ iterable = true
+ yielded = []
+ begin
+ r.each { yielded << _1 }
+ rescue TypeError
+ iterable = false
+ end
+
+ if iterable
+ assert_equal(yielded.size, r.size, "failed on #{r}")
+ assert_equal(yielded.size, r.each.size, "failed on #{r}")
+ else
+ assert_raise(TypeError, "failed on #{r}") { r.size }
+ assert_raise(TypeError, "failed on #{r}") { r.each.size }
+ end
+ end
+
+ assert_nil ("a"..."z").size
+
+ assert_equal Float::INFINITY, (1..).size
+ assert_raise(TypeError) { (1.0..).size }
+ assert_raise(TypeError) { (1r..).size }
+ assert_nil ("a"..).size
+
+ assert_raise(TypeError) { (..1).size }
+ assert_raise(TypeError) { (..1.0).size }
+ assert_raise(TypeError) { (..1r).size }
+ assert_raise(TypeError) { (..'z').size }
+
+ assert_raise(TypeError) { (nil...nil).size }
end
def test_bsearch_typechecks_return_values
@@ -478,9 +1262,6 @@ class TestRange < Test::Unit::TestCase
assert_raise(TypeError) {
(Rational(-1,2)..Rational(9,4)).bsearch
}
- assert_raise(TypeError) {
- (BigDecimal('0.5')..BigDecimal('2.25')).bsearch
- }
end
def test_bsearch_for_fixnum
@@ -496,6 +1277,9 @@ class TestRange < Test::Unit::TestCase
ary = [0, 100, 100, 100, 200]
assert_equal(1, (0...ary.size).bsearch {|i| ary[i] >= 100 })
+
+ assert_equal(1_000_001, (0...).bsearch {|i| i > 1_000_000 })
+ assert_equal( -999_999, (...0).bsearch {|i| i > -1_000_000 })
end
def test_bsearch_for_float
@@ -547,6 +1331,9 @@ class TestRange < Test::Unit::TestCase
assert_in_delta(1.0, (0.0..inf).bsearch {|x| Math.log(x) >= 0 })
assert_in_delta(7.0, (0.0..10).bsearch {|x| 7.0 - x })
+
+ assert_equal( 1_000_000.0.next_float, (0.0..).bsearch {|x| x > 1_000_000 })
+ assert_equal(-1_000_000.0.next_float, (..0.0).bsearch {|x| x > -1_000_000 })
end
def check_bsearch_values(range, search, a)
@@ -649,6 +1436,11 @@ class TestRange < Test::Unit::TestCase
assert_equal(bignum + 0, (bignum...bignum+ary.size).bsearch {|i| true })
assert_equal(nil, (bignum...bignum+ary.size).bsearch {|i| false })
+ assert_equal(bignum * 2 + 1, (0...).bsearch {|i| i > bignum * 2 })
+ assert_equal(bignum * 2 + 1, (bignum...).bsearch {|i| i > bignum * 2 })
+ assert_equal(-bignum * 2 + 1, (...0).bsearch {|i| i > -bignum * 2 })
+ assert_equal(-bignum * 2 + 1, (...-bignum).bsearch {|i| i > -bignum * 2 })
+
assert_raise(TypeError) { ("a".."z").bsearch {} }
end
@@ -659,4 +1451,101 @@ class TestRange < Test::Unit::TestCase
end
(a.."c").each {|x, &b| assert_nil(b)}
end
+
+ def test_to_a
+ assert_equal([1,2,3,4,5], (1..5).to_a)
+ assert_equal([1,2,3,4], (1...5).to_a)
+ 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
+
+ def test_count
+ assert_equal 42, (1..42).count
+ assert_equal 41, (1...42).count
+ assert_equal 0, (42..1).count
+ assert_equal 0, (42...1).count
+ assert_equal 2**100, (1..2**100).count
+ assert_equal 6, (1...6.3).count
+ assert_equal 4, ('a'..'d').count
+ assert_equal 3, ('a'...'d').count
+
+ assert_equal(Float::INFINITY, (1..).count)
+ assert_equal(Float::INFINITY, (..1).count)
+ end
+
+ def test_overlap?
+ assert_not_operator(0..2, :overlap?, -2..-1)
+ assert_not_operator(0..2, :overlap?, -2...0)
+ assert_operator(0..2, :overlap?, -1..0)
+ assert_operator(0..2, :overlap?, 1..2)
+ assert_operator(0..2, :overlap?, 2..3)
+ assert_not_operator(0..2, :overlap?, 3..4)
+ assert_not_operator(0...2, :overlap?, 2..3)
+
+ assert_operator(..0, :overlap?, -1..0)
+ assert_operator(...0, :overlap?, -1..0)
+ assert_operator(..0, :overlap?, 0..1)
+ assert_operator(..0, :overlap?, ..1)
+ assert_not_operator(..0, :overlap?, 1..2)
+ assert_not_operator(...0, :overlap?, 0..1)
+
+ assert_not_operator(0.., :overlap?, -2..-1)
+ assert_not_operator(0.., :overlap?, ...0)
+ assert_operator(0.., :overlap?, -1..0)
+ assert_operator(0.., :overlap?, ..0)
+ assert_operator(0.., :overlap?, 0..1)
+ assert_operator(0.., :overlap?, 1..2)
+ assert_operator(0.., :overlap?, 1..)
+
+ assert_not_operator((1..3), :overlap?, ('a'..'d'))
+ assert_not_operator((1..), :overlap?, ('a'..))
+ assert_not_operator((..1), :overlap?, (..'a'))
+
+ assert_raise(TypeError) { (0..).overlap?(1) }
+ assert_raise(TypeError) { (0..).overlap?(nil) }
+
+ assert_operator((1..3), :overlap?, (2..4))
+ assert_operator((1...3), :overlap?, (2..3))
+ assert_operator((2..3), :overlap?, (1..2))
+ assert_operator((..3), :overlap?, (3..))
+ assert_operator((nil..nil), :overlap?, (3..))
+ assert_operator((nil...nil), :overlap?, (nil..))
+ assert_operator((nil..nil), :overlap?, (..3))
+ assert_operator((..3), :overlap?, (nil..nil))
+
+ assert_raise(TypeError) { (1..3).overlap?(1) }
+
+ assert_not_operator((1..2), :overlap?, (2...2))
+ assert_not_operator((2...2), :overlap?, (1..2))
+
+ assert_not_operator((4..1), :overlap?, (2..3))
+ assert_not_operator((4..1), :overlap?, (..3))
+ assert_not_operator((4..1), :overlap?, (2..))
+
+ assert_not_operator((1..4), :overlap?, (3..2))
+ assert_not_operator((..4), :overlap?, (3..2))
+ assert_not_operator((1..), :overlap?, (3..2))
+
+ assert_not_operator((4..5), :overlap?, (2..3))
+ assert_not_operator((4..5), :overlap?, (2...4))
+
+ assert_not_operator((1..2), :overlap?, (3..4))
+ assert_not_operator((1...3), :overlap?, (3..4))
+
+ assert_not_operator((4..5), :overlap?, (2..3))
+ assert_not_operator((4..5), :overlap?, (2...4))
+
+ assert_not_operator((1..2), :overlap?, (3..4))
+ assert_not_operator((1...3), :overlap?, (3..4))
+ assert_not_operator((...3), :overlap?, (3..))
+ end
end
diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb
index 2152486742..e0edbde463 100644
--- a/test/ruby/test_rational.rb
+++ b/test/ruby/test_rational.rb
@@ -110,16 +110,56 @@ class Rational_Test < Test::Unit::TestCase
assert_equal(Rational(3),Rational('3'))
assert_equal(Rational(1),Rational('3.0','3.0'))
assert_equal(Rational(1),Rational('3/3','3/3'))
+ assert_equal(Rational(111, 1), Rational('1.11e+2'))
assert_equal(Rational(111, 10), Rational('1.11e+1'))
assert_equal(Rational(111, 10), Rational('1.11e1'))
assert_equal(Rational(111, 100), Rational('1.11e0'))
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?)
}
+
assert_raise(TypeError){Rational(Object.new)}
+ assert_raise(TypeError){Rational(Object.new, Object.new)}
+ assert_raise(TypeError){Rational(1, Object.new)}
+
+ bug12485 = '[ruby-core:75995] [Bug #12485]'
+ o = Object.new
+ def o.to_int; 1; end
+ assert_equal(1, Rational(o, 1), bug12485)
+ assert_equal(1, Rational(1, o), bug12485)
+ assert_equal(1, Rational(o, o), bug12485)
+
+ o = Object.new
+ def o.to_r; 1/42r; end
+ assert_equal(1/42r, Rational(o))
+ assert_equal(1/84r, Rational(o, 2))
+ assert_equal(42, Rational(1, o))
+ assert_equal(1, Rational(o, o))
+
+ o = Object.new
+ def o.to_r; nil; end
+ assert_raise(TypeError) { Rational(o) }
+ assert_raise(TypeError) { Rational(o, 2) }
+ assert_raise(TypeError) { Rational(1, o) }
+ assert_raise(TypeError) { Rational(o, o) }
+
+ o = Object.new
+ def o.to_r; raise; end
+ assert_raise(RuntimeError) { Rational(o) }
+ assert_raise(RuntimeError) { Rational(o, 2) }
+ assert_raise(RuntimeError) { Rational(1, o) }
+ assert_raise(RuntimeError) { Rational(o, o) }
+
assert_raise(ArgumentError){Rational()}
assert_raise(ArgumentError){Rational(1,2,3)}
@@ -129,6 +169,14 @@ class Rational_Test < Test::Unit::TestCase
if (1.0/0).infinite?
assert_raise(FloatDomainError){Rational(1.0/0)}
end
+
+ bug16518 = "[ruby-core:96942] [Bug #16518]"
+ cls = Class.new(Numeric) do
+ def /(y); 42; end
+ def to_r; 1r; end
+ def to_int; 1; end
+ end
+ assert_equal(1/2r, Rational(cls.new, 2), bug16518)
end
def test_attr
@@ -566,9 +614,16 @@ class Rational_Test < Test::Unit::TestCase
assert_equal([Rational(2.2),Rational(1)], Rational(1).coerce(2.2))
assert_equal([Rational(2),Rational(1)], Rational(1).coerce(Rational(2)))
- assert_nothing_raised(TypeError, '[Bug #5020] [ruby-devl:44088]') do
+ assert_nothing_raised(TypeError, '[Bug #5020] [ruby-dev:44088]') do
Rational(1,2).coerce(Complex(1,1))
end
+
+ assert_raise(ZeroDivisionError) do
+ 1 / 0r.coerce(0+0i)[0]
+ end
+ assert_raise(ZeroDivisionError) do
+ 1 / 0r.coerce(0.0+0i)[0]
+ end
end
class ObjectX
@@ -772,6 +827,8 @@ class Rational_Test < Test::Unit::TestCase
ng[5, 1, '5/_3']
ng[5, 3, '5/3_']
ng[5, 3, '5/3x']
+
+ ng[5, 1, '5/-3']
end
def test_parse_zero_denominator
@@ -779,6 +836,54 @@ class Rational_Test < Test::Unit::TestCase
assert_raise(ZeroDivisionError) {Rational("1/0")}
end
+ def test_Rational_with_invalid_exception
+ assert_raise(ArgumentError) {
+ Rational("1/1", exception: 1)
+ }
+ end
+
+ def test_Rational_without_exception
+ assert_nothing_raised(ArgumentError) {
+ assert_equal(nil, Rational("5/3x", exception: false))
+ }
+ assert_nothing_raised(ZeroDivisionError) {
+ assert_equal(nil, Rational("1/0", exception: false))
+ }
+ assert_nothing_raised(TypeError) {
+ assert_equal(nil, Rational(nil, exception: false))
+ }
+ assert_nothing_raised(TypeError) {
+ assert_equal(nil, Rational(Object.new, exception: false))
+ }
+ assert_nothing_raised(TypeError) {
+ assert_equal(nil, Rational(1, nil, exception: false))
+ }
+ assert_nothing_raised(TypeError) {
+ assert_equal(nil, Rational(1, Object.new, exception: false))
+ }
+
+ bug12485 = '[ruby-core:75995] [Bug #12485]'
+ assert_nothing_raised(RuntimeError, bug12485) {
+ o = Object.new
+ def o.to_int; raise; end
+ assert_equal(nil, Rational(o, exception: false))
+ }
+ assert_nothing_raised(RuntimeError, bug12485) {
+ o = Object.new
+ def o.to_int; raise; end
+ assert_equal(nil, Rational(1, o, exception: false))
+ }
+
+ o = Object.new;
+ def o.to_r; raise; end
+ assert_nothing_raised(RuntimeError) {
+ assert_equal(nil, Rational(o, exception: false))
+ }
+ assert_nothing_raised(TypeError) {
+ assert_equal(nil, Rational(1, o, exception: false))
+ }
+ end
+
def test_to_i
assert_equal(1, Rational(3,2).to_i)
assert_equal(1, Integer(Rational(3,2)))
@@ -787,6 +892,7 @@ class Rational_Test < Test::Unit::TestCase
def test_to_f
assert_equal(1.5, Rational(3,2).to_f)
assert_equal(1.5, Float(Rational(3,2)))
+ assert_equal(1e-23, Rational(1, 10**23).to_f, "Bug #14637")
end
def test_to_c
@@ -964,11 +1070,10 @@ class Rational_Test < Test::Unit::TestCase
end
def test_power_overflow
- bug = '[ruby-core:79686] [Bug #13242]: Infinity due to overflow'
- x = EnvUtil.suppress_warning {4r**40000000}
- assert_predicate x, :infinite?, bug
- x = EnvUtil.suppress_warning {(1/4r)**40000000}
- assert_equal 0, x, bug
+ assert_raise(ArgumentError) { 4r**400000000000000000000 }
+ exp = 4**40000000
+ assert_equal exp, 4r**40000000
+ assert_equal 1r/exp, (1/4r)**40000000
end
def test_positive_p
diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb
index 7725820038..209e55294b 100644
--- a/test/ruby/test_refinement.rb
+++ b/test/ruby/test_refinement.rb
@@ -19,6 +19,10 @@ class TestRefinement < Test::Unit::TestCase
return "Foo#a"
end
+ def b
+ return "Foo#b"
+ end
+
def call_x
return x
end
@@ -41,6 +45,10 @@ class TestRefinement < Test::Unit::TestCase
def a
return "FooExt#a"
end
+
+ private def b
+ return "FooExt#b"
+ end
end
end
@@ -94,10 +102,26 @@ class TestRefinement < Test::Unit::TestCase
return foo.send(:z)
end
+ def self.send_b_on(foo)
+ return foo.send(:b)
+ end
+
+ def self.public_send_z_on(foo)
+ return foo.public_send(:z)
+ end
+
+ def self.public_send_b_on(foo)
+ return foo.public_send(:b)
+ end
+
def self.method_z(foo)
return foo.method(:z)
end
+ def self.instance_method_z(foo)
+ return foo.class.instance_method(:z)
+ end
+
def self.invoke_call_x_on(foo)
return foo.call_x
end
@@ -179,16 +203,63 @@ class TestRefinement < Test::Unit::TestCase
foo = Foo.new
assert_raise(NoMethodError) { foo.send(:z) }
assert_equal("FooExt#z", FooExtClient.send_z_on(foo))
+ assert_equal("FooExt#b", FooExtClient.send_b_on(foo))
assert_raise(NoMethodError) { foo.send(:z) }
assert_equal(true, RespondTo::Sub.new.respond_to?(:foo))
end
- def test_method_should_not_use_refinements
+ def test_public_send_should_use_refinements
+ foo = Foo.new
+ assert_raise(NoMethodError) { foo.public_send(:z) }
+ assert_equal("FooExt#z", FooExtClient.public_send_z_on(foo))
+ assert_equal("Foo#b", foo.public_send(:b))
+ assert_raise(NoMethodError) { FooExtClient.public_send_b_on(foo) }
+ end
+
+ module MethodIntegerPowEx
+ refine Integer do
+ def pow(*)
+ :refine_pow
+ end
+ end
+ end
+ def test_method_should_use_refinements
+ omit if Test::Unit::Runner.current_repeat_count > 0
+
foo = Foo.new
assert_raise(NameError) { foo.method(:z) }
- assert_raise(NameError) { FooExtClient.method_z(foo) }
+ assert_equal("FooExt#z", FooExtClient.method_z(foo).call)
assert_raise(NameError) { foo.method(:z) }
+ assert_equal(8, eval(<<~EOS, Sandbox::BINDING))
+ meth = 2.method(:pow)
+ using MethodIntegerPowEx
+ meth.call(3)
+ EOS
+ assert_equal(:refine_pow, eval_using(MethodIntegerPowEx, "2.pow(3)"))
+ assert_equal(:refine_pow, eval_using(MethodIntegerPowEx, "2.method(:pow).(3)"))
+ end
+
+ module InstanceMethodIntegerPowEx
+ refine Integer do
+ def abs
+ :refine_abs
+ end
+ end
+ end
+ def test_instance_method_should_use_refinements
+ omit if Test::Unit::Runner.current_repeat_count > 0
+
+ foo = Foo.new
+ assert_raise(NameError) { Foo.instance_method(:z) }
+ assert_equal("FooExt#z", FooExtClient.instance_method_z(foo).bind(foo).call)
+ assert_raise(NameError) { Foo.instance_method(:z) }
+ assert_equal(4, eval(<<~EOS, Sandbox::BINDING))
+ meth = Integer.instance_method(:abs)
+ using InstanceMethodIntegerPowEx
+ meth.bind(-4).call
+ EOS
+ assert_equal(:refine_abs, eval_using(InstanceMethodIntegerPowEx, "Integer.instance_method(:abs).bind(-4).call"))
end
def test_no_local_rebinding
@@ -297,9 +368,9 @@ class TestRefinement < Test::Unit::TestCase
end
end
- def test_respond_to_should_not_use_refinements
+ def test_respond_to_should_use_refinements
assert_equal(false, 1.respond_to?(:foo))
- assert_equal(false, eval_using(IntegerFooExt, "1.respond_to?(:foo)"))
+ assert_equal(true, eval_using(IntegerFooExt, "1.respond_to?(:foo)"))
end
module StringCmpExt
@@ -471,7 +542,7 @@ class TestRefinement < Test::Unit::TestCase
def test_main_using_is_private
assert_raise(NoMethodError) do
- eval("self.using Module.new", Sandbox::BINDING)
+ eval("recv = self; recv.using Module.new", Sandbox::BINDING)
end
end
@@ -676,130 +747,37 @@ class TestRefinement < Test::Unit::TestCase
end
end
- module IncludeIntoRefinement
- class C
- def bar
- return "C#bar"
- end
-
- def baz
- return "C#baz"
- end
- end
-
- module Mixin
- def foo
- return "Mixin#foo"
- end
-
- def bar
- return super << " Mixin#bar"
- end
-
- def baz
- return super << " Mixin#baz"
- end
- end
-
- module M
- refine C do
- include Mixin
-
- def baz
- return super << " M#baz"
- end
- end
- end
+ def self.suppress_verbose
+ verbose, $VERBOSE = $VERBOSE, nil
+ yield
+ ensure
+ $VERBOSE = verbose
end
- eval <<-EOF, Sandbox::BINDING
- using TestRefinement::IncludeIntoRefinement::M
-
- module TestRefinement::IncludeIntoRefinement::User
- def self.invoke_foo_on(x)
- x.foo
- end
-
- def self.invoke_bar_on(x)
- x.bar
- end
-
- def self.invoke_baz_on(x)
- x.baz
- end
- end
- EOF
-
def test_include_into_refinement
- x = IncludeIntoRefinement::C.new
- assert_equal("Mixin#foo", IncludeIntoRefinement::User.invoke_foo_on(x))
- assert_equal("C#bar Mixin#bar",
- IncludeIntoRefinement::User.invoke_bar_on(x))
- assert_equal("C#baz Mixin#baz M#baz",
- IncludeIntoRefinement::User.invoke_baz_on(x))
- end
-
- module PrependIntoRefinement
- class C
- def bar
- return "C#bar"
- end
-
- def baz
- return "C#baz"
- end
- end
-
- module Mixin
- def foo
- return "Mixin#foo"
- end
-
- def bar
- return super << " Mixin#bar"
- end
-
- def baz
- return super << " Mixin#baz"
- end
- end
-
- module M
- refine C do
- prepend Mixin
+ assert_raise(TypeError) do
+ c = Class.new
+ mixin = Module.new
- def baz
- return super << " M#baz"
+ Module.new do
+ refine c do
+ include mixin
end
end
end
end
- eval <<-EOF, Sandbox::BINDING
- using TestRefinement::PrependIntoRefinement::M
-
- module TestRefinement::PrependIntoRefinement::User
- def self.invoke_foo_on(x)
- x.foo
- end
-
- def self.invoke_bar_on(x)
- x.bar
- end
+ def test_prepend_into_refinement
+ assert_raise(TypeError) do
+ c = Class.new
+ mixin = Module.new
- def self.invoke_baz_on(x)
- x.baz
+ Module.new do
+ refine c do
+ prepend mixin
+ end
end
end
- EOF
-
- def test_prepend_into_refinement
- x = PrependIntoRefinement::C.new
- assert_equal("Mixin#foo", PrependIntoRefinement::User.invoke_foo_on(x))
- assert_equal("C#bar Mixin#bar",
- PrependIntoRefinement::User.invoke_bar_on(x))
- assert_equal("C#baz M#baz Mixin#baz",
- PrependIntoRefinement::User.invoke_baz_on(x))
end
PrependAfterRefine_CODE = <<-EOC
@@ -841,15 +819,15 @@ class TestRefinement < Test::Unit::TestCase
def test_prepend_after_refine_wb_miss
if /\A(arm|mips)/ =~ RUBY_PLATFORM
- skip "too slow cpu"
+ omit "too slow cpu"
end
assert_normal_exit %Q{
GC.stress = true
10.times{
#{PrependAfterRefine_CODE}
- undef PrependAfterRefine
+ Object.send(:remove_const, :PrependAfterRefine)
}
- }
+ }, timeout: 60
end
def test_prepend_after_refine
@@ -907,6 +885,10 @@ class TestRefinement < Test::Unit::TestCase
return foo.send(:z)
end
+ def self.public_send_z_on(foo)
+ return foo.public_send(:z)
+ end
+
def self.method_z(foo)
return foo.method(:z)
end
@@ -1062,7 +1044,7 @@ class TestRefinement < Test::Unit::TestCase
end
using Test
def t
- 'Refinements are broken!'.chop!
+ 'Refinements are broken!'.dup.chop!
end
t
module Test
@@ -1224,6 +1206,41 @@ class TestRefinement < Test::Unit::TestCase
INPUT
end
+ def test_refined_protected_methods
+ assert_separately([], <<-"end;")
+ bug18806 = '[ruby-core:108705] [Bug #18806]'
+ class C; end
+
+ module R
+ refine C do
+ def refined_call_foo = foo
+ def refined_call_foo_on(other) = other.foo
+
+ protected
+
+ def foo = :foo
+ end
+ end
+
+ class C
+ using R
+
+ def call_foo = foo
+ def call_foo_on(other) = other.foo
+ end
+
+ c = C.new
+ assert_equal :foo, c.call_foo, bug18806
+ assert_equal :foo, c.call_foo_on(c), bug18806
+ assert_equal :foo, c.call_foo_on(C.new), bug18806
+
+ using R
+ assert_equal :foo, c.refined_call_foo, bug18806
+ assert_equal :foo, c.refined_call_foo_on(c), bug18806
+ assert_equal :foo, c.refined_call_foo_on(C.new), bug18806
+ end;
+ end
+
def test_refine_basic_object
assert_separately([], <<-"end;")
bug10106 = '[ruby-core:64166] [Bug #10106]'
@@ -1491,6 +1508,7 @@ class TestRefinement < Test::Unit::TestCase
undef :foo
end
end
+ ext
end
def test_call_refined_method_in_duplicate_module
@@ -1576,7 +1594,6 @@ class TestRefinement < Test::Unit::TestCase
def test_reopen_refinement_module
assert_separately([], <<-"end;")
- $VERBOSE = nil
class C
end
@@ -1589,17 +1606,35 @@ class TestRefinement < Test::Unit::TestCase
end
using R
+ def m
+ C.new.m
+ end
+
assert_equal(:foo, C.new.m)
+ assert_equal(:foo, m)
module R
refine C do
+
+ assert_equal(:foo, C.new.m)
+ assert_equal(:foo, m)
+
+ alias m m
+
+ assert_equal(:foo, C.new.m)
+ assert_equal(:foo, m)
+
def m
:bar
end
+
+ assert_equal(:bar, C.new.m, "[ruby-core:71423] [Bug #11672]")
+ assert_equal(:bar, m, "[Bug #20285]")
end
end
assert_equal(:bar, C.new.m, "[ruby-core:71423] [Bug #11672]")
+ assert_equal(:bar, m, "[Bug #20285]")
end;
end
@@ -1704,6 +1739,8 @@ class TestRefinement < Test::Unit::TestCase
refine Object do
def in_ref_a
end
+
+ RefA.const_set(:REF, self)
end
end
@@ -1711,6 +1748,8 @@ class TestRefinement < Test::Unit::TestCase
refine Object do
def in_ref_b
end
+
+ RefB.const_set(:REF, self)
end
end
@@ -1720,23 +1759,28 @@ class TestRefinement < Test::Unit::TestCase
refine Object do
def in_ref_c
end
+
+ RefC.const_set(:REF, self)
end
end
module Foo
using RefB
USED_MODS = Module.used_modules
+ USED_REFS = Module.used_refinements
end
module Bar
using RefC
USED_MODS = Module.used_modules
+ USED_REFS = Module.used_refinements
end
module Combined
using RefA
using RefB
USED_MODS = Module.used_modules
+ USED_REFS = Module.used_refinements
end
end
@@ -1748,6 +1792,41 @@ class TestRefinement < Test::Unit::TestCase
assert_equal [ref::RefB, ref::RefA], ref::Combined::USED_MODS
end
+ def test_used_refinements
+ ref = VisibleRefinements
+ assert_equal [], Module.used_refinements
+ assert_equal [ref::RefB::REF], ref::Foo::USED_REFS
+ assert_equal [ref::RefC::REF], ref::Bar::USED_REFS
+ assert_equal [ref::RefB::REF, ref::RefA::REF], ref::Combined::USED_REFS
+ end
+
+ def test_refinements
+ int_refinement = nil
+ str_refinement = nil
+ m = Module.new {
+ refine Integer do
+ int_refinement = self
+ end
+
+ refine String do
+ str_refinement = self
+ end
+ }
+ assert_equal([int_refinement, str_refinement], m.refinements)
+ end
+
+ def test_target
+ refinements = Module.new {
+ refine Integer do
+ end
+
+ refine String do
+ end
+ }.refinements
+ assert_equal(Integer, refinements[0].target)
+ assert_equal(String, refinements[1].target)
+ end
+
def test_warn_setconst_in_refinmenet
bug10103 = '[ruby-core:64143] [Bug #10103]'
warnings = [
@@ -1854,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
@@ -1892,10 +1994,10 @@ class TestRefinement < Test::Unit::TestCase
m = Module.new do
r = refine(String) {def test;:ok end}
end
- assert_raise_with_message(ArgumentError, /refinement/, bug) do
+ assert_raise_with_message(TypeError, /refinement/, bug) do
m.module_eval {include r}
end
- assert_raise_with_message(ArgumentError, /refinement/, bug) do
+ assert_raise_with_message(TypeError, /refinement/, bug) do
m.module_eval {prepend r}
end
end
@@ -1992,7 +2094,6 @@ class TestRefinement < Test::Unit::TestCase
def test_tostring
assert_equal("ok", ToString.new.string)
- assert_predicate(ToString.new.taint.string, :tainted?)
end
class ToSymbol
@@ -2010,6 +2111,122 @@ class TestRefinement < Test::Unit::TestCase
assert_equal(:foo, ToSymbol.new("foo").symbol)
end
+ module ToProc
+ def self.call &block
+ block.call
+ end
+
+ class ReturnProc
+ c = self
+ using Module.new {
+ refine c do
+ def to_proc
+ proc { "to_proc" }
+ end
+ end
+ }
+ def call
+ ToProc.call(&self)
+ end
+ end
+
+ class ReturnNoProc
+ c = self
+ using Module.new {
+ refine c do
+ def to_proc
+ true
+ end
+ end
+ }
+
+ def call
+ ToProc.call(&self)
+ end
+ end
+
+ class PrivateToProc
+ c = self
+ using Module.new {
+ refine c do
+ private
+ def to_proc
+ proc { "private_to_proc" }
+ end
+ end
+ }
+
+ def call
+ ToProc.call(&self)
+ end
+ end
+
+
+ class NonProc
+ def call
+ ToProc.call(&self)
+ end
+ end
+
+ class MethodMissing
+ def method_missing *args
+ proc { "method_missing" }
+ end
+
+ def call
+ ToProc.call(&self)
+ end
+ end
+
+ class ToProcAndMethodMissing
+ def method_missing *args
+ proc { "method_missing" }
+ end
+
+ c = self
+ using Module.new {
+ refine c do
+ def to_proc
+ proc { "to_proc" }
+ end
+ end
+ }
+
+ def call
+ ToProc.call(&self)
+ end
+ end
+
+ class ToProcAndRefinements
+ def to_proc
+ proc { "to_proc" }
+ end
+
+ c = self
+ using Module.new {
+ refine c do
+ def to_proc
+ proc { "refinements_to_proc" }
+ end
+ end
+ }
+
+ def call
+ ToProc.call(&self)
+ end
+ end
+ end
+
+ def test_to_proc
+ assert_equal("to_proc", ToProc::ReturnProc.new.call)
+ assert_equal("private_to_proc", ToProc::PrivateToProc.new.call)
+ assert_raise(TypeError){ ToProc::ReturnNoProc.new.call }
+ assert_raise(TypeError){ ToProc::NonProc.new.call }
+ assert_equal("method_missing", ToProc::MethodMissing.new.call)
+ assert_equal("to_proc", ToProc::ToProcAndMethodMissing.new.call)
+ assert_equal("refinements_to_proc", ToProc::ToProcAndRefinements.new.call)
+ end
+
def test_unused_refinement_for_module
bug14068 = '[ruby-core:83613] [Bug #14068]'
assert_in_out_err([], <<-INPUT, ["M1#foo"], [], bug14068)
@@ -2104,6 +2321,442 @@ class TestRefinement < Test::Unit::TestCase
end
end
assert_equal("[C[A[B]]]", c.new.foo, '[ruby-dev:50390] [Bug #14232]')
+ d
+ end
+
+ class RefineInUsing
+ module M1
+ refine RefineInUsing do
+ def foo
+ :ok
+ end
+ end
+ end
+
+ module M2
+ using M1
+ refine RefineInUsing do
+ def call_foo
+ RefineInUsing.new.foo
+ end
+ end
+ end
+
+ using M2
+ def self.test
+ new.call_foo
+ end
+ end
+
+ def test_refine_in_using
+ assert_equal(:ok, RefineInUsing.test)
+ end
+
+ class Bug16242
+ module OtherM
+ end
+
+ module M
+ prepend OtherM
+
+ refine M do
+ def refine_method
+ "refine_method"
+ end
+ end
+ using M
+
+ def hoge
+ refine_method
+ end
+ end
+
+ class X
+ include M
+ end
+ end
+
+ def test_refine_prepended_module
+ assert_equal("refine_method", Bug16242::X.new.hoge)
+ end
+
+ module Bug13446
+ module Enumerable
+ def sum(*args)
+ i = 0
+ args.each { |arg| i += a }
+ i
+ end
+ end
+
+ using Module.new {
+ refine Enumerable do
+ alias :orig_sum :sum
+ end
+ }
+
+ module Enumerable
+ def sum(*args)
+ orig_sum(*args)
+ end
+ end
+
+ class GenericEnumerable
+ include Enumerable
+ end
+
+ Enumerable.prepend(Module.new)
+ end
+
+ def test_prepend_refined_module
+ assert_equal(0, Bug13446::GenericEnumerable.new.sum)
+ end
+
+ def test_unbound_refine_method
+ a = EnvUtil.labeled_class("A") do
+ def foo
+ self.class
+ end
+ end
+ b = EnvUtil.labeled_class("B")
+ bar = EnvUtil.labeled_module("R") do
+ break refine a do
+ def foo
+ super
+ end
+ end
+ end
+ assert_raise(TypeError) do
+ bar.instance_method(:foo).bind(b.new)
+ end
+ end
+
+ def test_refine_frozen_class
+ verbose_bak, $VERBOSE = $VERBOSE, nil
+ singleton_class.instance_variable_set(:@x, self)
+ class << self
+ c = Class.new do
+ def foo
+ :cfoo
+ end
+ end
+ foo = Module.new do
+ refine c do
+ def foo
+ :rfoo
+ end
+ end
+ end
+ using foo
+ @x.assert_equal(:rfoo, c.new.foo)
+ c.freeze
+ foo.module_eval do
+ refine c do
+ def foo
+ :rfoo2
+ end
+ def bar
+ :rbar
+ end
+ end
+ end
+ @x.assert_equal(:rfoo2, c.new.foo)
+ @x.assert_equal(:rbar, c.new.bar, '[ruby-core:71391] [Bug #11669]')
+ end
+ ensure
+ $VERBOSE = verbose_bak
+ end
+
+ # [Bug #17386]
+ def test_prepended_with_method_cache
+ foo = Class.new do
+ def foo
+ :Foo
+ end
+ end
+
+ code = Module.new do
+ def foo
+ :Code
+ end
+ end
+
+ _ext = Module.new do
+ refine foo do
+ def foo; end
+ end
+ end
+
+ obj = foo.new
+
+ assert_equal :Foo, obj.foo
+ foo.prepend code
+ assert_equal :Code, obj.foo
+ end
+
+ # [Bug #17417]
+ def test_prepended_with_method_cache_17417
+ assert_normal_exit %q{
+ module M
+ def hoge; end
+ end
+
+ module R
+ refine Hash do
+ def except *args; end
+ end
+ end
+
+ h = {}
+ h.method(:except) # put it on pCMC
+ Hash.prepend(M)
+ h.method(:except)
+ }
+ end
+
+ def test_defining_after_cached
+ klass = Class.new
+ _refinement = Module.new { refine(klass) { def foo; end } }
+ klass.new.foo rescue nil # cache the refinement method entry
+ klass.define_method(:foo) { 42 }
+ assert_equal(42, klass.new.foo)
+ end
+
+ # [Bug #17806]
+ def test_two_refinements_for_prepended_class
+ assert_normal_exit %q{
+ module R1
+ refine Hash do
+ def foo; :r1; end
+ end
+ end
+
+ class Hash
+ prepend(Module.new)
+ end
+
+ class Hash
+ def foo; end
+ end
+
+ {}.method(:foo) # put it on pCMC
+
+ module R2
+ refine Hash do
+ def foo; :r2; end
+ end
+ end
+
+ {}.foo
+ }
+ end
+
+ # [Bug #17806]
+ def test_redefining_refined_for_prepended_class
+ klass = Class.new { def foo; end }
+ _refinement = Module.new do
+ refine(klass) { def foo; :refined; end }
+ end
+ klass.prepend(Module.new)
+ klass.new.foo # cache foo
+ klass.define_method(:foo) { :second }
+ assert_equal(:second, klass.new.foo)
+ end
+
+ class Bug18180
+ module M
+ refine Array do
+ def min; :min; end
+ def max; :max; end
+ end
+ end
+
+ using M
+
+ def t
+ [[1+0, 2, 4].min, [1, 2, 4].min, [1+0, 2, 4].max, [1, 2, 4].max]
+ end
+ end
+
+ def test_refine_array_min_max
+ assert_equal([:min, :min, :max, :max], Bug18180.new.t)
+ end
+
+ class Bug17822
+ module Ext
+ refine(Bug17822) do
+ def foo = :refined
+ end
+ end
+
+ private(def foo = :not_refined)
+
+ module Client
+ using Ext
+ def self.call_foo
+ Bug17822.new.foo
+ end
+ end
+ end
+
+ # [Bug #17822]
+ def test_privatizing_refined_method
+ assert_equal(:refined, Bug17822::Client.call_foo)
+ end
+
+ def test_ancestors
+ refinement = nil
+ as = nil
+ Module.new do
+ refine Array do
+ refinement = self
+ as = ancestors
+ end
+ end
+ assert_equal([refinement], as, "[ruby-core:86949] [Bug #14744]")
+ end
+
+ module TestImport
+ class A
+ def foo
+ "original"
+ end
+ end
+
+ module B
+ BAR = "bar"
+
+ def bar
+ "#{foo}:#{BAR}"
+ end
+ end
+
+ module C
+ refine A do
+ import_methods B
+
+ def foo
+ "refined"
+ end
+ end
+ end
+
+ module UsingC
+ using C
+
+ def self.call_bar
+ A.new.bar
+ end
+ end
+ end
+
+ def test_import_methods
+ assert_equal("refined:bar", TestImport::UsingC.call_bar)
+
+ assert_raise(ArgumentError) do
+ Module.new do
+ refine Integer do
+ import_methods Enumerable
+ end
+ end
+ end
+ end
+
+ def test_inherit_singleton_methods_of_module
+ assert_equal([], Refinement.used_modules)
+ end
+
+ def test_inlinecache
+ assert_separately([], <<-"end;")
+ module R
+ refine String do
+ def to_s = :R
+ end
+ end
+
+ 2.times{|i|
+ s = ''.to_s
+ assert_equal '', s if i == 0
+ assert_equal :R, s if i == 1
+ using R if i == 0
+ assert_equal :R, ''.to_s
+ }
+ end;
+ end
+
+ def test_inline_cache_invalidation
+ klass = Class.new do
+ def cached_foo_callsite = foo
+
+ def foo = :v1
+
+ host = self
+ @refinement = Module.new do
+ refine(host) do
+ def foo = :unused
+ end
+ end
+ end
+
+ obj = klass.new
+ obj.cached_foo_callsite # prime cache
+ klass.class_eval do
+ def foo = :v2 # invalidate
+ end
+ assert_equal(:v2, obj.cached_foo_callsite)
+ end
+
+ # [Bug #20302]
+ def test_multiple_refinements_for_same_module
+ assert_in_out_err([], <<-INPUT, %w(:f2 :f1), [])
+ module M1
+ refine(Kernel) do
+ def f1 = :f1
+ end
+ end
+
+ module M2
+ refine(Kernel) do
+ def f2 = :f2
+ end
+ end
+
+ class Foo
+ using M1
+ using M2
+
+ def test
+ p f2
+ p f1
+ end
+ end
+
+ Foo.new.test
+ 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
diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb
index fe271dc3d7..9feababa53 100644
--- a/test/ruby/test_regexp.rb
+++ b/test/ruby/test_regexp.rb
@@ -5,7 +5,6 @@ require 'test/unit'
class TestRegexp < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
end
def teardown
@@ -41,22 +40,21 @@ class TestRegexp < Test::Unit::TestCase
assert_equal("a".gsub(/a\Z/, ""), "")
end
- def test_yoshidam_net_20041111_1
- s = "[\xC2\xA0-\xC3\xBE]"
- assert_match(Regexp.new(s, nil, "u"), "\xC3\xBE")
+ def test_ruby_dev_31309
+ assert_equal('Ruby', 'Ruby'.sub(/[^a-z]/i, '-'))
end
- def test_yoshidam_net_20041111_2
- assert_raise(RegexpError) do
- s = "[\xFF-\xFF]".force_encoding("utf-8")
- Regexp.new(s, nil, "u")
+ def test_premature_end_char_property
+ ["\\p{",
+ "\\p{".dup.force_encoding("UTF-8"),
+ "\\p{".dup.force_encoding("US-ASCII")
+ ].each do |string|
+ assert_raise(RegexpError) do
+ Regexp.new(string)
+ end
end
end
- def test_ruby_dev_31309
- assert_equal('Ruby', 'Ruby'.sub(/[^a-z]/i, '-'))
- end
-
def test_assert_normal_exit
# moved from knownbug. It caused core.
Regexp.union("a", "a")
@@ -74,12 +72,141 @@ class TestRegexp < Test::Unit::TestCase
end
end
+ def test_to_s_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ str = "abcd\u3042"
+ [:UTF_16BE, :UTF_16LE, :UTF_32BE, :UTF_32LE].each do |es|
+ enc = Encoding.const_get(es)
+ rs = Regexp.new(str.encode(enc)).to_s
+ assert_equal("(?-mix:abcd\u3042)".encode(enc), rs)
+ assert_equal(enc, rs.encoding)
+ end
+ end
+ end
+
def test_to_s_extended_subexp
re = /#\g#{"\n"}/x
re = /#{re}/
assert_warn('', '[ruby-core:82328] [Bug #13798]') {re.to_s}
end
+ def test_extended_comment_invalid_escape_bug_18294
+ assert_separately([], <<-RUBY)
+ re = / C:\\\\[a-z]{5} # e.g. C:\\users /x
+ assert_match(re, 'C:\\users')
+ assert_not_match(re, 'C:\\user')
+
+ re = /
+ foo # \\M-ca
+ bar
+ /x
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /
+ f[#o]o # \\M-ca
+ bar
+ /x
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /
+ f[[:alnum:]#]o # \\M-ca
+ bar
+ /x
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /
+ f(?# \\M-ca)oo # \\M-ca
+ bar
+ /x
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /f(?# \\M-ca)oobar/
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /[-(?# fca)]oobar/
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /f(?# ca\0\\M-ca)oobar/
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+ RUBY
+
+ assert_raise(SyntaxError) {eval "/\\users/x"}
+ assert_raise(SyntaxError) {eval "/[\\users]/x"}
+ assert_raise(SyntaxError) {eval "/(?<\\users)/x"}
+ assert_raise(SyntaxError) {eval "/# \\users/"}
+ end
+
+ def test_nonextended_section_of_extended_regexp_bug_19379
+ assert_separately([], <<-'RUBY')
+ re = /(?-x:#)/x
+ assert_match(re, '#')
+ assert_not_match(re, '-')
+
+ re = /(?xi:#
+ y)/
+ assert_match(re, 'Y')
+ assert_not_match(re, '-')
+
+ re = /(?mix:#
+ y)/
+ assert_match(re, 'Y')
+ assert_not_match(re, '-')
+
+ re = /(?x-im:#
+ y)/i
+ assert_match(re, 'y')
+ assert_not_match(re, 'Y')
+
+ re = /(?-imx:(?xim:#
+ y))/x
+ assert_match(re, 'y')
+ assert_not_match(re, '-')
+
+ re = /(?x)#
+ y/
+ assert_match(re, 'y')
+ assert_not_match(re, 'Y')
+
+ re = /(?mx-i)#
+ y/i
+ assert_match(re, 'y')
+ assert_not_match(re, 'Y')
+
+ re = /(?-imx:(?xim:#
+ (?-x)y#))/x
+ assert_match(re, 'Y#')
+ assert_not_match(re, '-#')
+
+ re = /(?imx:#
+ (?-xim:#(?im)#(?x)#
+ )#
+ (?x)#
+ y)/
+ assert_match(re, '###Y')
+ assert_not_match(re, '###-')
+
+ re = %r{#c-\w+/comment/[\w-]+}
+ re = %r{https?://[^/]+#{re}}x
+ assert_match(re, 'http://foo#c-x/comment/bar')
+ assert_not_match(re, 'http://foo#cx/comment/bar')
+ RUBY
+ end
+
+ def test_utf8_comment_in_usascii_extended_regexp_bug_19455
+ assert_separately([], <<-RUBY)
+ assert_equal(Encoding::UTF_8, /(?#\u1000)/x.encoding)
+ assert_equal(Encoding::UTF_8, /#\u1000/x.encoding)
+ RUBY
+ end
+
def test_union
assert_equal :ok, begin
Regexp.union(
@@ -90,6 +217,11 @@ class TestRegexp < Test::Unit::TestCase
rescue ArgumentError
:ok
end
+ re = Regexp.union(/\//, "")
+ re2 = eval(re.inspect)
+ assert_equal(re.to_s, re2.to_s)
+ assert_equal(re.source, re2.source)
+ assert_equal(re, re2)
end
def test_word_boundary
@@ -192,6 +324,9 @@ class TestRegexp < Test::Unit::TestCase
assert_equal({'a' => '1', 'b' => '2', 'c' => '3'}, /^(?<a>.)(?<b>.)(?<c>.)?/.match('123').named_captures)
assert_equal({'a' => '1', 'b' => '2', 'c' => ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures)
+ assert_equal({a: '1', b: '2', c: ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures(symbolize_names: true))
+ assert_equal({'a' => '1', 'b' => '2', 'c' => ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures(symbolize_names: false))
+
assert_equal({'a' => 'x'}, /(?<a>x)|(?<a>y)/.match('x').named_captures)
assert_equal({'a' => 'y'}, /(?<a>x)|(?<a>y)/.match('y').named_captures)
@@ -213,6 +348,23 @@ class TestRegexp < Test::Unit::TestCase
def test_assign_named_capture_to_reserved_word
/(?<nil>.)/ =~ "a"
assert_not_include(local_variables, :nil, "[ruby-dev:32675]")
+
+ def (obj = Object.new).test(s, nil: :ng)
+ /(?<nil>.)/ =~ s
+ binding.local_variable_get(:nil)
+ end
+ assert_equal("b", obj.test("b"))
+
+ tap do |nil: :ng|
+ /(?<nil>.)/ =~ "c"
+ assert_equal("c", binding.local_variable_get(:nil))
+ end
+ end
+
+ def test_assign_named_capture_to_const
+ %W[C \u{1d402}].each do |name|
+ assert_equal(:ok, Class.new.class_eval("#{name} = :ok; /(?<#{name}>.*)/ =~ 'ng'; #{name}"))
+ end
end
def test_assign_named_capture_trace
@@ -232,6 +384,27 @@ class TestRegexp < Test::Unit::TestCase
assert_equal(re, re.match("foo").regexp)
end
+ def test_match_lambda_multithread
+ bug17507 = "[ruby-core:101901]"
+ str = "a-x-foo-bar-baz-z-b"
+
+ worker = lambda do
+ m = /foo-([A-Za-z0-9_\.]+)-baz/.match(str)
+ assert_equal("bar", m[1], bug17507)
+
+ # These two lines are needed to trigger the bug
+ File.exist? "/tmp"
+ str.gsub(/foo-bar-baz/, "foo-abc-baz")
+ end
+
+ def self. threaded_test(worker)
+ 6.times.map {Thread.new {10_000.times {worker.call}}}.each(&:join)
+ end
+
+ # The bug only occurs in a method calling a block/proc/lambda
+ threaded_test(worker)
+ end
+
def test_source
bug5484 = '[ruby-core:40364]'
assert_equal('', //.source)
@@ -297,6 +470,13 @@ class TestRegexp < Test::Unit::TestCase
assert_equal('/\/\xF1\xF2\xF3/i', /\/#{s}/i.inspect)
end
+ def test_inspect_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ assert_equal('/(?-mix:\\/)|/', Regexp.union(/\//, "").inspect)
+ end
+ end
+
def test_char_to_option
assert_equal("BAR", "FOOBARBAZ"[/b../i])
assert_equal("bar", "foobarbaz"[/ b . . /x])
@@ -370,6 +550,37 @@ class TestRegexp < Test::Unit::TestCase
assert_equal([2, 3], m.offset(3))
end
+ def test_match_byteoffset_begin_end
+ m = /(?<x>b..)/.match("foobarbaz")
+ assert_equal([3, 6], m.byteoffset("x"))
+ assert_equal(3, m.begin("x"))
+ assert_equal(6, m.end("x"))
+ assert_raise(IndexError) { m.byteoffset("y") }
+ assert_raise(IndexError) { m.byteoffset(2) }
+ assert_raise(IndexError) { m.begin(2) }
+ assert_raise(IndexError) { m.end(2) }
+ assert_raise(IndexError) { m.bytebegin(2) }
+ assert_raise(IndexError) { m.byteend(2) }
+
+ m = /(?<x>q..)?/.match("foobarbaz")
+ assert_equal([nil, nil], m.byteoffset("x"))
+ assert_equal(nil, m.begin("x"))
+ assert_equal(nil, m.end("x"))
+ assert_equal(nil, m.bytebegin("x"))
+ assert_equal(nil, m.byteend("x"))
+
+ m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044")
+ assert_equal([3, 6], m.byteoffset(1))
+ assert_equal(3, m.bytebegin(1))
+ assert_equal(6, m.byteend(1))
+ assert_equal([nil, nil], m.byteoffset(2))
+ assert_equal(nil, m.bytebegin(2))
+ assert_equal(nil, m.byteend(2))
+ assert_equal([6, 9], m.byteoffset(3))
+ assert_equal(6, m.bytebegin(3))
+ assert_equal(9, m.byteend(3))
+ end
+
def test_match_to_s
m = /(?<x>b..)/.match("foobarbaz")
assert_equal("bar", m.to_s)
@@ -413,6 +624,7 @@ class TestRegexp < Test::Unit::TestCase
assert_nil(m[5])
assert_raise(IndexError) { m[:foo] }
assert_raise(TypeError) { m[nil] }
+ assert_equal(["baz", nil], m[-2, 3])
end
def test_match_values_at
@@ -440,22 +652,112 @@ class TestRegexp < Test::Unit::TestCase
assert_equal("foobarbaz", m.string)
end
+ def test_match_matchsubstring
+ m = /(.)(.)(\d+)(\d)(\w)?/.match("THX1138.")
+ assert_equal("HX1138", m.match(0))
+ assert_equal("8", m.match(4))
+ assert_nil(m.match(5))
+
+ m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044")
+ assert_equal("\u3043", m.match(1))
+ assert_nil(m.match(2))
+ assert_equal("\u3044", m.match(3))
+
+ m = /(?<foo>.)(?<n>[^aeiou])?(?<bar>.+)/.match("hoge\u3042")
+ assert_equal("h", m.match(:foo))
+ assert_nil(m.match(:n))
+ assert_equal("oge\u3042", m.match(:bar))
+ end
+
+ def test_match_match_length
+ m = /(.)(.)(\d+)(\d)(\w)?/.match("THX1138.")
+ assert_equal(6, m.match_length(0))
+ assert_equal(1, m.match_length(4))
+ assert_nil(m.match_length(5))
+
+ m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044")
+ assert_equal(1, m.match_length(1))
+ assert_nil(m.match_length(2))
+ assert_equal(1, m.match_length(3))
+
+ m = /(?<foo>.)(?<n>[^aeiou])?(?<bar>.+)/.match("hoge\u3042")
+ assert_equal(1, m.match_length(:foo))
+ assert_nil(m.match_length(:n))
+ assert_equal(4, m.match_length(:bar))
+ end
+
def test_match_inspect
m = /(...)(...)(...)(...)?/.match("foobarbaz")
assert_equal('#<MatchData "foobarbaz" 1:"foo" 2:"bar" 3:"baz" 4:nil>', m.inspect)
end
- def test_initialize
- assert_raise(ArgumentError) { Regexp.new }
- assert_equal(/foo/, Regexp.new(/foo/, Regexp::IGNORECASE))
+ def test_match_data_deconstruct
+ m = /foo.+/.match("foobarbaz")
+ assert_equal([], m.deconstruct)
+
+ m = /(foo).+(baz)/.match("foobarbaz")
+ assert_equal(["foo", "baz"], m.deconstruct)
+
+ m = /(...)(...)(...)(...)?/.match("foobarbaz")
+ assert_equal(["foo", "bar", "baz", nil], m.deconstruct)
+ end
- assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", nil, "n").encoding)
- assert_equal("bar", "foobarbaz"[Regexp.new("b..", nil, "n")])
- assert_equal(//n, Regexp.new("", nil, "n"))
+ def test_match_data_deconstruct_keys
+ m = /foo.+/.match("foobarbaz")
+ assert_equal({}, m.deconstruct_keys([:a]))
- arg_encoding_none = 32 # ARG_ENCODING_NONE is implementation defined value
- assert_equal(arg_encoding_none, Regexp.new("", nil, "n").options)
- assert_equal(arg_encoding_none, Regexp.new("", nil, "N").options)
+ m = /(?<a>foo).+(?<b>baz)/.match("foobarbaz")
+ assert_equal({a: "foo", b: "baz"}, m.deconstruct_keys(nil))
+ assert_equal({a: "foo", b: "baz"}, m.deconstruct_keys([:a, :b]))
+ assert_equal({b: "baz"}, m.deconstruct_keys([:b]))
+ assert_equal({}, m.deconstruct_keys([:c, :a]))
+ assert_equal({a: "foo"}, m.deconstruct_keys([:a, :c]))
+ assert_equal({}, m.deconstruct_keys([:a, :b, :c]))
+
+ assert_raise(TypeError) {
+ m.deconstruct_keys(0)
+ }
+
+ assert_raise(TypeError) {
+ m.deconstruct_keys(["a", "b"])
+ }
+ end
+
+ def test_match_no_match_no_matchdata
+ EnvUtil.without_gc do
+ h = {}
+ ObjectSpace.count_objects(h)
+ prev_matches = h[:T_MATCH] || 0
+ _md = /[A-Z]/.match('1') # no match
+ ObjectSpace.count_objects(h)
+ new_matches = h[:T_MATCH] || 0
+ assert_equal prev_matches, new_matches, "Bug [#20104]"
+ end
+ end
+
+ def test_initialize
+ assert_raise(ArgumentError) { Regexp.new }
+ assert_equal(/foo/, assert_warning(/ignored/) {Regexp.new(/foo/, Regexp::IGNORECASE)})
+ assert_equal(/foo/, assert_no_warning(/ignored/) {Regexp.new(/foo/)})
+ assert_equal(/foo/, assert_no_warning(/ignored/) {Regexp.new(/foo/, timeout: nil)})
+
+ arg_encoding_none = //n.options # ARG_ENCODING_NONE is implementation defined value
+
+ assert_deprecated_warning('') do
+ assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", Regexp::NOENCODING).encoding)
+ assert_equal("bar", "foobarbaz"[Regexp.new("b..", Regexp::NOENCODING)])
+ assert_equal(//, Regexp.new(""))
+ assert_equal(//, Regexp.new("", timeout: 1))
+ assert_equal(//n, Regexp.new("", Regexp::NOENCODING))
+ assert_equal(//n, Regexp.new("", Regexp::NOENCODING, timeout: 1))
+
+ assert_equal(arg_encoding_none, Regexp.new("", Regexp::NOENCODING).options)
+
+ assert_nil(Regexp.new("").timeout)
+ assert_equal(1.0, Regexp.new("", timeout: 1.0).timeout)
+ assert_nil(Regexp.compile("").timeout)
+ assert_equal(1.0, Regexp.compile("", timeout: 1.0).timeout)
+ end
assert_raise(RegexpError) { Regexp.new(")(") }
assert_raise(RegexpError) { Regexp.new('[\\40000000000') }
@@ -463,6 +765,53 @@ class TestRegexp < Test::Unit::TestCase
assert_raise(RegexpError) { Regexp.new("((?<v>))\\g<0>") }
end
+ def test_initialize_from_regex_memory_corruption
+ assert_ruby_status([], <<-'end;')
+ 10_000.times { Regexp.new(Regexp.new("(?<name>)")) }
+ end;
+ end
+
+ def test_initialize_bool_warning
+ assert_warning(/expected true or false as ignorecase/) do
+ Regexp.new("foo", :i)
+ end
+ end
+
+ def test_initialize_option
+ assert_equal(//i, Regexp.new("", "i"))
+ assert_equal(//m, Regexp.new("", "m"))
+ assert_equal(//x, Regexp.new("", "x"))
+ assert_equal(//imx, Regexp.new("", "imx"))
+ assert_equal(//, Regexp.new("", ""))
+ assert_equal(//imx, Regexp.new("", "mimix"))
+
+ assert_raise(ArgumentError) { Regexp.new("", "e") }
+ assert_raise(ArgumentError) { Regexp.new("", "n") }
+ assert_raise(ArgumentError) { Regexp.new("", "s") }
+ assert_raise(ArgumentError) { Regexp.new("", "u") }
+ assert_raise(ArgumentError) { Regexp.new("", "o") }
+ assert_raise(ArgumentError) { Regexp.new("", "j") }
+ assert_raise(ArgumentError) { Regexp.new("", "xmen") }
+ end
+
+ def test_match_control_meta_escape
+ assert_equal(0, /\c\xFF/ =~ "\c\xFF")
+ assert_equal(0, /\c\M-\xFF/ =~ "\c\M-\xFF")
+ assert_equal(0, /\C-\xFF/ =~ "\C-\xFF")
+ assert_equal(0, /\C-\M-\xFF/ =~ "\C-\M-\xFF")
+ assert_equal(0, /\M-\xFF/ =~ "\M-\xFF")
+ assert_equal(0, /\M-\C-\xFF/ =~ "\M-\C-\xFF")
+ assert_equal(0, /\M-\c\xFF/ =~ "\M-\c\xFF")
+
+ assert_nil(/\c\xFE/ =~ "\c\xFF")
+ assert_nil(/\c\M-\xFE/ =~ "\c\M-\xFF")
+ assert_nil(/\C-\xFE/ =~ "\C-\xFF")
+ assert_nil(/\C-\M-\xFE/ =~ "\C-\M-\xFF")
+ assert_nil(/\M-\xFE/ =~ "\M-\xFF")
+ assert_nil(/\M-\C-\xFE/ =~ "\M-\C-\xFF")
+ assert_nil(/\M-\c\xFE/ =~ "\M-\c\xFF")
+ end
+
def test_unescape
assert_raise(ArgumentError) { s = '\\'; /#{ s }/ }
assert_equal(/\xFF/n, /#{ s="\\xFF" }/n)
@@ -519,6 +868,8 @@ class TestRegexp < Test::Unit::TestCase
s = ".........."
5.times { s.sub!(".", "") }
assert_equal(".....", s)
+
+ assert_equal("\\\u{3042}", Regexp.new("\\\u{3042}").source)
end
def test_equal
@@ -537,7 +888,10 @@ class TestRegexp < Test::Unit::TestCase
assert_equal("bc", /../.match('abc', -2)[0])
assert_nil(/../.match("abc", -4))
assert_nil(/../.match("abc", 4))
- assert_equal('\x', /../n.match("\u3042" + '\x', 1)[0])
+
+ # use eval because only one warning is shown for the same regexp literal
+ pat = eval('/../n')
+ assert_equal('\x', assert_warning(/binary regexp/) {pat.match("\u3042" + '\x', 1)}[0])
r = nil
/.../.match("abc") {|m| r = m[0] }
@@ -548,6 +902,14 @@ class TestRegexp < Test::Unit::TestCase
$_ = nil; assert_nil(~/./)
end
+ def test_match_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ m = /(?<foo>.)(?<n>[^aeiou])?(?<bar>.+)/.match("hoge\u3042")
+ assert_equal("h", m.match(:foo))
+ end
+ end
+
def test_match_p
/backref/ =~ 'backref'
# must match here, but not in a separate method, e.g., assert_send,
@@ -613,7 +975,7 @@ class TestRegexp < Test::Unit::TestCase
def test_dup
assert_equal(//, //.dup)
- assert_raise(TypeError) { //.instance_eval { initialize_copy(nil) } }
+ assert_raise(TypeError) { //.dup.instance_eval { initialize_copy(nil) } }
end
def test_regsub
@@ -637,21 +999,22 @@ class TestRegexp < Test::Unit::TestCase
assert_equal('foobazquux/foobazquux', result, bug8856)
end
- def test_KCODE
- assert_nil($KCODE)
- assert_nothing_raised { $KCODE = nil }
- assert_equal(false, $=)
- assert_nothing_raised { $= = nil }
- end
+ def test_regsub_no_memory_leak
+ assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~"end;"}", rss: true)
+ code = proc do
+ "aaaaaaaaaaa".gsub(/a/, "")
+ end
- def test_KCODE_warning
- assert_warning(/variable \$KCODE is no longer effective; ignored/) { $KCODE = nil }
- assert_warning(/variable \$KCODE is no longer effective/) { $KCODE = nil }
+ 1_000.times(&code)
+ begin;
+ 100_000.times(&code)
+ end;
end
- def test_ignorecase_warning
- assert_warning(/variable \$= is no longer effective; ignored/) { $= = nil }
- assert_warning(/variable \$= is no longer effective/) { $= }
+ def test_ignorecase
+ v = assert_deprecated_warning(/variable \$= is no longer effective/) { $= }
+ assert_equal(false, v)
+ assert_deprecated_warning(/variable \$= is no longer effective; ignored/) { $= = nil }
end
def test_match_setter
@@ -667,11 +1030,18 @@ class TestRegexp < Test::Unit::TestCase
test = proc {|&blk| "abc".sub("a", ""); blk.call($~) }
bug10877 = '[ruby-core:68209] [Bug #10877]'
+ bug18160 = '[Bug #18160]'
test.call {|m| assert_raise_with_message(IndexError, /foo/, bug10877) {m["foo"]} }
key = "\u{3042}"
[Encoding::UTF_8, Encoding::Shift_JIS, Encoding::EUC_JP].each do |enc|
idx = key.encode(enc)
- test.call {|m| assert_raise_with_message(IndexError, /#{idx}/, bug10877) {m[idx]} }
+ pat = /#{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) }
@@ -713,11 +1083,13 @@ class TestRegexp < Test::Unit::TestCase
end
def test_rindex_regexp
- assert_equal(3, "foobarbaz\u3042".rindex(/b../n, 5))
+ # use eval because only one warning is shown for the same regexp literal
+ pat = eval('/b../n')
+ assert_equal(3, assert_warning(/binary regexp/) {"foobarbaz\u3042".rindex(pat, 5)})
end
def assert_regexp(re, ss, fs = [], msg = nil)
- re = Regexp.new(re) unless re.is_a?(Regexp)
+ re = EnvUtil.suppress_warning {Regexp.new(re)} unless re.is_a?(Regexp)
ss = [ss] unless ss.is_a?(Array)
ss.each do |e, s|
s ||= e
@@ -750,7 +1122,7 @@ class TestRegexp < Test::Unit::TestCase
check(/\A\80\z/, "80", ["\100", ""])
check(/\A\77\z/, "?")
check(/\A\78\z/, "\7" + '8', ["\100", ""])
- check(eval('/\A\Qfoo\E\z/'), "QfooE")
+ check(assert_warning(/Unknown escape/) {eval('/\A\Qfoo\E\z/')}, "QfooE")
check(/\Aa++\z/, "aaa")
check('\Ax]\z', "x]")
check(/x#foo/x, "x", "#foo")
@@ -794,8 +1166,8 @@ class TestRegexp < Test::Unit::TestCase
check(/^(A+|B(?>\g<1>)*)[AC]$/, %w(AAAC BBBAAAAC), %w(BBBAAA))
check(/^()(?>\g<1>)*$/, "", "a")
check(/^(?>(?=a)(#{ "a" * 1000 }|))++$/, ["a" * 1000, "a" * 2000, "a" * 3000], ["", "a" * 500, "b" * 1000])
- check(eval('/^(?:a?)?$/'), ["", "a"], ["aa"])
- check(eval('/^(?:a+)?$/'), ["", "a", "aa"], ["ab"])
+ check(assert_warning(/nested repeat operator/) {eval('/^(?:a?)?$/')}, ["", "a"], ["aa"])
+ check(assert_warning(/nested repeat operator/) {eval('/^(?:a+)?$/')}, ["", "a", "aa"], ["ab"])
check(/^(?:a?)+?$/, ["", "a", "aa"], ["ab"])
check(/^a??[ab]/, [["a", "a"], ["a", "aa"], ["b", "b"], ["a", "ab"]], ["c"])
check(/^(?:a*){3,5}$/, ["", "a", "aa", "aaa", "aaaa", "aaaaa", "aaaaaa"], ["b"])
@@ -924,13 +1296,13 @@ class TestRegexp < Test::Unit::TestCase
def test_posix_bracket
check(/\A[[:alpha:]0]\z/, %w(0 a), %w(1 .))
- check(eval('/\A[[:^alpha:]0]\z/'), %w(0 1 .), "a")
- check(eval('/\A[[:alpha\:]]\z/'), %w(a l p h a :), %w(b 0 1 .))
- check(eval('/\A[[:alpha:foo]0]\z/'), %w(0 a), %w(1 .))
+ check(assert_warning(/duplicated range/) {eval('/\A[[:^alpha:]0]\z/')}, %w(0 1 .), "a")
+ check(assert_warning(/duplicated range/) {eval('/\A[[:alpha\:]]\z/')}, %w(a l p h a :), %w(b 0 1 .))
+ check(assert_warning(/duplicated range/) {eval('/\A[[:alpha:foo]0]\z/')}, %w(0 a), %w(1 .))
check(/\A[[:xdigit:]&&[:alpha:]]\z/, "a", %w(g 0))
check('\A[[:abcdefghijklmnopqrstu:]]+\z', "[]")
failcheck('[[:alpha')
- failcheck('[[:alpha:')
+ assert_warning(/duplicated range/) {failcheck('[[:alpha:')}
failcheck('[[:alp:]]')
assert_match(/\A[[:digit:]]+\z/, "\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19")
@@ -938,27 +1310,35 @@ 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
- assert_match /\A\R\z/, "\r"
- assert_match /\A\R\z/, "\n"
- assert_match /\A\R\z/, "\r\n"
+ assert_match(/\A\R\z/, "\r")
+ assert_match(/\A\R\z/, "\n")
+ assert_match(/\A\R\z/, "\f")
+ assert_match(/\A\R\z/, "\v")
+ assert_match(/\A\R\z/, "\r\n")
+ assert_match(/\A\R\z/, "\u0085")
+ assert_match(/\A\R\z/, "\u2028")
+ assert_match(/\A\R\z/, "\u2029")
end
def test_cclass_X
- assert_match /\A\X\z/, "\u{20 200d}"
- assert_match /\A\X\z/, "\u{600 600}"
- assert_match /\A\X\z/, "\u{600 20}"
- assert_match /\A\X\z/, "\u{261d 1F3FB}"
- assert_match /\A\X\z/, "\u{1f600}"
- assert_match /\A\X\z/, "\u{20 308}"
- assert_match /\A\X\X\z/, "\u{a 308}"
- assert_match /\A\X\X\z/, "\u{d 308}"
- assert_match /\A\X\z/, "\u{1F477 1F3FF 200D 2640 FE0F}"
- assert_match /\A\X\z/, "\u{1F468 200D 1F393}"
- assert_match /\A\X\z/, "\u{1F46F 200D 2642 FE0F}"
- assert_match /\A\X\z/, "\u{1f469 200d 2764 fe0f 200d 1f469}"
+ assert_match(/\A\X\z/, "\u{20 200d}")
+ assert_match(/\A\X\z/, "\u{600 600}")
+ assert_match(/\A\X\z/, "\u{600 20}")
+ assert_match(/\A\X\z/, "\u{261d 1F3FB}")
+ assert_match(/\A\X\z/, "\u{1f600}")
+ assert_match(/\A\X\z/, "\u{20 324}")
+ assert_match(/\A\X\X\z/, "\u{a 324}")
+ assert_match(/\A\X\X\z/, "\u{d 324}")
+ assert_match(/\A\X\z/, "\u{1F477 1F3FF 200D 2640 FE0F}")
+ assert_match(/\A\X\z/, "\u{1F468 200D 1F393}")
+ assert_match(/\A\X\z/, "\u{1F46F 200D 2642 FE0F}")
+ assert_match(/\A\X\z/, "\u{1f469 200d 2764 fe0f 200d 1f469}")
assert_warning('') {/\X/ =~ "\u{a0}"}
end
@@ -988,6 +1368,8 @@ class TestRegexp < Test::Unit::TestCase
assert_raise(TypeError) { Regexp.allocate.names }
assert_raise(TypeError) { Regexp.allocate.named_captures }
+ assert_not_respond_to(MatchData, :allocate)
+=begin
assert_raise(TypeError) { MatchData.allocate.hash }
assert_raise(TypeError) { MatchData.allocate.regexp }
assert_raise(TypeError) { MatchData.allocate.names }
@@ -1010,6 +1392,7 @@ class TestRegexp < Test::Unit::TestCase
assert_raise(TypeError) { $` }
assert_raise(TypeError) { $' }
assert_raise(TypeError) { $+ }
+=end
end
def test_unicode
@@ -1050,22 +1433,223 @@ class TestRegexp < Test::Unit::TestCase
end
def test_unicode_age
- assert_match(/^\p{Age=6.0}$/u, "\u261c")
- assert_match(/^\p{Age=1.1}$/u, "\u261c")
- assert_no_match(/^\P{age=6.0}$/u, "\u261c")
+ assert_unicode_age("\u261c", matches: %w"6.0 1.1", unmatches: [])
+
+ assert_unicode_age("\u31f6", matches: %w"6.0 3.2", unmatches: %w"3.1 3.0 1.1")
+ assert_unicode_age("\u2754", matches: %w"6.0", unmatches: %w"5.0 4.0 3.0 2.0 1.1")
+
+ assert_unicode_age("\u32FF", matches: %w"12.1", unmatches: %w"12.0")
+ end
+
+ def test_unicode_age_14_0
+ @matches = %w"14.0"
+ @unmatches = %w"13.0"
+
+ assert_unicode_age("\u{10570}")
+ assert_unicode_age("\u9FFF")
+ assert_unicode_age("\u{2A6DF}")
+ assert_unicode_age("\u{2B738}")
+ end
+
+ def test_unicode_age_15_0
+ @matches = %w"15.0"
+ @unmatches = %w"14.0"
+
+ assert_unicode_age("\u{0CF3}",
+ "KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT")
+ assert_unicode_age("\u{0ECE}", "LAO YAMAKKAN")
+ assert_unicode_age("\u{10EFD}".."\u{10EFF}",
+ "ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA")
+ assert_unicode_age("\u{1123F}".."\u{11241}",
+ "KHOJKI LETTER QA..KHOJKI VOWEL SIGN VOCALIC R")
+ assert_unicode_age("\u{11B00}".."\u{11B09}",
+ "DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU")
+ assert_unicode_age("\u{11F00}".."\u{11F10}",
+ "KAWI SIGN CANDRABINDU..KAWI LETTER O")
+ assert_unicode_age("\u{11F12}".."\u{11F3A}",
+ "KAWI LETTER KA..KAWI VOWEL SIGN VOCALIC R")
+ assert_unicode_age("\u{11F3E}".."\u{11F59}",
+ "KAWI VOWEL SIGN E..KAWI DIGIT NINE")
+ assert_unicode_age("\u{1342F}",
+ "EGYPTIAN HIEROGLYPH V011D")
+ assert_unicode_age("\u{13439}".."\u{1343F}",
+ "EGYPTIAN HIEROGLYPH INSERT AT MIDDLE..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE")
+ assert_unicode_age("\u{13440}".."\u{13455}",
+ "EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED")
+ assert_unicode_age("\u{1B132}", "HIRAGANA LETTER SMALL KO")
+ assert_unicode_age("\u{1B155}", "KATAKANA LETTER SMALL KO")
+ assert_unicode_age("\u{1D2C0}".."\u{1D2D3}",
+ "KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN")
+ assert_unicode_age("\u{1DF25}".."\u{1DF2A}",
+ "LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK")
+ assert_unicode_age("\u{1E030}".."\u{1E06D}",
+ "MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE")
+ assert_unicode_age("\u{1E08F}",
+ "COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I")
+ assert_unicode_age("\u{1E4D0}".."\u{1E4F9}",
+ "NAG MUNDARI LETTER O..NAG MUNDARI DIGIT NINE")
+ assert_unicode_age("\u{1F6DC}", "WIRELESS")
+ assert_unicode_age("\u{1F774}".."\u{1F776}",
+ "LOT OF FORTUNE..LUNAR ECLIPSE")
+ assert_unicode_age("\u{1F77B}".."\u{1F77F}",
+ "HAUMEA..ORCUS")
+ assert_unicode_age("\u{1F7D9}", "NINE POINTED WHITE STAR")
+ assert_unicode_age("\u{1FA75}".."\u{1FA77}",
+ "LIGHT BLUE HEART..PINK HEART")
+ assert_unicode_age("\u{1FA87}".."\u{1FA88}",
+ "MARACAS..FLUTE")
+ assert_unicode_age("\u{1FAAD}".."\u{1FAAF}",
+ "FOLDING HAND FAN..KHANDA")
+ assert_unicode_age("\u{1FABB}".."\u{1FABD}",
+ "HYACINTH..WING")
+ assert_unicode_age("\u{1FABF}", "GOOSE")
+ assert_unicode_age("\u{1FACE}".."\u{1FACF}",
+ "MOOSE..DONKEY")
+ assert_unicode_age("\u{1FADA}".."\u{1FADB}",
+ "GINGER ROOT..PEA POD")
+ assert_unicode_age("\u{1FAE8}", "SHAKING FACE")
+ assert_unicode_age("\u{1FAF7}".."\u{1FAF8}",
+ "LEFTWARDS PUSHING HAND..RIGHTWARDS PUSHING HAND")
+ assert_unicode_age("\u{2B739}",
+ "CJK UNIFIED IDEOGRAPH-2B739")
+ assert_unicode_age("\u{31350}".."\u{323AF}",
+ "CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF")
+ end
+
+ def test_unicode_age_15_1
+ @matches = %w"15.1"
+ @unmatches = %w"15.0"
+
+ # https://www.unicode.org/Public/15.1.0/ucd/DerivedAge.txt
+ assert_unicode_age("\u{2FFC}".."\u{2FFF}",
+ "IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER ROTATION")
+ assert_unicode_age("\u{31EF}",
+ "IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION")
+ assert_unicode_age("\u{2EBF0}".."\u{2EE5D}",
+ "CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D")
+ end
+
+ def test_unicode_age_16_0
+ @matches = %w"16.0"
+ @unmatches = %w"15.1"
+
+ # https://www.unicode.org/Public/16.0.0/ucd/DerivedAge.txt
+ assert_unicode_age("\u{0897}",
+ "ARABIC PEPET")
+ assert_unicode_age("\u{1B4E}".."\u{1B4F}",
+ "BALINESE INVERTED CARIK SIKI..BALINESE INVERTED CARIK PAREREN")
+ assert_unicode_age("\u{1B7F}",
+ "BALINESE PANTI BAWAK")
+ assert_unicode_age("\u{1C89}".."\u{1C8A}",
+ "CYRILLIC CAPITAL LETTER TJE..CYRILLIC SMALL LETTER TJE")
+ assert_unicode_age("\u{2427}".."\u{2429}",
+ "SYMBOL FOR DELETE SQUARE CHECKER BOARD FORM..SYMBOL FOR DELETE MEDIUM SHADE FORM")
+ assert_unicode_age("\u{31E4}".."\u{31E5}",
+ "CJK STROKE HXG..CJK STROKE SZP")
+ assert_unicode_age("\u{A7CB}".."\u{A7CD}",
+ "LATIN CAPITAL LETTER RAMS HORN..LATIN SMALL LETTER S WITH DIAGONAL STROKE")
+ assert_unicode_age("\u{A7DA}".."\u{A7DC}",
+ "LATIN CAPITAL LETTER LAMBDA..LATIN CAPITAL LETTER LAMBDA WITH STROKE")
+ assert_unicode_age("\u{105C0}".."\u{105F3}",
+ "TODHRI LETTER A..TODHRI LETTER OO")
+ assert_unicode_age("\u{10D40}".."\u{10D65}",
+ "GARAY DIGIT ZERO..GARAY CAPITAL LETTER OLD NA")
+ assert_unicode_age("\u{10D69}".."\u{10D85}",
+ "GARAY VOWEL SIGN E..GARAY SMALL LETTER OLD NA")
+ assert_unicode_age("\u{10D8E}".."\u{10D8F}",
+ "GARAY PLUS SIGN..GARAY MINUS SIGN")
+ assert_unicode_age("\u{10EC2}".."\u{10EC4}",
+ "ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW")
+ assert_unicode_age("\u{10EFC}",
+ "ARABIC COMBINING ALEF OVERLAY")
+ assert_unicode_age("\u{11380}".."\u{11389}",
+ "TULU-TIGALARI LETTER A..TULU-TIGALARI LETTER VOCALIC LL")
+ assert_unicode_age("\u{1138B}",
+ "TULU-TIGALARI LETTER EE")
+ assert_unicode_age("\u{1138E}",
+ "TULU-TIGALARI LETTER AI")
+ assert_unicode_age("\u{11390}".."\u{113B5}",
+ "TULU-TIGALARI LETTER OO..TULU-TIGALARI LETTER LLLA")
+ assert_unicode_age("\u{113B7}".."\u{113C0}",
+ "TULU-TIGALARI SIGN AVAGRAHA..TULU-TIGALARI VOWEL SIGN VOCALIC LL")
+ assert_unicode_age("\u{113C2}",
+ "TULU-TIGALARI VOWEL SIGN EE")
+ assert_unicode_age("\u{113C5}",
+ "TULU-TIGALARI VOWEL SIGN AI")
+ assert_unicode_age("\u{113C7}".."\u{113CA}",
+ "TULU-TIGALARI VOWEL SIGN OO..TULU-TIGALARI SIGN CANDRA ANUNASIKA")
+ assert_unicode_age("\u{113CC}".."\u{113D5}",
+ "TULU-TIGALARI SIGN ANUSVARA..TULU-TIGALARI DOUBLE DANDA")
+ assert_unicode_age("\u{113D7}".."\u{113D8}",
+ "TULU-TIGALARI SIGN OM PUSHPIKA..TULU-TIGALARI SIGN SHRII PUSHPIKA")
+ assert_unicode_age("\u{113E1}".."\u{113E2}",
+ "TULU-TIGALARI VEDIC TONE SVARITA..TULU-TIGALARI VEDIC TONE ANUDATTA")
+ assert_unicode_age("\u{116D0}".."\u{116E3}",
+ "MYANMAR PAO DIGIT ZERO..MYANMAR EASTERN PWO KAREN DIGIT NINE")
+ assert_unicode_age("\u{11BC0}".."\u{11BE1}",
+ "SUNUWAR LETTER DEVI..SUNUWAR SIGN PVO")
+ assert_unicode_age("\u{11BF0}".."\u{11BF9}",
+ "SUNUWAR DIGIT ZERO..SUNUWAR DIGIT NINE")
+ assert_unicode_age("\u{11F5A}",
+ "KAWI SIGN NUKTA")
+ assert_unicode_age("\u{13460}".."\u{143FA}",
+ "EGYPTIAN HIEROGLYPH-13460..EGYPTIAN HIEROGLYPH-143FA")
+ assert_unicode_age("\u{16100}".."\u{16139}",
+ "GURUNG KHEMA LETTER A..GURUNG KHEMA DIGIT NINE")
+ assert_unicode_age("\u{16D40}".."\u{16D79}",
+ "KIRAT RAI SIGN ANUSVARA..KIRAT RAI DIGIT NINE")
+ assert_unicode_age("\u{18CFF}",
+ "KHITAN SMALL SCRIPT CHARACTER-18CFF")
+ assert_unicode_age("\u{1CC00}".."\u{1CCF9}",
+ "UP-POINTING GO-KART..OUTLINED DIGIT NINE")
+ assert_unicode_age("\u{1CD00}".."\u{1CEB3}",
+ "BLOCK OCTANT-3..BLACK RIGHT TRIANGLE CARET")
+ assert_unicode_age("\u{1E5D0}".."\u{1E5FA}",
+ "OL ONAL LETTER O..OL ONAL DIGIT NINE")
+ assert_unicode_age("\u{1E5FF}",
+ "OL ONAL ABBREVIATION SIGN")
+ assert_unicode_age("\u{1F8B2}".."\u{1F8BB}",
+ "RIGHTWARDS ARROW WITH LOWER HOOK..SOUTH WEST ARROW FROM BAR")
+ assert_unicode_age("\u{1F8C0}".."\u{1F8C1}",
+ "LEFTWARDS ARROW FROM DOWNWARDS ARROW..RIGHTWARDS ARROW FROM DOWNWARDS ARROW")
+ assert_unicode_age("\u{1FA89}",
+ "HARP")
+ assert_unicode_age("\u{1FA8F}",
+ "SHOVEL")
+ assert_unicode_age("\u{1FABE}",
+ "LEAFLESS TREE")
+ assert_unicode_age("\u{1FAC6}",
+ "FINGERPRINT")
+ assert_unicode_age("\u{1FADC}",
+ "ROOT VEGETABLE")
+ assert_unicode_age("\u{1FADF}",
+ "SPLATTER")
+ assert_unicode_age("\u{1FAE9}",
+ "FACE WITH BAGS UNDER EYES")
+ assert_unicode_age("\u{1FBCB}".."\u{1FBEF}",
+ "WHITE CROSS MARK..TOP LEFT JUSTIFIED LOWER RIGHT QUARTER BLACK CIRCLE")
+ end
+
+ UnicodeAgeRegexps = Hash.new do |h, age|
+ h[age] = [/\A\p{age=#{age}}+\z/u, /\A\P{age=#{age}}+\z/u].freeze
+ end
+
+ def assert_unicode_age(char, mesg = nil, matches: @matches, unmatches: @unmatches)
+ if Range === char
+ char = char.to_a.join("")
+ end
- assert_match(/^\p{age=6.0}$/u, "\u31f6")
- assert_match(/^\p{age=3.2}$/u, "\u31f6")
- assert_no_match(/^\p{age=3.1}$/u, "\u31f6")
- assert_no_match(/^\p{age=3.0}$/u, "\u31f6")
- assert_no_match(/^\p{age=1.1}$/u, "\u31f6")
+ matches.each do |age|
+ pos, neg = UnicodeAgeRegexps[age]
+ assert_match(pos, char, mesg)
+ assert_not_match(neg, char, mesg)
+ end
- assert_match(/^\p{age=6.0}$/u, "\u2754")
- assert_no_match(/^\p{age=5.0}$/u, "\u2754")
- assert_no_match(/^\p{age=4.0}$/u, "\u2754")
- assert_no_match(/^\p{age=3.0}$/u, "\u2754")
- assert_no_match(/^\p{age=2.0}$/u, "\u2754")
- assert_no_match(/^\p{age=1.1}$/u, "\u2754")
+ unmatches.each do |age|
+ pos, neg = UnicodeAgeRegexps[age]
+ assert_not_match(pos, char, mesg)
+ assert_match(neg, char, mesg)
+ end
end
MatchData_A = eval("class MatchData_\u{3042} < MatchData; self; end")
@@ -1076,21 +1660,51 @@ class TestRegexp < Test::Unit::TestCase
assert_equal(a, b, '[ruby-core:24748]')
h = {a => 42}
assert_equal(42, h[b], '[ruby-core:24748]')
+=begin
assert_match(/#<TestRegexp::MatchData_\u{3042}:/, MatchData_A.allocate.inspect)
+=end
h = /^(?<@time>\d+): (?<body>.*)/.match("123456: hoge fuga")
assert_equal("123456", h["@time"])
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
- assert_nothing_raised { eval("a = 1; /\#{ a }/; a") }
- assert_nothing_raised { eval("a = 1; /\#{ a }/o; a") }
+ EnvUtil.suppress_warning do
+ assert_nothing_raised { eval("a = 1; /\#{ a }/; a") }
+ assert_nothing_raised { eval("a = 1; /\#{ a }/o; a") }
+ end
end
def test_invalid_fragment
bug2547 = '[ruby-core:27374]'
- assert_raise(SyntaxError, bug2547) {eval('/#{"\\\\"}y/')}
+ assert_raise(SyntaxError, bug2547) do
+ assert_warning(/ignored/) {eval('/#{"\\\\"}y/')}
+ end
end
def test_dup_warn
@@ -1112,6 +1726,8 @@ class TestRegexp < Test::Unit::TestCase
bug8151 = '[ruby-core:53649]'
assert_warning(/\A\z/, bug8151) { Regexp.new('(?:[\u{33}])').to_s }
+
+ assert_warning(%r[/.*/\Z]) { Regexp.new("[\n\n]") }
end
def test_property_warn
@@ -1128,7 +1744,8 @@ class TestRegexp < Test::Unit::TestCase
def test_raw_hyphen_and_tk_char_type_after_range
bug6853 = '[ruby-core:47115]'
# use Regexp.new instead of literal to ignore a parser warning.
- check(Regexp.new('[0-1-\\s]'), [' ', '-'], ['2', 'a'], bug6853)
+ re = assert_warning(/without escape/) {Regexp.new('[0-1-\\s]')}
+ check(re, [' ', '-'], ['2', 'a'], bug6853)
end
def test_error_message_on_failed_conversion
@@ -1150,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]])
@@ -1164,6 +1808,20 @@ class TestRegexp < Test::Unit::TestCase
}
end
+ def test_quantifier_reduction
+ assert_equal('aa', eval('/(a+?)*/').match('aa')[0])
+ assert_equal('aa', eval('/(?:a+?)*/').match('aa')[0])
+
+ quantifiers = %w'? * + ?? *? +?'
+ quantifiers.product(quantifiers) do |q1, q2|
+ EnvUtil.suppress_warning do
+ r1 = eval("/(a#{q1})#{q2}/").match('aa')[0]
+ r2 = eval("/(?:a#{q1})#{q2}/").match('aa')[0]
+ assert_equal(r1, r2)
+ end
+ end
+ end
+
def test_once
pr1 = proc{|i| /#{i}/o}
assert_equal(/0/, pr1.call(0))
@@ -1247,6 +1905,36 @@ class TestRegexp < Test::Unit::TestCase
assert_equal(0, /(?~(a)c)/ =~ "abb")
assert_nil($1)
+
+ assert_equal(0, /(?~(a))/ =~ "")
+ assert_nil($1)
+ end
+
+ def test_backref_overrun
+ assert_raise_with_message(SyntaxError, /invalid backref number/) do
+ eval(%["".match(/(())(?<X>)((?(90000)))/)])
+ end
+ end
+
+ def test_bug18631
+ assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaaa")
+ assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaa")
+ assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaab")
+ end
+
+ def test_invalid_group
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ assert_raise_with_message(RegexpError, /invalid conditional pattern/) do
+ Regexp.new("((?(1)x|x|)x)+")
+ end
+ 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.
@@ -1278,4 +1966,353 @@ class TestRegexp < Test::Unit::TestCase
}
assert_empty(errs, msg)
end
+
+ def test_s_timeout
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(0.2).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_in_delta(timeout, Regexp.timeout, timeout * 2 * Float::EPSILON)
+
+ t = Time.now
+ assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do
+ # A typical ReDoS case
+ /^(a*)*\1$/ =~ "a" * 1000000 + "x"
+ end
+ t = Time.now - t
+
+ assert_operator(timeout, :<=, [timeout * 1.5, 1].max)
+ end;
+ end
+
+ def test_s_timeout_corner_cases
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ assert_nil(Regexp.timeout)
+
+ # This is just an implementation detail that users should not depend on:
+ # If Regexp.timeout is set to a value greater than the value that can be
+ # represented in the internal representation of timeout, it uses the
+ # maximum value that can be represented.
+ Regexp.timeout = Float::INFINITY
+ assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout)
+
+ Regexp.timeout = 1e300
+ assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout)
+
+ assert_raise(ArgumentError) { Regexp.timeout = 0 }
+ assert_raise(ArgumentError) { Regexp.timeout = -1 }
+
+ Regexp.timeout = nil
+ assert_nil(Regexp.timeout)
+ end;
+ end
+
+ def test_s_timeout_memory_leak
+ assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~"end;"}", "[Bug #20228]", rss: true)
+ Regexp.timeout = 0.001
+ regex = /^(a*)*$/
+ str = "a" * 1000000 + "x"
+
+ code = proc do
+ regex =~ str
+ rescue
+ end
+
+ 10.times(&code)
+ begin;
+ 1_000.times(&code)
+ end;
+ end
+
+ def test_bug_20453
+ re = Regexp.new("^(a*)x$", timeout: 0.001)
+
+ assert_raise(Regexp::TimeoutError) do
+ re =~ "a" * 1000000 + "x"
+ end
+ end
+
+ def test_bug_20886
+ re = Regexp.new("d()*+|a*a*bc", timeout: 0.02)
+ assert_raise(Regexp::TimeoutError) do
+ re === "b" + "a" * 1000
+ end
+ end
+
+ def per_instance_redos_test(global_timeout, per_instance_timeout, expected_timeout)
+ 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 }
+ begin;
+ Regexp.timeout = global_timeout
+
+ re = Regexp.new("^(a*)\\1b?a*$", timeout: per_instance_timeout)
+ if per_instance_timeout
+ assert_in_delta(per_instance_timeout, re.timeout, per_instance_timeout * 2 * Float::EPSILON)
+ else
+ assert_nil(re.timeout)
+ end
+
+ t = Time.now
+ assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do
+ re =~ "a" * 1000000 + "x"
+ end
+ t = Time.now - t
+
+ assert_in_delta(expected_timeout, t, expected_timeout * 3 / 4)
+ end;
+ end
+
+ def test_timeout_shorter_than_global
+ omit "timeout test is too unstable on s390x" if RUBY_PLATFORM =~ /s390x/
+ per_instance_redos_test(10, 0.5, 0.5)
+ end
+
+ def test_timeout_longer_than_global
+ omit "timeout test is too unstable on s390x" if RUBY_PLATFORM =~ /s390x/
+ per_instance_redos_test(0.01, 0.5, 0.5)
+ end
+
+ def test_timeout_nil
+ per_instance_redos_test(0.5, nil, 0.5)
+ end
+
+ def test_timeout_corner_cases
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ assert_nil(//.timeout)
+
+ # This is just an implementation detail that users should not depend on:
+ # If Regexp.timeout is set to a value greater than the value that can be
+ # represented in the internal representation of timeout, it uses the
+ # maximum value that can be represented.
+ assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: Float::INFINITY).timeout)
+
+ assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: 1e300).timeout)
+
+ assert_raise(ArgumentError) { Regexp.new("foo", timeout: 0) }
+ assert_raise(ArgumentError) { Regexp.new("foo", timeout: -1) }
+ end;
+ end
+
+ def test_timeout_memory_leak
+ assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", "[Bug #20650]", timeout: 100, rss: true)
+ regex = Regexp.new("^#{"(a*)" * 10_000}x$", timeout: 0.000001)
+ str = "a" * 1_000_000 + "x"
+
+ code = proc do
+ regex =~ str
+ rescue
+ end
+
+ 10.times(&code)
+ begin;
+ 1_000.times(&code)
+ end;
+ end
+
+ def test_match_cache_exponential
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/^(a*)*$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_square
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/^a*b?a*$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_atomic
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/^a*?(?>a*a*)$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_atomic_complex
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/a*(?>a*)ab/ =~ "a" * 1000000 + "b")
+ end;
+ end
+
+ def test_match_cache_positive_look_ahead
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", timeout: 30)
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/^a*?(?=a*a*)$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_positive_look_ahead_complex
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", timeout: 30)
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_equal(/(?:(?=a*)a)*/ =~ "a" * 1000000, 0)
+ end;
+ end
+
+ def test_match_cache_negative_look_ahead
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/^a*?(?!a*a*)$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_positive_look_behind
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/(?<=abc|def)(a|a)*$/ =~ "abc" + "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_negative_look_behind
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/(?<!x)(a|a)*$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_with_peek_optimization
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/a+z/ =~ "a" * 1000000 + "xz")
+ end;
+ end
+
+ def test_cache_opcodes_initialize
+ str = 'test1-test2-test3-test4-test_5'
+ re = '^([0-9a-zA-Z\-/]*){1,256}$'
+ 100.times do
+ assert !Regexp.new(re).match?(str)
+ end
+ end
+
+ def test_bug_19273 # [Bug #19273]
+ pattern = /(?:(?:-?b)|(?:-?(?:1_?(?:0_?)*)?0))(?::(?:(?:-?b)|(?:-?(?:1_?(?:0_?)*)?0))){0,3}/
+ assert_equal("10:0:0".match(pattern)[0], "10:0:0")
+ end
+
+ def test_bug_19467 # [Bug #19467]
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+
+ assert_nil(/\A.*a.*z\z/ =~ "a" * 1000000 + "y")
+ end;
+ end
+
+ def test_bug_19476 # [Bug #19476]
+ assert_equal("123456789".match(/(?:x?\dx?){2,10}/)[0], "123456789")
+ assert_equal("123456789".match(/(?:x?\dx?){2,}/)[0], "123456789")
+ end
+
+ def test_encoding_flags_are_preserved_when_initialized_with_another_regexp
+ re = Regexp.new("\u2018hello\u2019".encode("UTF-8"))
+ str = "".encode("US-ASCII")
+
+ assert_nothing_raised do
+ str.match?(re)
+ str.match?(Regexp.new(re))
+ end
+ end
+
+ def test_bug_19537 # [Bug #19537]
+ str = 'aac'
+ re = '^([ab]{1,3})(a?)*$'
+ 100.times do
+ assert !Regexp.new(re).match?(str)
+ end
+ end
+
+ def test_bug_20083 # [Bug #20083]
+ re = /([\s]*ABC)$/i
+ (1..100).each do |n|
+ text = "#{"0" * n}ABC"
+ assert text.match?(re)
+ end
+ end
+
+ def test_bug_20098 # [Bug #20098]
+ assert(/a((.|.)|bc){,4}z/.match? 'abcbcbcbcz')
+ assert(/a(b+?c*){4,5}z/.match? 'abbbccbbbccbcbcz')
+ assert(/a(b+?(.|.)){2,3}z/.match? 'abbbcbbbcbbbcz')
+ assert(/a(b*?(.|.)[bc]){2,5}z/.match? 'abcbbbcbcccbcz')
+ assert(/^(?:.+){2,4}?b|b/.match? "aaaabaa")
+ end
+
+ def test_bug_20207 # [Bug #20207]
+ assert(!'clan'.match?(/(?=.*a)(?!.*n)/))
+ end
+
+ def test_bug_20212 # [Bug #20212]
+ regex = Regexp.new(
+ /\A((?=.*?[a-z])(?!.*--)[a-z\d]+[a-z\d-]*[a-z\d]+).((?=.*?[a-z])(?!.*--)[a-z\d]+[a-z\d-]*[a-z\d]+).((?=.*?[a-z])(?!.*--)[a-z]+[a-z-]*[a-z]+).((?=.*?[a-z])(?!.*--)[a-z]+[a-z-]*[a-z]+)\Z/x
+ )
+ string = "www.google.com"
+ 100.times.each { assert(regex.match?(string)) }
+ end
+
+ def test_bug_20246 # [Bug #20246]
+ assert_equal '1.2.3', '1.2.3'[/(\d+)(\.\g<1>){2}/]
+ assert_equal '1.2.3', '1.2.3'[/((?:\d|foo|bar)+)(\.\g<1>){2}/]
+ end
+
+ def test_linear_time_p
+ assert_send [Regexp, :linear_time?, /a/]
+ assert_send [Regexp, :linear_time?, 'a']
+ assert_send [Regexp, :linear_time?, 'a', Regexp::IGNORECASE]
+ assert_not_send [Regexp, :linear_time?, /(a)\1/]
+ assert_not_send [Regexp, :linear_time?, "(a)\\1"]
+
+ assert_not_send [Regexp, :linear_time?, /(?=(a))/]
+ assert_not_send [Regexp, :linear_time?, /(?!(a))/]
+
+ assert_raise(TypeError) {Regexp.linear_time?(nil)}
+ assert_raise(TypeError) {Regexp.linear_time?(Regexp.allocate)}
+ end
+
+ def test_linear_performance
+ pre = ->(n) {[Regexp.new("a?" * n + "a" * n), "a" * n]}
+ assert_linear_performance([10, 29], pre: pre) do |re, s|
+ re =~ s
+ end
+ end
+
+ def test_bug_16145_and_bug_21176_caseinsensitive_small # [Bug#16145] [Bug#21176]
+ encodings = [Encoding::UTF_8, Encoding::ISO_8859_1]
+ encodings.each do |enc|
+ o_acute_lower = "\u00F3".encode(enc)
+ o_acute_upper = "\u00D3".encode(enc)
+ assert_match(/[x#{o_acute_lower}]/i, "abc#{o_acute_upper}", "should match o acute case insensitive")
+
+ e_acute_lower = "\u00E9".encode(enc)
+ e_acute_upper = "\u00C9".encode(enc)
+ assert_match(/[x#{e_acute_lower}]/i, "CAF#{e_acute_upper}", "should match e acute case insensitive")
+ end
+ end
end
diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb
index 28cf686a26..0067a49700 100644
--- a/test/ruby/test_require.rb
+++ b/test/ruby/test_require.rb
@@ -6,11 +6,27 @@ require 'tmpdir'
class TestRequire < Test::Unit::TestCase
def test_load_error_path
- filename = "should_not_exist"
- error = assert_raise(LoadError) do
- require filename
- end
- assert_equal filename, error.path
+ Tempfile.create(["should_not_exist", ".rb"]) {|t|
+ filename = t.path
+ t.close
+ File.unlink(filename)
+
+ error = assert_raise(LoadError) do
+ require filename
+ end
+ assert_equal filename, error.path
+
+ # with --disable=gems
+ assert_separately(["-", filename], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ filename = ARGV[0]
+ path = Struct.new(:to_path).new(filename)
+ error = assert_raise(LoadError) do
+ require path
+ end
+ assert_equal filename, error.path
+ end;
+ }
end
def test_require_invalid_shared_object
@@ -52,7 +68,8 @@ class TestRequire < Test::Unit::TestCase
def test_require_nonascii
bug3758 = '[ruby-core:31915]'
["\u{221e}", "\x82\xa0".force_encoding("cp932")].each do |path|
- assert_raise_with_message(LoadError, /#{path}\z/, bug3758) {require path}
+ e = assert_raise(LoadError, bug3758) {require path}
+ assert_operator(e.message, :end_with?, path, bug3758)
end
end
@@ -62,12 +79,6 @@ class TestRequire < Test::Unit::TestCase
assert_require_nonascii_path(encoding, bug8165)
end
- def test_require_insecure_path
- assert_require_insecure_path("foo")
- encoding = 'filesystem'
- assert_require_insecure_path(nil, encoding)
- end
-
def test_require_nonascii_path_utf8
bug8676 = '[ruby-core:56136] [Bug #8676]'
encoding = Encoding::UTF_8
@@ -75,12 +86,6 @@ class TestRequire < Test::Unit::TestCase
assert_require_nonascii_path(encoding, bug8676)
end
- def test_require_insecure_path_utf8
- encoding = Encoding::UTF_8
- return if Encoding.find('filesystem') == encoding
- assert_require_insecure_path(nil, encoding)
- end
-
def test_require_nonascii_path_shift_jis
bug8676 = '[ruby-core:56136] [Bug #8676]'
encoding = Encoding::Shift_JIS
@@ -88,12 +93,6 @@ class TestRequire < Test::Unit::TestCase
assert_require_nonascii_path(encoding, bug8676)
end
- def test_require_insecure_path_shift_jis
- encoding = Encoding::Shift_JIS
- return if Encoding.find('filesystem') == encoding
- assert_require_insecure_path(nil, encoding)
- end
-
case RUBY_PLATFORM
when /cygwin/, /mswin/, /mingw/, /darwin/
def self.ospath_encoding(path)
@@ -105,22 +104,13 @@ class TestRequire < Test::Unit::TestCase
end
end
- SECURITY_WARNING =
- if /mswin|mingw/ =~ RUBY_PLATFORM
- nil
- else
- proc do |require_path|
- $SAFE = 1
- require(require_path)
- end
- end
-
def prepare_require_path(dir, encoding)
+ require 'enc/trans/single_byte'
Dir.mktmpdir {|tmp|
begin
require_path = File.join(tmp, dir, 'foo.rb').encode(encoding)
rescue
- skip "cannot convert path encoding to #{encoding}"
+ omit "cannot convert path encoding to #{encoding}"
end
Dir.mkdir(File.dirname(require_path))
open(require_path, "wb") {|f| f.puts '$:.push __FILE__'}
@@ -151,31 +141,6 @@ class TestRequire < Test::Unit::TestCase
}
end
- def assert_require_insecure_path(dirname, encoding = nil)
- return unless SECURITY_WARNING
- dirname ||= "\u3042" * 5
- encoding ||= dirname.encoding
- prepare_require_path(dirname, encoding) {|require_path|
- require_path.untaint
- require(require_path)
- $".pop
- File.chmod(0777, File.dirname(require_path))
- ospath = (require_path.encode('filesystem') rescue
- require_path.encode(self.class.ospath_encoding(require_path)))
- e = nil
- stderr = EnvUtil.verbose_warning do
- e = assert_raise(SecurityError) do
- SECURITY_WARNING.call(require_path)
- end
- end
- assert_include(e.message, "loading from unsafe path")
- assert_include(stderr, "Insecure world writable dir")
- require_path = require_path.encode(self.class.ospath_encoding(require_path))
- assert_include(e.message, require_path)
- assert_include(stderr, File.dirname(require_path))
- }
- end
-
def test_require_path_home_1
env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"]
pathname_too_long = /pathname too long \(ignored\).*\(LoadError\)/m
@@ -227,7 +192,7 @@ class TestRequire < Test::Unit::TestCase
t.close
path = File.expand_path(t.path).sub(/\A(\w):/, '//127.0.0.1/\1$')
- skip "local drive #$1: is not shared" unless File.exist?(path)
+ omit "local drive #$1: is not shared" unless File.exist?(path)
args = ['--disable-gems', "-I#{File.dirname(path)}"]
assert_in_out_err(args, "#{<<~"END;"}", [path], [])
begin
@@ -244,7 +209,7 @@ class TestRequire < Test::Unit::TestCase
File.write(req, "p :ok\n")
assert_file.exist?(req)
req[/.rb$/i] = ""
- assert_in_out_err(['--disable-gems'], <<-INPUT, %w(:ok), [])
+ assert_in_out_err([], <<-INPUT, %w(:ok), [])
require "#{req}"
require "#{req}"
INPUT
@@ -252,21 +217,31 @@ class TestRequire < Test::Unit::TestCase
end
def assert_syntax_error_backtrace
+ loaded_features = $LOADED_FEATURES.dup
Dir.mktmpdir do |tmp|
req = File.join(tmp, "test.rb")
- File.write(req, "'\n")
- e = assert_raise_with_message(SyntaxError, /unterminated/) {
+ File.write(req, ",\n")
+ e = assert_raise_with_message(SyntaxError, /unexpected/) {
yield req
}
- assert_not_nil(bt = e.backtrace)
- assert_not_empty(bt.find_all {|b| b.start_with? __FILE__})
+ assert_not_nil(bt = e.backtrace, "no backtrace")
+ assert_not_empty(bt.find_all {|b| b.start_with? __FILE__}, proc {bt.inspect})
end
+ ensure
+ $LOADED_FEATURES.replace loaded_features
end
def test_require_syntax_error
assert_syntax_error_backtrace {|req| require req}
end
+ def test_require_syntax_error_rescued
+ assert_syntax_error_backtrace do |req|
+ assert_raise_with_message(SyntaxError, /unexpected/) {require req}
+ require req
+ end
+ end
+
def test_load_syntax_error
assert_syntax_error_backtrace {|req| load req}
end
@@ -382,6 +357,39 @@ class TestRequire < Test::Unit::TestCase
}
end
+ def test_require_in_wrapped_load
+ Dir.mktmpdir do |tmp|
+ File.write("#{tmp}/1.rb", "require_relative '2'\n")
+ File.write("#{tmp}/2.rb", "class Foo\n""end\n")
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ path = ""#{tmp.dump}"/1.rb"
+ begin;
+ load path, true
+ assert_instance_of(Class, Foo)
+ end;
+ end
+ end
+
+ def test_public_in_wrapped_load
+ Tempfile.create(["test_public_in_wrapped_load", ".rb"]) do |t|
+ t.puts "def foo; end", "public :foo"
+ t.close
+ assert_warning(/main\.public/) do
+ assert load(t.path, true)
+ end
+ end
+ end
+
+ def test_private_in_wrapped_load
+ Tempfile.create(["test_private_in_wrapped_load", ".rb"]) do |t|
+ t.puts "def foo; end", "private :foo"
+ t.close
+ assert_warning(/main\.private/) do
+ assert load(t.path, true)
+ end
+ end
+ end
+
def test_load_scope
bug1982 = '[ruby-core:25039] [Bug #1982]'
Tempfile.create(["test_ruby_test_require", ".rb"]) {|t|
@@ -397,19 +405,51 @@ class TestRequire < Test::Unit::TestCase
}
end
+ def test_load_into_module
+ Tempfile.create(["test_ruby_test_require", ".rb"]) {|t|
+ t.puts "def b; 1 end"
+ t.puts "class Foo"
+ t.puts " def c; 2 end"
+ t.puts "end"
+ t.close
+
+ m = Module.new
+ load(t.path, m)
+ assert_equal([:b], m.private_instance_methods(false))
+ c = Class.new do
+ include m
+ public :b
+ end
+ assert_equal(1, c.new.b)
+ assert_equal(2, m::Foo.new.c)
+ }
+ end
+
+ def test_load_wrap_nil
+ Dir.mktmpdir do |tmp|
+ File.write("#{tmp}/1.rb", "class LoadWrapNil; end\n")
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ path = ""#{tmp.dump}"/1.rb"
+ begin;
+ load path, nil
+ assert_instance_of(Class, LoadWrapNil)
+ end;
+ end
+ end
+
def test_load_ospath
bug = '[ruby-list:49994] path in ospath'
base = "test_load\u{3042 3044 3046 3048 304a}".encode(Encoding::Windows_31J)
path = nil
- Tempfile.create([base, ".rb"]) do |t|
- path = t.path
-
+ Dir.mktmpdir do |dir|
+ path = File.join(dir, base+".rb")
assert_raise_with_message(LoadError, /#{base}/) {
- load(File.join(File.dirname(path), base))
+ load(File.join(dir, base))
}
- t.puts "warn 'ok'"
- t.close
+ File.open(path, "w+b") do |t|
+ t.puts "warn 'ok'"
+ end
assert_include(path, base)
assert_warn("ok\n", bug) {
assert_nothing_raised(LoadError, bug) {
@@ -419,40 +459,10 @@ class TestRequire < Test::Unit::TestCase
end
end
- def test_tainted_loadpath
- Tempfile.create(["test_ruby_test_require", ".rb"]) {|t|
- abs_dir, file = File.split(t.path)
- abs_dir = File.expand_path(abs_dir).untaint
-
- assert_separately([], <<-INPUT)
- abs_dir = "#{ abs_dir }"
- $: << abs_dir
- assert_nothing_raised {require "#{ file }"}
- INPUT
-
- assert_separately([], <<-INPUT)
- abs_dir = "#{ abs_dir }"
- $: << abs_dir.taint
- assert_nothing_raised {require "#{ file }"}
- INPUT
-
- assert_separately([], <<-INPUT)
- abs_dir = "#{ abs_dir }"
- $: << abs_dir.taint
- $SAFE = 1
- assert_raise(SecurityError) {require "#{ file }"}
- INPUT
-
- assert_separately([], <<-INPUT)
- abs_dir = "#{ abs_dir }"
- $: << abs_dir << 'elsewhere'.taint
- assert_nothing_raised {require "#{ file }"}
- INPUT
- }
- end
-
def test_relative
load_path = $:.dup
+ loaded_featrures = $LOADED_FEATURES.dup
+
$:.delete(".")
Dir.mktmpdir do |tmp|
Dir.chdir(tmp) do
@@ -472,6 +482,7 @@ class TestRequire < Test::Unit::TestCase
end
ensure
$:.replace(load_path) if load_path
+ $LOADED_FEATURES.replace loaded_featrures
end
def test_relative_symlink
@@ -487,7 +498,33 @@ class TestRequire < Test::Unit::TestCase
result = IO.popen([EnvUtil.rubybin, "b/tst.rb"], &:read)
assert_equal("a/lib.rb\n", result, "[ruby-dev:40040]")
rescue NotImplementedError, Errno::EACCES
- skip "File.symlink is not implemented"
+ omit "File.symlink is not implemented"
+ end
+ }
+ }
+ end
+
+ def test_relative_symlink_realpath
+ Dir.mktmpdir {|tmp|
+ Dir.chdir(tmp) {
+ Dir.mkdir "a"
+ File.open("a/a.rb", "w") {|f| f.puts 'require_relative "b"' }
+ File.open("a/b.rb", "w") {|f| f.puts '$t += 1' }
+ Dir.mkdir "b"
+ File.binwrite("c.rb", <<~RUBY)
+ $t = 0
+ $:.unshift(File.expand_path('../b', __FILE__))
+ require "b"
+ require "a"
+ print $t
+ RUBY
+ begin
+ File.symlink("../a/a.rb", "b/a.rb")
+ File.symlink("../a/b.rb", "b/b.rb")
+ result = IO.popen([EnvUtil.rubybin, "c.rb"], &:read)
+ assert_equal("1", result, "bug17885 [ruby-core:104010]")
+ rescue NotImplementedError, Errno::EACCES
+ omit "File.symlink is not implemented"
end
}
}
@@ -495,7 +532,7 @@ class TestRequire < Test::Unit::TestCase
def test_frozen_loaded_features
bug3756 = '[ruby-core:31913]'
- assert_in_out_err(['-e', '$LOADED_FEATURES.freeze; require "ostruct"'], "",
+ assert_in_out_err(['-e', '$LOADED_FEATURES.freeze; require "erb"'], "",
[], /\$LOADED_FEATURES is frozen; cannot append feature \(RuntimeError\)$/,
bug3756)
end
@@ -537,7 +574,7 @@ class TestRequire < Test::Unit::TestCase
t1 = Thread.new do
Thread.pass until start
begin
- require(path)
+ Kernel.send(:require, path)
rescue RuntimeError
end
@@ -546,7 +583,7 @@ class TestRequire < Test::Unit::TestCase
t2 = Thread.new do
Thread.pass until scratch[0]
- t2_res = require(path)
+ t2_res = Kernel.send(:require, path)
end
t1[:scratch] = t2[:scratch] = scratch
@@ -563,9 +600,6 @@ class TestRequire < Test::Unit::TestCase
assert_equal(true, (t1_res ^ t2_res), bug5754 + " t1:#{t1_res} t2:#{t2_res}")
assert_equal([:pre, :post], scratch, bug5754)
-
- assert_match(/circular require/, output)
- assert_match(/in #{__method__}'$/o, output)
}
ensure
$VERBOSE = verbose
@@ -590,6 +624,28 @@ class TestRequire < Test::Unit::TestCase
$".replace(features)
end
+ def test_default_loaded_features_encoding
+ Dir.mktmpdir {|tmp|
+ Dir.mkdir("#{tmp}/1")
+ Dir.mkdir("#{tmp}/2")
+ File.write("#{tmp}/1/bug18191-1.rb", "")
+ File.write("#{tmp}/2/bug18191-2.rb", "")
+ assert_separately(%W[-Eutf-8 -I#{tmp}/1 -], "#{<<~"begin;"}\n#{<<~'end;'}")
+ tmp = #{tmp.dump}"/2"
+ begin;
+ $:.unshift(tmp)
+ require "bug18191-1"
+ require "bug18191-2"
+ encs = [Encoding::US_ASCII, Encoding.find("filesystem")]
+ message = -> {
+ require "pp"
+ {filesystem: encs[1], **$".group_by(&:encoding)}.pretty_inspect
+ }
+ assert($".all? {|n| encs.include?(n.encoding)}, message)
+ end;
+ }
+ end
+
def test_require_changed_current_dir
bug7158 = '[ruby-core:47970]'
Dir.mktmpdir {|tmp|
@@ -661,7 +717,7 @@ class TestRequire < Test::Unit::TestCase
Dir.mktmpdir {|tmp|
Dir.chdir(tmp) {
open("foo.rb", "w") {}
- assert_in_out_err([{"RUBYOPT"=>nil}, '--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
+ assert_in_out_err([{"RUBYOPT"=>nil}], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
begin;
$:.replace([IO::NULL])
a = Object.new
@@ -689,7 +745,7 @@ class TestRequire < Test::Unit::TestCase
Dir.mktmpdir {|tmp|
Dir.chdir(tmp) {
open("foo.rb", "w") {}
- assert_in_out_err([{"RUBYOPT"=>nil}, '--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
+ assert_in_out_err([{"RUBYOPT"=>nil}], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
begin;
$:.replace([IO::NULL])
a = Object.new
@@ -719,7 +775,7 @@ class TestRequire < Test::Unit::TestCase
open("foo.rb", "w") {}
Dir.mkdir("a")
open(File.join("a", "bar.rb"), "w") {}
- assert_in_out_err(['--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7383)
+ assert_in_out_err([], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7383)
begin;
$:.replace([IO::NULL])
$:.#{add} "#{tmp}"
@@ -770,8 +826,8 @@ class TestRequire < Test::Unit::TestCase
assert_in_out_err([{"RUBYOPT" => nil}, "-", script.path], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7530, timeout: 60)
begin;
PATH = ARGV.shift
- THREADS = 4
- ITERATIONS_PER_THREAD = 1000
+ THREADS = 30
+ ITERATIONS_PER_THREAD = 300
THREADS.times.map {
Thread.new do
@@ -784,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
@@ -791,7 +877,7 @@ class TestRequire < Test::Unit::TestCase
f.close
File.unlink(f.path)
File.mkfifo(f.path)
- assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 3)
+ assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
begin;
th = Thread.current
Thread.start {begin sleep(0.001) end until th.stop?; th.raise(IOError)}
@@ -803,12 +889,14 @@ class TestRequire < Test::Unit::TestCase
end if File.respond_to?(:mkfifo)
def test_loading_fifo_threading_success
+ omit "[Bug #18613]" if /freebsd/=~ RUBY_PLATFORM
+
Tempfile.create(%w'fifo .rb') {|f|
f.close
File.unlink(f.path)
File.mkfifo(f.path)
- assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 3)
+ assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
begin;
path = ARGV[0]
th = Thread.current
@@ -829,6 +917,8 @@ class TestRequire < Test::Unit::TestCase
end if File.respond_to?(:mkfifo)
def test_loading_fifo_fd_leak
+ omit if RUBY_PLATFORM =~ /android/ # https://rubyci.org/logs/rubyci.s3.amazonaws.com/android29-x86_64/ruby-master/log/20200419T124100Z.fail.html.gz
+
Tempfile.create(%w'fifo .rb') {|f|
f.close
File.unlink(f.path)
@@ -888,11 +978,81 @@ class TestRequire < Test::Unit::TestCase
begin
File.symlink "real", File.join(tmp, "symlink")
rescue NotImplementedError, Errno::EACCES
- skip "File.symlink is not implemented"
+ omit "File.symlink is not implemented"
end
- File.write(File.join(tmp, "real/a.rb"), "print __FILE__")
- result = IO.popen([EnvUtil.rubybin, "-I#{tmp}/symlink", "-e", "require 'a.rb'"], &:read)
- assert_operator(result, :end_with?, "/real/a.rb")
+ File.write(File.join(tmp, "real/test_symlink_load_path.rb"), "print __FILE__")
+ result = IO.popen([EnvUtil.rubybin, "-I#{tmp}/symlink", "-e", "require 'test_symlink_load_path.rb'"], &:read)
+ assert_operator(result, :end_with?, "/real/test_symlink_load_path.rb")
}
end
+
+ def test_provide_in_required_file
+ paths, loaded = $:.dup, $".dup
+ Dir.mktmpdir do |tmp|
+ provide = File.realdirpath("provide.rb", tmp)
+ File.write(File.join(tmp, "target.rb"), "raise __FILE__\n")
+ File.write(provide, '$" << '"'target.rb'\n")
+ $:.replace([tmp])
+ assert(require("provide"))
+ assert(!require("target"))
+ assert_equal($".pop, provide)
+ assert_equal($".pop, "target.rb")
+ end
+ ensure
+ $:.replace(paths)
+ $".replace(loaded)
+ end
+
+ if defined?($LOAD_PATH.resolve_feature_path)
+ def test_resolve_feature_path
+ paths, loaded = $:.dup, $".dup
+ Dir.mktmpdir do |tmp|
+ Tempfile.create(%w[feature .rb], tmp) do |file|
+ file.close
+ path = File.realpath(file.path)
+ dir, base = File.split(path)
+ $:.unshift(dir)
+ assert_equal([:rb, path], $LOAD_PATH.resolve_feature_path(base))
+ $".push(path)
+ assert_equal([:rb, path], $LOAD_PATH.resolve_feature_path(base))
+ end
+ end
+ ensure
+ $:.replace(paths)
+ $".replace(loaded)
+ end
+
+ def test_resolve_feature_path_with_missing_feature
+ assert_nil($LOAD_PATH.resolve_feature_path("superkalifragilisticoespialidoso"))
+ end
+ end
+
+ def test_require_with_public_method_missing
+ # [Bug #19793]
+ assert_ruby_status(["-W0", "-rtempfile"], <<~RUBY, timeout: 60)
+ GC.stress = true
+
+ class Object
+ public :method_missing
+ end
+
+ Tempfile.create(["empty", ".rb"]) do |file|
+ require file.path
+ 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_require_lib.rb b/test/ruby/test_require_lib.rb
new file mode 100644
index 0000000000..44dfbcf9ec
--- /dev/null
+++ b/test/ruby/test_require_lib.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+class TestRequireLib < Test::Unit::TestCase
+ libdir = __dir__ + '/../../lib'
+
+ # .rb files at lib
+ scripts = Dir.glob('*.rb', base: libdir).map {|f| f.chomp('.rb')}
+
+ # .rb files in subdirectories of lib without same name script
+ dirs = Dir.glob('*/', base: libdir).map {|d| d.chomp('/')}
+ dirs -= scripts
+ scripts.concat(Dir.glob(dirs.map {|d| d + '/*.rb'}, base: libdir).map {|f| f.chomp('.rb')})
+
+ # skip some problems
+ scripts -= %w[bundler bundled_gems rubygems mkmf set/sorted_set]
+
+ scripts.each do |lib|
+ define_method "test_thread_size:#{lib}" do
+ assert_separately(['-W0'], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 60)
+ begin;
+ n = Thread.list.size
+ require #{lib.dump}
+ assert_equal n, Thread.list.size
+ end;
+ end
+ end
+end
diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb
index 54213c4698..cd2dd5d3ff 100644
--- a/test/ruby/test_rubyoptions.rb
+++ b/test/ruby/test_rubyoptions.rb
@@ -1,10 +1,33 @@
# -*- coding: us-ascii -*-
require 'test/unit'
+require 'timeout'
require 'tmpdir'
require 'tempfile'
+require_relative '../lib/jit_support'
+require_relative '../lib/parser_support'
class TestRubyOptions < Test::Unit::TestCase
+ # 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.
+ RUBY_DESCRIPTION =
+ if ParserSupport.prism_enabled_in_subprocess?
+ ::RUBY_DESCRIPTION
+ else
+ ::RUBY_DESCRIPTION.sub(/\+PRISM /, '')
+ end
+
+ NO_JIT_DESCRIPTION =
+ case
+ when JITSupport.yjit_enabled?
+ RUBY_DESCRIPTION.sub(/\+YJIT( \w+)? /, '')
+ when JITSupport.zjit_enabled?
+ RUBY_DESCRIPTION.sub(/\+ZJIT( \w+)? /, '')
+ else
+ RUBY_DESCRIPTION
+ end
+
def write_file(filename, content)
File.open(filename, "w") {|f|
f << content
@@ -24,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, :<=, 24)
- 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
@@ -57,32 +85,84 @@ class TestRubyOptions < Test::Unit::TestCase
end
end
+ def test_backtrace_limit
+ assert_in_out_err(%w(--backtrace-limit), "", [], /missing argument for --backtrace-limit/)
+ assert_in_out_err(%w(--backtrace-limit= 1), "", [], /missing argument for --backtrace-limit/)
+ assert_in_out_err(%w(--backtrace-limit=-2), "", [], /wrong limit for backtrace length/)
+ code = 'def f(n);n > 0 ? f(n-1) : raise;end;f(5)'
+ assert_in_out_err(%w(--backtrace-limit=1), code, [],
+ [/.*unhandled exception\n/, /^\tfrom .*\n/,
+ /^\t \.{3} \d+ levels\.{3}\n/])
+ assert_in_out_err(%w(--backtrace-limit=3), code, [],
+ [/.*unhandled exception\n/, *[/^\tfrom .*\n/]*3,
+ /^\t \.{3} \d+ levels\.{3}\n/])
+ assert_kind_of(Integer, Thread::Backtrace.limit)
+ assert_in_out_err(%w(--backtrace-limit=1), "p Thread::Backtrace.limit", ['1'], [])
+ assert_in_out_err(%w(--backtrace-limit 1), "p Thread::Backtrace.limit", ['1'], [])
+ env = {"RUBYOPT" => "--backtrace-limit=5"}
+ assert_in_out_err([env], "p Thread::Backtrace.limit", ['5'], [])
+ assert_in_out_err([env, "--backtrace-limit=1"], "p Thread::Backtrace.limit", ['1'], [])
+ assert_in_out_err([env, "--backtrace-limit=-1"], "p Thread::Backtrace.limit", ['-1'], [])
+ assert_in_out_err([env, "--backtrace-limit=3", "--backtrace-limit=1"],
+ "p Thread::Backtrace.limit", ['1'], [])
+ assert_in_out_err([{"RUBYOPT" => "--backtrace-limit=5 --backtrace-limit=3"}],
+ "p Thread::Backtrace.limit", ['3'], [])
+ long_max = RbConfig::LIMITS["LONG_MAX"]
+ assert_in_out_err(%W(--backtrace-limit=#{long_max}), "p Thread::Backtrace.limit",
+ ["#{long_max}"], [])
+ end
def test_warning
- save_rubyopt = ENV['RUBYOPT']
- ENV['RUBYOPT'] = nil
+ save_rubyopt = ENV.delete('RUBYOPT')
assert_in_out_err(%w(-W0 -e) + ['p $-W'], "", %w(0), [])
assert_in_out_err(%w(-W1 -e) + ['p $-W'], "", %w(1), [])
- assert_in_out_err(%w(-Wx -e) + ['p $-W'], "", %w(1), [])
+ assert_in_out_err(%w(-Wx -e) + ['p $-W'], "", %w(2), [])
assert_in_out_err(%w(-W -e) + ['p $-W'], "", %w(2), [])
+ assert_in_out_err(%w(-We) + ['p $-W'], "", %w(2), [])
assert_in_out_err(%w(-w -W0 -e) + ['p $-W'], "", %w(0), [])
- ensure
- ENV['RUBYOPT'] = save_rubyopt
- end
- def test_safe_level
- assert_in_out_err(%w(-T -e) + [""], "", [],
- /no -e allowed in tainted mode \(SecurityError\)/)
+ categories = {deprecated: 1, experimental: 0, performance: 2, strict_unused_block: 3}
+ assert_equal categories.keys.sort, Warning.categories.sort
- assert_in_out_err(%w(-T4 -S foo.rb), "", [],
- /no -S allowed in tainted mode \(SecurityError\)/)
+ categories.each do |category, level|
+ assert_in_out_err(["-W:#{category}", "-e", "p Warning[:#{category}]"], "", %w(true), [])
+ assert_in_out_err(["-W:no-#{category}", "-e", "p Warning[:#{category}]"], "", %w(false), [])
+ assert_in_out_err(["-e", "p Warning[:#{category}]"], "", level > 0 ? %w(false) : %w(true), [])
+ assert_in_out_err(["-w", "-e", "p Warning[:#{category}]"], "", level > 1 ? %w(false) : %w(true), [])
+ assert_in_out_err(["-W", "-e", "p Warning[:#{category}]"], "", level > 1 ? %w(false) : %w(true), [])
+ assert_in_out_err(["-We", "p Warning[:#{category}]"], "", level > 1 ? %w(false) : %w(true), [])
+ end
+ assert_in_out_err(%w(-W:qux), "", [], /unknown warning category: 'qux'/)
+
+ def categories.expected(lev = 1, **warnings)
+ [
+ (lev > 1).to_s,
+ *map {|category, level| warnings.fetch(category, lev > level).to_s}
+ ].join(':')
+ end
+ code = ['#{$VERBOSE}', *categories.map {|category, | "\#{Warning[:#{category}]}"}].join(':')
+ code = %[puts "#{code}"]
+ Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) do |t|
+ t.puts code
+ t.close
+ assert_in_out_err(["-r#{t.path}", '-e', code], "", [categories.expected(1)]*2, [])
+ assert_in_out_err(["-r#{t.path}", '-w', '-e', code], "", [categories.expected(2)]*2, [])
+ categories.each do |category, |
+ assert_in_out_err(["-r#{t.path}", "-W:#{category}", '-e', code], "", [categories.expected(category => 'true')]*2, [])
+ assert_in_out_err(["-r#{t.path}", "-W:no-#{category}", '-e', code], "", [categories.expected(category => 'false')]*2, [])
+ end
+ end
+ ensure
+ ENV['RUBYOPT'] = save_rubyopt
end
def test_debug
- assert_in_out_err(["--disable-gems", "-de", "p $DEBUG"], "", %w(true), [])
+ assert_in_out_err(["-de", "p $DEBUG"], "", %w(true), [])
- assert_in_out_err(["--disable-gems", "--debug", "-e", "p $DEBUG"],
+ assert_in_out_err(["--debug", "-e", "p $DEBUG"],
"", %w(true), [])
+
+ assert_in_out_err(["--debug-", "-e", "p $DEBUG"], "", %w(), /invalid option --debug-/)
end
q = Regexp.method(:quote)
@@ -92,14 +172,18 @@ class TestRubyOptions < Test::Unit::TestCase
/^jruby #{q[RUBY_ENGINE_VERSION]} \(#{q[RUBY_VERSION]}\).*? \[#{
q[RbConfig::CONFIG["host_os"]]}-#{q[RbConfig::CONFIG["host_cpu"]]}\]$/
else
- /^ruby #{q[RUBY_VERSION]}(?:[p ]|dev|rc).*? \[#{q[RUBY_PLATFORM]}\]$/
+ /^ruby #{q[RUBY_VERSION]}(?:[p ]|dev|rc).*? (\+PRISM )?\[#{q[RUBY_PLATFORM]}\]$/
end
private_constant :VERSION_PATTERN
def test_verbose
- assert_in_out_err(["-vve", ""]) do |r, e|
+ assert_in_out_err([{'RUBY_YJIT_ENABLE' => nil}, "-vve", ""]) do |r, e|
assert_match(VERSION_PATTERN, r[0])
- assert_equal(RUBY_DESCRIPTION, r[0])
+ 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])
+ end
assert_equal([], e)
end
@@ -116,12 +200,16 @@ class TestRubyOptions < Test::Unit::TestCase
end
def test_enable
- assert_in_out_err(%w(--enable all -e) + [""], "", [], [])
- assert_in_out_err(%w(--enable-all -e) + [""], "", [], [])
- assert_in_out_err(%w(--enable=all -e) + [""], "", [], [])
+ if JITSupport.yjit_supported?
+ assert_in_out_err(%w(--enable all -e) + [""], "", [], [])
+ assert_in_out_err(%w(--enable-all -e) + [""], "", [], [])
+ assert_in_out_err(%w(--enable=all -e) + [""], "", [], [])
+ end
assert_in_out_err(%w(--enable foobarbazqux -e) + [""], "", [],
- /unknown argument for --enable: `foobarbazqux'/)
+ /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
@@ -129,15 +217,16 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%w(--disable-all -e) + [""], "", [], [])
assert_in_out_err(%w(--disable=all -e) + [""], "", [], [])
assert_in_out_err(%w(--disable foobarbazqux -e) + [""], "", [],
- /unknown argument for --disable: `foobarbazqux'/)
+ /unknown argument for --disable: 'foobarbazqux'/)
assert_in_out_err(%w(--disable), "", [], /missing argument for --disable/)
- assert_in_out_err(%w(--disable-gems -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
def test_kanji
assert_in_out_err(%w(-KU), "p '\u3042'") do |r, e|
- assert_equal("\"\u3042\"", r.join.force_encoding(Encoding::UTF_8))
+ assert_equal("\"\u3042\"", r.join('').force_encoding(Encoding::UTF_8))
end
line = '-eputs"\xc2\xa1".encoding'
env = {'RUBYOPT' => nil}
@@ -153,13 +242,44 @@ class TestRubyOptions < Test::Unit::TestCase
end
def test_version
- assert_in_out_err(%w(--version)) do |r, e|
+ env = { 'RUBY_YJIT_ENABLE' => nil } # unset in children
+ assert_in_out_err([env, '--version']) do |r, e|
assert_match(VERSION_PATTERN, r[0])
- assert_equal(RUBY_DESCRIPTION, r[0])
+ if ENV['RUBY_YJIT_ENABLE'] == '1'
+ assert_equal(NO_JIT_DESCRIPTION, r[0])
+ 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])
+ end
assert_equal([], e)
end
end
+ def test_enabled_gc
+ omit unless /linux|darwin/ =~ RUBY_PLATFORM
+
+ if RbConfig::CONFIG['modular_gc_dir'].length > 0
+ assert_match(/\+GC/, RUBY_DESCRIPTION)
+ else
+ assert_no_match(/\+GC/, RUBY_DESCRIPTION)
+ end
+ 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"/, [])
+
+ assert_in_out_err(%w(--parser=parse.y -e) + ["puts :hi"], "", %w(hi), [])
+ assert_norun_with_rflag('--parser=parse.y', '--version', "")
+
+ assert_in_out_err(%w(--parser=notreal -e) + ["puts :hi"], "", [], /unknown parser notreal/)
+
+ assert_in_out_err(%w(--parser=prism --version), "", /\+PRISM/, [])
+ end
+
def test_eval
assert_in_out_err(%w(-e), "", [], /no code specified for -e \(RuntimeError\)/)
end
@@ -192,7 +312,7 @@ class TestRubyOptions < Test::Unit::TestCase
end
def test_autosplit
- assert_in_out_err(%w(-an -F: -e) + ["p $F"], "foo:bar:baz\nqux:quux:quuux\n",
+ assert_in_out_err(%w(-W0 -an -F: -e) + ["p $F"], "foo:bar:baz\nqux:quux:quuux\n",
['["foo", "bar", "baz\n"]', '["qux", "quux", "quuux\n"]'], [])
end
@@ -203,12 +323,25 @@ class TestRubyOptions < Test::Unit::TestCase
d = Dir.tmpdir
assert_in_out_err(["-C", d, "-e", "puts Dir.pwd"]) do |r, e|
- assert_file.identical?(r.join, d)
+ assert_file.identical?(r.join(''), d)
assert_equal([], e)
end
+
+ Dir.mktmpdir(nil, d) do |base|
+ # "test" in Japanese and N'Ko
+ test = base + "/\u{30c6 30b9 30c8}_\u{7e1 7ca 7dd 7cc 7df 7cd 7eb}"
+ Dir.mkdir(test)
+ assert_in_out_err(["-C", base, "-C", File.basename(test), "-e", "puts Dir.pwd"]) do |r, e|
+ assert_file.identical?(r.join(''), test)
+ assert_equal([], e)
+ end
+ Dir.rmdir(test)
+ end
end
def test_yydebug
+ omit if ParserSupport.prism_enabled_in_subprocess?
+
assert_in_out_err(["-ye", ""]) do |r, e|
assert_not_equal([], r)
assert_equal([], e)
@@ -226,22 +359,32 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%w(--encoding test_ruby_test_rubyoptions_foobarbazqux), "", [],
/unknown encoding name - test_ruby_test_rubyoptions_foobarbazqux \(RuntimeError\)/)
- if /mswin|mingw|aix/ =~ RUBY_PLATFORM &&
- (str = "\u3042".force_encoding(Encoding.find("locale"))).valid_encoding?
- # This result depends on locale because LANG=C doesn't affect locale
- # on Windows.
- # On AIX, the source encoding of stdin with LANG=C is ISO-8859-1,
- # which allows \u3042.
- out, err = [str], []
- else
- out, err = [], /invalid multibyte char/
- end
- assert_in_out_err(%w(-Eutf-8), "puts '\u3042'", out, err)
- assert_in_out_err(%w(--encoding utf-8), "puts '\u3042'", out, err)
+ assert_in_out_err(%w(-Eutf-8), 'puts Encoding::default_external', ["UTF-8"])
+ assert_in_out_err(%w(-Ecesu-8), 'puts Encoding::default_external', ["CESU-8"])
+ assert_in_out_err(%w(--encoding utf-8), 'puts Encoding::default_external', ["UTF-8"])
+ assert_in_out_err(%w(--encoding cesu-8), 'puts Encoding::default_external', ["CESU-8"])
end
def test_syntax_check
- assert_in_out_err(%w(-c -e a=1+1 -e !a), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-cw -e a=1+1 -e !a), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-cw -e break), "", [], [:*, /(-e:1:|~) Invalid break/, :*])
+ assert_in_out_err(%w(-cw -e next), "", [], [:*, /(-e:1:|~) Invalid next/, :*])
+ assert_in_out_err(%w(-cw -e redo), "", [], [:*, /(-e:1:|~) Invalid redo/, :*])
+ assert_in_out_err(%w(-cw -e retry), "", [], [:*, /(-e:1:|~) Invalid retry/, :*])
+ assert_in_out_err(%w(-cw -e yield), "", [], [:*, /(-e:1:|~) Invalid yield/, :*])
+ assert_in_out_err(%w(-cw -e begin -e break -e end), "", [], [:*, /(-e:2:|~) Invalid break/, :*])
+ assert_in_out_err(%w(-cw -e begin -e next -e end), "", [], [:*, /(-e:2:|~) Invalid next/, :*])
+ assert_in_out_err(%w(-cw -e begin -e redo -e end), "", [], [:*, /(-e:2:|~) Invalid redo/, :*])
+ assert_in_out_err(%w(-cw -e begin -e retry -e end), "", [], [:*, /(-e:2:|~) Invalid retry/, :*])
+ assert_in_out_err(%w(-cw -e begin -e yield -e end), "", [], [:*, /(-e:2:|~) Invalid yield/, :*])
+ assert_in_out_err(%w(-cw -e !defined?(break)), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-cw -e !defined?(next)), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-cw -e !defined?(redo)), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-cw -e !defined?(retry)), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-cw -e !defined?(yield)), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-n -cw -e break), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-n -cw -e next), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-n -cw -e redo), "", ["Syntax OK"], [])
end
def test_invalid_option
@@ -249,11 +392,15 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%W(-\r -e) + [""], "", [], [])
- assert_in_out_err(%W(-\rx), "", [], /invalid option -\\x0D \(-h will show valid options\) \(RuntimeError\)/)
+ assert_in_out_err(%W(-\rx), "", [], /invalid option -\\r \(-h will show valid options\) \(RuntimeError\)/)
assert_in_out_err(%W(-\x01), "", [], /invalid option -\\x01 \(-h will show valid options\) \(RuntimeError\)/)
assert_in_out_err(%w(-Z), "", [], /invalid option -Z \(-h will show valid options\) \(RuntimeError\)/)
+
+ assert_in_out_err(%W(-\u{1f608}), "", [],
+ /invalid option -(\\xf0|\u{1f608}) \(-h will show valid options\) \(RuntimeError\)/,
+ encoding: Encoding::UTF_8)
end
def test_rubyopt
@@ -265,12 +412,6 @@ class TestRubyOptions < Test::Unit::TestCase
ENV['RUBYOPT'] = '-e "p 1"'
assert_in_out_err([], "", [], /invalid switch in RUBYOPT: -e \(RuntimeError\)/)
- ENV['RUBYOPT'] = '-T1'
- assert_in_out_err(["--disable-gems"], "", [], /no program input from stdin allowed in tainted mode \(SecurityError\)/)
-
- ENV['RUBYOPT'] = '-T4'
- assert_in_out_err(["--disable-gems"], "", [], /no program input from stdin allowed in tainted mode \(SecurityError\)/)
-
ENV['RUBYOPT'] = '-Eus-ascii -KN'
assert_in_out_err(%w(-Eutf-8 -KU), "p '\u3042'") do |r, e|
assert_equal("\"\u3042\"", r.join.force_encoding(Encoding::UTF_8))
@@ -281,12 +422,25 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%w(), "p $VERBOSE", ["true"])
assert_in_out_err(%w(-W1), "p $VERBOSE", ["false"])
assert_in_out_err(%w(-W0), "p $VERBOSE", ["nil"])
+ assert_in_out_err(%w(), "p Warning[:deprecated]", ["true"])
+ assert_in_out_err(%w(-W0), "p Warning[:deprecated]", ["false"])
+ assert_in_out_err(%w(-W1), "p Warning[:deprecated]", ["false"])
+ assert_in_out_err(%w(-W2), "p Warning[:deprecated]", ["true"])
+ ENV['RUBYOPT'] = '-W:deprecated'
+ assert_in_out_err(%w(), "p Warning[:deprecated]", ["true"])
+ ENV['RUBYOPT'] = '-W:no-deprecated'
+ assert_in_out_err(%w(), "p Warning[:deprecated]", ["false"])
+ ENV['RUBYOPT'] = '-W:experimental'
+ assert_in_out_err(%w(), "p Warning[:experimental]", ["true"])
+ ENV['RUBYOPT'] = '-W:no-experimental'
+ assert_in_out_err(%w(), "p Warning[:experimental]", ["false"])
+ ENV['RUBYOPT'] = '-W:qux'
+ assert_in_out_err(%w(), "", [], /unknown warning category: 'qux'/)
+
+ ENV['RUBYOPT'] = 'w'
+ assert_in_out_err(%w(), "p $VERBOSE", ["true"])
ensure
- if rubyopt_orig
- ENV['RUBYOPT'] = rubyopt_orig
- else
- ENV.delete('RUBYOPT')
- end
+ ENV['RUBYOPT'] = rubyopt_orig
end
def test_search
@@ -374,6 +528,18 @@ 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
+ assert_in_out_err(%w(-0 --enable), "", [], /missing argument for --enable/)
+ assert_in_out_err(%w(-0 --disable), "", [], /missing argument for --disable/)
+ assert_in_out_err(%w(-0 --dump), "", [], /missing argument for --dump/)
+ assert_in_out_err(%w(-0 --encoding), "", [], /missing argument for --encoding/)
+ assert_in_out_err(%w(-0 --external-encoding), "", [], /missing argument for --external-encoding/)
+ assert_in_out_err(%w(-0 --internal-encoding), "", [], /missing argument for --internal-encoding/)
+ assert_in_out_err(%w(-0 --backtrace-limit), "", [], /missing argument for --backtrace-limit/)
end
def test_assignment_in_conditional
@@ -386,7 +552,7 @@ class TestRubyOptions < Test::Unit::TestCase
t.puts " end"
t.puts "end"
t.flush
- warning = ' warning: found = in conditional, should be =='
+ warning = ' warning: found \'= literal\' in conditional, should be =='
err = ["#{t.path}:1:#{warning}",
"#{t.path}:4:#{warning}",
]
@@ -403,16 +569,29 @@ class TestRubyOptions < Test::Unit::TestCase
t.puts "if a = {}; end"
t.puts "if a = {1=>2}; end"
t.puts "if a = {3=>a}; end"
+ t.puts "if a = :sym; end"
t.flush
err = ["#{t.path}:1:#{warning}",
"#{t.path}:2:#{warning}",
"#{t.path}:3:#{warning}",
"#{t.path}:5:#{warning}",
"#{t.path}:6:#{warning}",
+ "#{t.path}:8:#{warning}",
]
feature4299 = '[ruby-dev:43083]'
assert_in_out_err(["-w", t.path], "", [], err, feature4299)
assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err, feature4299)
+
+ t.rewind
+ t.truncate(0)
+ t.puts "if a = __LINE__; end"
+ t.puts "if a = __FILE__; end"
+ t.flush
+ err = ["#{t.path}:1:#{warning}",
+ "#{t.path}:2:#{warning}",
+ ]
+ assert_in_out_err(["-w", t.path], "", [], err)
+ assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err)
}
end
@@ -423,13 +602,27 @@ class TestRubyOptions < Test::Unit::TestCase
"begin", "if false", "for _ in []", "while false",
"def foo", "class X", "module M",
["-> do", "end"], ["-> {", "}"],
+ ["if false;", "else ; end"],
+ ["if false;", "elsif false ; end"],
+ ["begin", "rescue ; end"],
+ ["begin rescue", "else ; end"],
+ ["begin", "ensure ; end"],
+ [" case nil", "when true; end"],
+ ["case nil; when true", "end"],
+ ["if false;", "end", "if true\nelse ", "end"],
+ ["else", " end", "_ = if true\n"],
+ ["begin\n def f() = nil", "end"],
+ ["begin\n def self.f() = nil", "end"],
].each do
- |b, e = 'end'|
- src = ["#{b}\n", " #{e}\n"]
- k = b[/\A\S+/]
-
- a.for("no directives with #{b}") do
- err = ["#{t.path}:2: warning: mismatched indentations at '#{e}' with '#{k}' at 1"]
+ |b, e = 'end', pre = nil, post = nil|
+ src = ["#{pre}#{b}\n", " #{e}\n#{post}"]
+ k = b[/\A\s*(\S+)/, 1]
+ e = e[/\A\s*(\S+)/, 1]
+ n = 1 + src[0].count("\n")
+ n1 = 1 + (pre ? pre.count("\n") : 0)
+
+ a.for("no directives with #{src}") do
+ err = ["#{t.path}:#{n}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n1}"]
t.rewind
t.truncate(0)
t.puts src
@@ -438,7 +631,7 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err)
end
- a.for("false directive with #{b}") do
+ a.for("false directive with #{src}") do
t.rewind
t.truncate(0)
t.puts "# -*- warn-indent: false -*-"
@@ -447,8 +640,8 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(["-w", t.path], "", [], [], '[ruby-core:25442]')
end
- a.for("false and true directives with #{b}") do
- err = ["#{t.path}:4: warning: mismatched indentations at '#{e}' with '#{k}' at 3"]
+ a.for("false and true directives with #{src}") do
+ err = ["#{t.path}:#{n+2}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n1+2}"]
t.rewind
t.truncate(0)
t.puts "# -*- warn-indent: false -*-"
@@ -458,7 +651,7 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(["-w", t.path], "", [], err, '[ruby-core:25442]')
end
- a.for("false directives after #{b}") do
+ a.for("false directives after #{src}") do
t.rewind
t.truncate(0)
t.puts "# -*- warn-indent: true -*-"
@@ -469,8 +662,8 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(["-w", t.path], "", [], [], '[ruby-core:25442]')
end
- a.for("BOM with #{b}") do
- err = ["#{t.path}:2: warning: mismatched indentations at '#{e}' with '#{k}' at 1"]
+ a.for("BOM with #{src}") do
+ err = ["#{t.path}:#{n}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n1}"]
t.rewind
t.truncate(0)
t.print "\u{feff}"
@@ -542,30 +735,34 @@ class TestRubyOptions < Test::Unit::TestCase
if /linux|freebsd|netbsd|openbsd|darwin/ =~ RUBY_PLATFORM
PSCMD = EnvUtil.find_executable("ps", "-o", "command", "-p", $$.to_s) {|out| /ruby/=~out}
- PSCMD.pop if PSCMD
+ PSCMD&.pop
end
def test_set_program_name
- skip "platform dependent feature" unless defined?(PSCMD) and PSCMD
+ omit "platform dependent feature" unless defined?(PSCMD) and PSCMD
with_tmpchdir do
write_file("test-script", "$0 = 'hello world'; /test-script/ =~ Process.argv0 or $0 = 'Process.argv0 changed!'; sleep 60")
pid = spawn(EnvUtil.rubybin, "test-script")
ps = nil
- 10.times do
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ stop = now + 30
+ begin
sleep 0.1
ps = `#{PSCMD.join(' ')} #{pid}`
break if /hello world/ =~ ps
- end
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ end until Process.wait(pid, Process::WNOHANG) || now > stop
assert_match(/hello world/, ps)
+ assert_operator now, :<, stop
Process.kill :KILL, pid
- Process.wait(pid)
+ EnvUtil.timeout(5) { Process.wait(pid) }
end
end
def test_setproctitle
- skip "platform dependent feature" unless defined?(PSCMD) and PSCMD
+ omit "platform dependent feature" unless defined?(PSCMD) and PSCMD
assert_separately([], "#{<<-"{#"}\n#{<<-'};'}")
{#
@@ -579,30 +776,39 @@ class TestRubyOptions < Test::Unit::TestCase
pid = spawn(EnvUtil.rubybin, "test-script")
ps = nil
- 10.times do
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ stop = now + 30
+ begin
sleep 0.1
ps = `#{PSCMD.join(' ')} #{pid}`
break if /hello world/ =~ ps
- end
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ end until Process.wait(pid, Process::WNOHANG) || now > stop
assert_match(/hello world/, ps)
+ assert_operator now, :<, stop
Process.kill :KILL, pid
- Process.wait(pid)
+ Timeout.timeout(5) { Process.wait(pid) }
end
end
module SEGVTest
opts = {}
- if /mswin|mingw/ =~ RUBY_PLATFORM
- additional = /[\s\w\.\']*/
- else
+ unless /mswin|mingw/ =~ RUBY_PLATFORM
opts[:rlimit_core] = 0
- additional = nil
+ 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.
+ # see assert_pattern_list
ExpectedStderrList = [
%r(
- -e:(?:1:)?\s\[BUG\]\sSegmentation\sfault.*\n
+ (?:-e:(?:1:)?\s)?\[BUG\]\sSegmentation\sfault.*\n
)x,
%r(
#{ Regexp.quote(RUBY_DESCRIPTION) }\n\n
@@ -610,61 +816,77 @@ class TestRubyOptions < Test::Unit::TestCase
%r(
(?:--\s(?:.+\n)*\n)?
--\sControl\sframe\sinformation\s-+\n
- (?:c:.*\n)*
+ (?:(?:c:.*\n)|(?:^\s+.+\n))*
+ \n
)x,
%r(
(?:
--\sRuby\slevel\sbacktrace\sinformation\s----------------------------------------\n
- -e:1:in\s\`<main>\'\n
- -e:1:in\s\`kill\'\n
+ (?:-e:1:in\s\'(?:block\sin\s)?<main>\'\n)*
+ -e:1:in\s\'kill\'\n
+ \n
)?
)x,
%r(
+ (?:--\sThreading(?:.+\n)*\n)?
+ )x,
+ %r(
+ (?:--\sMachine(?:.+\n)*\n)?
+ )x,
+ %r(
(?:
--\sC\slevel\sbacktrace\sinformation\s-------------------------------------------\n
- (?:(?:.*\s)?\[0x\h+\]\n)*\n
+ (?:Un(?:expected|supported|known)\s.*\n)*
+ (?:(?:.*\s)?\[0x\h+\].*\n|.*:\d+\n)*\n
)?
)x,
- :*,
%r(
- \[NOTE\]\n
- You\smay\shave\sencountered\sa\sbug\sin\sthe\sRuby\sinterpreter\sor\sextension\slibraries.\n
- Bug\sreports\sare\swelcome.\n
- (?:.*\n)?
- For\sdetails:\shttp:\/\/.*\.ruby-lang\.org/.*\n
- \n
- (?:
- \[IMPORTANT\]\n
- (?:.+\n)+
- \n
+ (?:--\sOther\sruntime\sinformation\s-+\n
+ (?:.*\n)*
)?
)x,
]
- ExpectedStderrList << additional if additional
+
+ KILL_SELF = "Process.kill :SEGV, $$"
end
- def assert_segv(args, message=nil)
+ def assert_segv(args, message=nil, list: SEGVTest::ExpectedStderrList, **opt, &block)
+ # 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 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', 'LSAN_OPTIONS' => 'handle_segv=0'})
+ args.unshift(env)
+
test_stdin = ""
- opt = SEGVTest::ExecOptions.dup
- list = SEGVTest::ExpectedStderrList
+ if !block
+ tests = [//, list, message]
+ elsif message
+ tests = [[], [], message]
+ end
- assert_in_out_err(args, test_stdin, //, list, encoding: "ASCII-8BIT", **opt)
+ assert_in_out_err(args, test_stdin, *tests, encoding: "ASCII-8BIT",
+ **SEGVTest::ExecOptions, **opt, &block)
end
def test_segv_test
- assert_segv(["--disable-gems", "-e", "Process.kill :SEGV, $$"])
+ assert_segv(["--disable-gems", "-e", SEGVTest::KILL_SELF])
end
def test_segv_loaded_features
bug7402 = '[ruby-core:49573]'
- status = assert_segv(['-e', 'END {Process.kill :SEGV, $$}',
- '-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
@@ -672,10 +894,85 @@ class TestRubyOptions < Test::Unit::TestCase
Tempfile.create(["test_ruby_test_bug7597", ".rb"]) {|t|
t.write "f" * 100
t.flush
- assert_segv(["--disable-gems", "-e", "$0=ARGV[0]; Process.kill :SEGV, $$", t.path], bug7597)
+ assert_segv(["--disable-gems", "-e", "$0=ARGV[0]; #{SEGVTest::KILL_SELF}", t.path], bug7597)
}
end
+ def assert_crash_report(path, cmd = nil, &block)
+ Dir.mktmpdir("ruby_crash_report") do |dir|
+ list = SEGVTest::ExpectedStderrList
+ if cmd
+ FileUtils.mkpath(File.join(dir, File.dirname(cmd)))
+ File.write(File.join(dir, cmd), SEGVTest::KILL_SELF+"\n")
+ c = Regexp.quote(cmd)
+ list = list.map {|re| Regexp.new(re.source.gsub(/^\s*(\(\?:)?\K-e(?=:)/) {c}, re.options)}
+ else
+ cmd = ['-e', SEGVTest::KILL_SELF]
+ end
+ status = assert_segv([{"RUBY_CRASH_REPORT"=>path}, *cmd], list: [], chdir: dir, &block)
+ next if block
+ reports = Dir.glob("*.log", File::FNM_DOTMATCH, base: dir)
+ assert_equal(1, reports.size)
+ assert_pattern_list(list, File.read(File.join(dir, reports.first)))
+ break status, reports.first
+ end
+ end
+
+ def test_crash_report
+ status, report = assert_crash_report("%e.%f.%p.log")
+ assert_equal("#{File.basename(EnvUtil.rubybin)}.-e.#{status.pid}.log", report)
+ end
+
+ def test_crash_report_script
+ status, report = assert_crash_report("%e.%f.%p.log", "bug.rb")
+ assert_equal("#{File.basename(EnvUtil.rubybin)}.bug.rb.#{status.pid}.log", report)
+ end
+
+ def test_crash_report_executable_path
+ omit if EnvUtil.rubybin.size > 245
+ status, report = assert_crash_report("%E.%p.log")
+ path = EnvUtil.rubybin.sub(/\A\w\K:[\/\\]/, '!').tr_s('/', '!')
+ assert_equal("#{path}.#{status.pid}.log", report)
+ end
+
+ def test_crash_report_script_path
+ status, report = assert_crash_report("%F.%p.log", "test/bug.rb")
+ assert_equal("test!bug.rb.#{status.pid}.log", report)
+ end
+
+ def test_crash_report_pipe
+ if File.executable?(echo = "/bin/echo")
+ elsif /mswin|ming/ =~ RUBY_PLATFORM
+ echo = "echo"
+ else
+ omit "/bin/echo not found"
+ end
+ assert_crash_report("| #{echo} %e:%f:%p") do |stdin, stdout, status|
+ assert_equal(["#{File.basename(EnvUtil.rubybin)}:-e:#{status.pid}"], stdin)
+ 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"
@@ -705,20 +1002,6 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(["-w", "-"], "eval('a=1')", [], [], feature7730)
end
- def test_shadowing_variable
- bug4130 = '[ruby-dev:42718]'
- assert_in_out_err(["-we", "def foo\n"" a=1\n"" 1.times do |a| end\n"" a\n""end"],
- "", [], ["-e:3: warning: shadowing outer local variable - a"], bug4130)
- assert_in_out_err(["-we", "def foo\n"" a=1\n"" 1.times do |a| end\n""end"],
- "", [],
- ["-e:3: warning: shadowing outer local variable - a",
- "-e:2: warning: assigned but unused variable - a",
- ], bug4130)
- feature6693 = '[ruby-core:46160]'
- assert_in_out_err(["-we", "def foo\n"" _a=1\n"" 1.times do |_a| end\n""end"],
- "", [], [], feature6693)
- end
-
def test_script_from_stdin
begin
require 'pty'
@@ -736,12 +1019,12 @@ class TestRubyOptions < Test::Unit::TestCase
pid = spawn(EnvUtil.rubybin, :in => s, :out => w)
w.close
assert_nothing_raised('[ruby-dev:37798]') do
- result = Timeout.timeout(3) {r.read}
+ result = EnvUtil.timeout(10) {r.read}
end
Process.wait pid
}
rescue RuntimeError
- skip $!
+ omit $!
end
}
assert_equal("", result, '[ruby-dev:37798]')
@@ -776,11 +1059,11 @@ class TestRubyOptions < Test::Unit::TestCase
def test_command_line_glob_nonascii
bug10555 = '[ruby-dev:48752] [Bug #10555]'
name = "\u{3042}.txt"
- expected = name.encode("locale") rescue "?.txt"
+ expected = name.encode("external") rescue "?.txt"
with_tmpchdir do |dir|
open(name, "w") {}
assert_in_out_err(["-e", "puts ARGV", "?.txt"], "", [expected], [],
- bug10555, encoding: "locale")
+ bug10555, encoding: "external")
end
end
@@ -791,7 +1074,7 @@ class TestRubyOptions < Test::Unit::TestCase
name = c.chr(Encoding::UTF_8)
expected = name.encode("locale") rescue nil
}
- skip "can't make locale name"
+ omit "can't make locale name"
end
name << ".rb"
expected << ".rb"
@@ -815,7 +1098,7 @@ class TestRubyOptions < Test::Unit::TestCase
with_tmpchdir do |dir|
Ougai.each {|f| open(f, "w") {}}
assert_in_out_err(["-Eutf-8", "-e", "puts ARGV", "*"], "", Ougai, encoding: "utf-8")
- ougai = Ougai.map {|f| f.encode("locale", replace: "?")}
+ ougai = Ougai.map {|f| f.encode("external", replace: "?")}
assert_in_out_err(["-e", "puts ARGV", "*.txt"], "", ougai)
end
end
@@ -872,18 +1155,17 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(['-p', '-e', 'sub(/t.*/){"TEST"}'], %[test], %w[TEST], [], bug7157)
end
- def assert_norun_with_rflag(*opt)
+ def assert_norun_with_rflag(*opt, test_stderr: [])
bug10435 = "[ruby-dev:48712] [Bug #10435]: should not run with #{opt} option"
stderr = []
Tempfile.create(%w"bug10435- .rb") do |script|
dir, base = File.split(script.path)
- script.puts "abort ':run'"
- script.close
+ File.write(script, "abort ':run'\n")
opts = ['-C', dir, '-r', "./#{base}", *opt]
- _, e = assert_in_out_err([*opts, '-ep'], "", //)
+ _, e = assert_in_out_err([*opts, '-ep'], "", //, test_stderr)
stderr.concat(e) if e
stderr << "---"
- _, e = assert_in_out_err([*opts, base], "", //)
+ _, e = assert_in_out_err([*opts, base], "", //, test_stderr)
stderr.concat(e) if e
end
assert_not_include(stderr, ":run", bug10435)
@@ -902,6 +1184,19 @@ class TestRubyOptions < Test::Unit::TestCase
def test_dump_parsetree_with_rflag
assert_norun_with_rflag('--dump=parsetree')
assert_norun_with_rflag('--dump=parsetree', '-e', '#frozen-string-literal: true')
+ assert_norun_with_rflag('--dump=parsetree+error_tolerant')
+ assert_norun_with_rflag('--dump=parse+error_tolerant')
+ end
+
+ def test_dump_parsetree_error_tolerant
+ omit if ParserSupport.prism_enabled_in_subprocess?
+
+ assert_in_out_err(['--dump=parse', '-e', 'begin'],
+ "", [], /unexpected end-of-input/, success: false)
+ assert_in_out_err(['--dump=parse', '--dump=+error_tolerant', '-e', 'begin'],
+ "", /^# @/, /unexpected end-of-input/, success: true)
+ assert_in_out_err(['--dump=+error_tolerant', '-e', 'begin p :run'],
+ "", [], /unexpected end-of-input/, success: false)
end
def test_dump_insns_with_rflag
@@ -913,8 +1208,10 @@ class TestRubyOptions < Test::Unit::TestCase
[["disable", "false"], ["enable", "true"]].each do |opt, exp|
%W[frozen_string_literal frozen-string-literal].each do |arg|
key = "#{opt}=#{arg}"
+ negopt = exp == "true" ? "disable" : "enable"
+ env = {"RUBYOPT"=>"--#{negopt}=#{arg}"}
a.for(key) do
- assert_in_out_err(["--disable=gems", "--#{key}"], 'p("foo".frozen?)', [exp])
+ assert_in_out_err([env, "--disable=gems", "--#{key}"], 'p("foo".frozen?)', [exp])
end
end
end
@@ -931,12 +1228,12 @@ class TestRubyOptions < Test::Unit::TestCase
def test_frozen_string_literal_debug
with_debug_pat = /created at/
- wo_debug_pat = /can\'t modify frozen String \(FrozenError\)\n\z/
+ wo_debug_pat = /can\'t modify frozen String: "\w+" \(FrozenError\)\n\z/
frozen = [
["--enable-frozen-string-literal", true],
["--disable-frozen-string-literal", false],
- [nil, false],
]
+
debugs = [
["--debug-frozen-string-literal", true],
["--debug=frozen-string-literal", true],
@@ -947,13 +1244,27 @@ class TestRubyOptions < Test::Unit::TestCase
frozen.product(debugs) do |(opt1, freeze), (opt2, debug)|
opt = opts + [opt1, opt2].compact
err = !freeze ? [] : debug ? with_debug_pat : wo_debug_pat
- assert_in_out_err(opt, '"foo" << "bar"', [], err)
- if freeze
- assert_in_out_err(opt, '"foo#{123}bar" << "bar"', [], err)
+ [
+ ['"foo" << "bar"', err],
+ ['"foo#{123}bar" << "bar"', []],
+ ['+"foo#{123}bar" << "bar"', []],
+ ['-"foo#{123}bar" << "bar"', wo_debug_pat],
+ ].each do |code, expected|
+ assert_in_out_err(opt, code, [], expected, "#{opt} #{code}")
end
end
end
+ def test_frozen_string_literal_debug_chilled_strings
+ code = <<~RUBY
+ "foo" << "bar"
+ RUBY
+ assert_in_out_err(["-W:deprecated"], code, [], ["-:1: warning: literal string will be frozen in the future (run with --debug-frozen-string-literal for more information)"])
+ assert_in_out_err(["-W:deprecated", "--debug-frozen-string-literal"], code, [], ["-:1: warning: literal string will be frozen in the future", "-:1: info: the string was created here"])
+ assert_in_out_err(["-W:deprecated", "--disable-frozen-string-literal", "--debug-frozen-string-literal"], code, [], [])
+ assert_in_out_err(["-W:deprecated", "--enable-frozen-string-literal", "--debug-frozen-string-literal"], code, [], ["-:1:in '<main>': can't modify frozen String: \"foo\", created at -:1 (FrozenError)"])
+ end
+
def test___dir__encoding
lang = {"LC_ALL"=>ENV["LC_ALL"]||ENV["LANG"]}
with_tmpchdir do
@@ -987,10 +1298,29 @@ class TestRubyOptions < Test::Unit::TestCase
end
end
- def test_argv_tainted
- assert_separately(%w[- arg], "#{<<~"begin;"}\n#{<<~'end;'}")
- begin;
- assert_predicate(ARGV[0], :tainted?, '[ruby-dev:50596] [Bug #14941]')
- end;
+ def test_rubylib_invalid_encoding
+ env = {"RUBYLIB"=>"\xFF", "LOCALE"=>"en_US.UTF-8", "LC_ALL"=>"en_US.UTF-8"}
+ assert_ruby_status([env, "-e;"])
+ end
+
+ def test_null_script
+ omit "#{IO::NULL} is not a character device" unless File.chardev?(IO::NULL)
+ assert_in_out_err([IO::NULL], success: true)
+ end
+
+ def test_free_at_exit_env_var
+ env = {"RUBY_FREE_AT_EXIT"=>"1"}
+ assert_ruby_status([env, "-e;"])
+ assert_in_out_err([env, "-W"], "", [], /Free at exit is experimental and may be unstable/)
+ end
+
+ 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_rubyvm.rb b/test/ruby/test_rubyvm.rb
index 7673d8dfbe..225cb45f33 100644
--- a/test/ruby/test_rubyvm.rb
+++ b/test/ruby/test_rubyvm.rb
@@ -1,18 +1,73 @@
# frozen_string_literal: false
require 'test/unit'
+require_relative '../lib/parser_support'
class TestRubyVM < Test::Unit::TestCase
def test_stat
assert_kind_of Hash, RubyVM.stat
- assert_kind_of Integer, RubyVM.stat[:global_method_state]
RubyVM.stat(stat = {})
assert_not_empty stat
- assert_equal stat[:global_method_state], RubyVM.stat(:global_method_state)
end
def test_stat_unknown
assert_raise(ArgumentError){ RubyVM.stat(:unknown) }
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {RubyVM.stat(:"\u{30eb 30d3 30fc}")}
end
+
+ def parse_and_compile
+ script = <<~RUBY
+ _a = 1
+ def foo
+ _b = 2
+ end
+ 1.times{
+ _c = 3
+ }
+ RUBY
+
+ ast = RubyVM::AbstractSyntaxTree.parse(script)
+ iseq = RubyVM::InstructionSequence.compile(script)
+
+ [ast, iseq]
+ end
+
+ def test_keep_script_lines
+ omit if ParserSupport.prism_enabled?
+ pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO
+
+ prev_conf = RubyVM.keep_script_lines
+
+ # keep
+ RubyVM.keep_script_lines = true
+
+ ast, iseq = *parse_and_compile
+
+ lines = ast.script_lines
+ assert_equal Array, lines.class
+
+ lines = iseq.script_lines
+ assert_equal Array, lines.class
+ iseq.each_child{|child|
+ assert_equal lines, child.script_lines
+ }
+ assert lines.frozen?
+
+ # don't keep
+ RubyVM.keep_script_lines = false
+
+ ast, iseq = *parse_and_compile
+
+ lines = ast.script_lines
+ assert_equal nil, lines
+
+ lines = iseq.script_lines
+ assert_equal nil, lines
+ iseq.each_child{|child|
+ assert_equal lines, child.script_lines
+ }
+
+ ensure
+ RubyVM.keep_script_lines = prev_conf
+ end
end
diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb
new file mode 100644
index 0000000000..70a61aa3b5
--- /dev/null
+++ b/test/ruby/test_set.rb
@@ -0,0 +1,1052 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'set'
+
+class TC_Set < Test::Unit::TestCase
+ class SetSubclass < Set
+ end
+ class CoreSetSubclass < Set::CoreSet
+ end
+ ALL_SET_CLASSES = [Set, SetSubclass, CoreSetSubclass].freeze
+
+ def test_marshal
+ set = Set[1, 2, 3]
+ mset = Marshal.load(Marshal.dump(set))
+ assert_equal(set, mset)
+ assert_equal(set.compare_by_identity?, mset.compare_by_identity?)
+
+ set.compare_by_identity
+ mset = Marshal.load(Marshal.dump(set))
+ assert_equal(set, mset)
+ assert_equal(set.compare_by_identity?, mset.compare_by_identity?)
+
+ set.instance_variable_set(:@a, 1)
+ mset = Marshal.load(Marshal.dump(set))
+ assert_equal(set, mset)
+ assert_equal(set.compare_by_identity?, mset.compare_by_identity?)
+ assert_equal(1, mset.instance_variable_get(:@a))
+
+ old_stdlib_set_data = "\x04\bo:\bSet\x06:\n@hash}\bi\x06Ti\aTi\bTF".b
+ set = Marshal.load(old_stdlib_set_data)
+ assert_equal(Set[1, 2, 3], set)
+
+ old_stdlib_set_cbi_data = "\x04\bo:\bSet\x06:\n@hashC:\tHash}\ai\x06Ti\aTF".b
+ set = Marshal.load(old_stdlib_set_cbi_data)
+ assert_equal(Set[1, 2].compare_by_identity, set)
+ end
+
+ def test_aref
+ assert_nothing_raised {
+ Set[]
+ Set[nil]
+ Set[1,2,3]
+ }
+
+ assert_equal(0, Set[].size)
+ assert_equal(1, Set[nil].size)
+ assert_equal(1, Set[[]].size)
+ assert_equal(1, Set[[nil]].size)
+
+ set = Set[2,4,6,4]
+ assert_equal(Set.new([2,4,6]), set)
+ end
+
+ def test_s_new
+ assert_nothing_raised {
+ Set.new()
+ Set.new(nil)
+ Set.new([])
+ Set.new([1,2])
+ Set.new('a'..'c')
+ }
+ assert_raise(ArgumentError) {
+ Set.new(false)
+ }
+ assert_raise(ArgumentError) {
+ Set.new(1)
+ }
+ assert_raise(ArgumentError) {
+ Set.new(1,2)
+ }
+
+ assert_equal(0, Set.new().size)
+ assert_equal(0, Set.new(nil).size)
+ assert_equal(0, Set.new([]).size)
+ assert_equal(1, Set.new([nil]).size)
+
+ ary = [2,4,6,4]
+ set = Set.new(ary)
+ ary.clear
+ assert_equal(false, set.empty?)
+ assert_equal(3, set.size)
+
+ ary = [1,2,3]
+
+ s = Set.new(ary) { |o| o * 2 }
+ assert_equal([2,4,6], s.sort)
+ end
+
+ def test_clone
+ set1 = Set.new
+ set2 = set1.clone
+ set1 << 'abc'
+ assert_equal(Set.new, set2)
+ end
+
+ def test_dup
+ set1 = Set[1,2]
+ set2 = set1.dup
+
+ assert_not_same(set1, set2)
+
+ assert_equal(set1, set2)
+
+ set1.add(3)
+
+ assert_not_equal(set1, set2)
+ end
+
+ def test_size
+ assert_equal(0, Set[].size)
+ assert_equal(2, Set[1,2].size)
+ assert_equal(2, Set[1,2,1].size)
+ end
+
+ def test_empty?
+ assert_equal(true, Set[].empty?)
+ assert_equal(false, Set[1, 2].empty?)
+ end
+
+ def test_clear
+ set = Set[1,2]
+ ret = set.clear
+
+ assert_same(set, ret)
+ assert_equal(true, set.empty?)
+ end
+
+ def test_replace
+ set = Set[1,2]
+ ret = set.replace('a'..'c')
+
+ assert_same(set, ret)
+ 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)
+ }
+ assert_equal(Set[1,2], set)
+ end
+
+ def test_to_a
+ set = Set[1,2,3,2]
+ ary = set.to_a
+
+ assert_equal([1,2,3], ary.sort)
+ end
+
+ def test_flatten
+ # test1
+ set1 = Set[
+ 1,
+ Set[
+ 5,
+ Set[7,
+ Set[0]
+ ],
+ Set[6,2],
+ 1
+ ],
+ 3,
+ Set[3,4]
+ ]
+
+ set2 = set1.flatten
+ set3 = Set.new(0..7)
+
+ assert_not_same(set2, set1)
+ assert_equal(set3, set2)
+
+ # test2; destructive
+ orig_set1 = set1
+ set1.flatten!
+
+ assert_same(orig_set1, set1)
+ assert_equal(set3, set1)
+
+ # test3; multiple occurrences of a set in an set
+ set1 = Set[1, 2]
+ set2 = Set[set1, Set[set1, 4], 3]
+
+ assert_nothing_raised {
+ set2.flatten!
+ }
+
+ assert_equal(Set.new(1..4), set2)
+
+ # test4; recursion
+ set2 = Set[]
+ set1 = Set[1, set2]
+ set2.add(set1)
+
+ assert_raise(ArgumentError) {
+ set1.flatten!
+ }
+
+ # test5; miscellaneous
+ empty = Set[]
+ set = Set[Set[empty, "a"],Set[empty, "b"]]
+
+ assert_nothing_raised {
+ set.flatten
+ }
+
+ set1 = empty.merge(Set["no_more", set])
+
+ assert_nil(Set.new(0..31).flatten!)
+
+ x = Set[Set[],Set[1,2]].flatten!
+ y = Set[1,2]
+
+ assert_equal(x, y)
+ end
+
+ def test_include?
+ set = Set[1,2,3]
+
+ assert_equal(true, set.include?(1))
+ assert_equal(true, set.include?(2))
+ assert_equal(true, set.include?(3))
+ assert_equal(false, set.include?(0))
+ assert_equal(false, set.include?(nil))
+
+ set = Set["1",nil,"2",nil,"0","1",false]
+ assert_equal(true, set.include?(nil))
+ assert_equal(true, set.include?(false))
+ assert_equal(true, set.include?("1"))
+ assert_equal(false, set.include?(0))
+ assert_equal(false, set.include?(true))
+ end
+
+ def test_eqq
+ set = Set[1,2,3]
+
+ assert_equal(true, set === 1)
+ assert_equal(true, set === 2)
+ assert_equal(true, set === 3)
+ assert_equal(false, set === 0)
+ assert_equal(false, set === nil)
+
+ set = Set["1",nil,"2",nil,"0","1",false]
+ assert_equal(true, set === nil)
+ assert_equal(true, set === false)
+ assert_equal(true, set === "1")
+ assert_equal(false, set === 0)
+ assert_equal(false, set === true)
+ end
+
+ def test_superset?
+ set = Set[1,2,3]
+
+ assert_raise(ArgumentError) {
+ set.superset?()
+ }
+
+ assert_raise(ArgumentError) {
+ set.superset?(2)
+ }
+
+ assert_raise(ArgumentError) {
+ set.superset?([2])
+ }
+
+ 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)
+ assert_equal(false, set.superset?(klass[1,2,3,4]), klass.name)
+ assert_equal(false, set.superset?(klass[1,4]), klass.name)
+
+ assert_equal(true, set >= klass[1,2,3], klass.name)
+ assert_equal(true, set >= klass[1,2], klass.name)
+
+ assert_equal(true, Set[].superset?(klass[]), klass.name)
+ }
+ end
+
+ def test_proper_superset?
+ set = Set[1,2,3]
+
+ assert_raise(ArgumentError) {
+ set.proper_superset?()
+ }
+
+ assert_raise(ArgumentError) {
+ set.proper_superset?(2)
+ }
+
+ assert_raise(ArgumentError) {
+ set.proper_superset?([2])
+ }
+
+ 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)
+ assert_equal(false, set.proper_superset?(klass[1,2,3,4]), klass.name)
+ assert_equal(false, set.proper_superset?(klass[1,4]), klass.name)
+
+ assert_equal(false, set > klass[1,2,3], klass.name)
+ assert_equal(true, set > klass[1,2], klass.name)
+
+ assert_equal(false, Set[].proper_superset?(klass[]), klass.name)
+ }
+ end
+
+ def test_subset?
+ set = Set[1,2,3]
+
+ assert_raise(ArgumentError) {
+ set.subset?()
+ }
+
+ assert_raise(ArgumentError) {
+ set.subset?(2)
+ }
+
+ assert_raise(ArgumentError) {
+ set.subset?([2])
+ }
+
+ 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)
+ assert_equal(false, set.subset?(klass[]), klass.name)
+
+ assert_equal(true, set <= klass[1,2,3], klass.name)
+ assert_equal(true, set <= klass[1,2,3,4], klass.name)
+
+ assert_equal(true, Set[].subset?(klass[1]), klass.name)
+ assert_equal(true, Set[].subset?(klass[]), klass.name)
+ }
+ end
+
+ def test_proper_subset?
+ set = Set[1,2,3]
+
+ assert_raise(ArgumentError) {
+ set.proper_subset?()
+ }
+
+ assert_raise(ArgumentError) {
+ set.proper_subset?(2)
+ }
+
+ assert_raise(ArgumentError) {
+ set.proper_subset?([2])
+ }
+
+ 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)
+ assert_equal(false, set.proper_subset?(klass[]), klass.name)
+
+ assert_equal(false, set < klass[1,2,3], klass.name)
+ assert_equal(true, set < klass[1,2,3,4], klass.name)
+
+ assert_equal(false, Set[].proper_subset?(klass[]), klass.name)
+ }
+ end
+
+ def test_spacecraft_operator
+ set = Set[1,2,3]
+
+ assert_nil(set <=> 2)
+
+ assert_nil(set <=> set.to_a)
+
+ 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)
+ assert_equal(+1, set <=> klass[2,3] , klass.name)
+ assert_equal(+1, set <=> klass[] , klass.name)
+
+ assert_equal(0, Set[] <=> klass[], klass.name)
+ }
+ end
+
+ def assert_intersect(expected, set, other)
+ case expected
+ when true
+ assert_send([set, :intersect?, other])
+ assert_send([set, :intersect?, other.to_a])
+ assert_send([other, :intersect?, set])
+ assert_not_send([set, :disjoint?, other])
+ assert_not_send([set, :disjoint?, other.to_a])
+ assert_not_send([other, :disjoint?, set])
+ when false
+ assert_not_send([set, :intersect?, other])
+ assert_not_send([set, :intersect?, other.to_a])
+ assert_not_send([other, :intersect?, set])
+ assert_send([set, :disjoint?, other])
+ assert_send([set, :disjoint?, other.to_a])
+ assert_send([other, :disjoint?, set])
+ when Class
+ assert_raise(expected) {
+ set.intersect?(other)
+ }
+ assert_raise(expected) {
+ set.disjoint?(other)
+ }
+ else
+ raise ArgumentError, "%s: unsupported expected value: %s" % [__method__, expected.inspect]
+ end
+ end
+
+ def test_intersect?
+ set = Set[3,4,5]
+
+ assert_intersect(ArgumentError, set, 3)
+ assert_intersect(true, set, Set[2,4,6])
+
+ assert_intersect(true, set, set)
+ assert_intersect(true, set, Set[2,4])
+ assert_intersect(true, set, Set[5,6,7])
+ assert_intersect(true, set, Set[1,2,6,8,4])
+
+ assert_intersect(false, set, Set[])
+ assert_intersect(false, set, Set[0,2])
+ assert_intersect(false, set, Set[0,2,6])
+ assert_intersect(false, set, Set[0,2,6,8,10])
+
+ # Make sure set hasn't changed
+ assert_equal(Set[3,4,5], set)
+ end
+
+ def test_each
+ ary = [1,3,5,7,10,20]
+ set = Set.new(ary)
+
+ ret = set.each { |o| }
+ assert_same(set, ret)
+
+ e = set.each
+ assert_instance_of(Enumerator, e)
+
+ assert_nothing_raised {
+ set.each { |o|
+ ary.delete(o) or raise "unexpected element: #{o}"
+ }
+
+ ary.empty? or raise "forgotten elements: #{ary.join(', ')}"
+ }
+
+ assert_equal(6, e.size)
+ set << 42
+ assert_equal(7, e.size)
+ end
+
+ def test_add
+ set = Set[1,2,3]
+
+ ret = set.add(2)
+ assert_same(set, ret)
+ assert_equal(Set[1,2,3], set)
+
+ ret = set.add?(2)
+ assert_nil(ret)
+ assert_equal(Set[1,2,3], set)
+
+ ret = set.add(4)
+ assert_same(set, ret)
+ assert_equal(Set[1,2,3,4], set)
+
+ ret = set.add?(5)
+ assert_same(set, ret)
+ assert_equal(Set[1,2,3,4,5], set)
+ end
+
+ def test_delete
+ set = Set[1,2,3]
+
+ ret = set.delete(4)
+ assert_same(set, ret)
+ assert_equal(Set[1,2,3], set)
+
+ ret = set.delete?(4)
+ assert_nil(ret)
+ assert_equal(Set[1,2,3], set)
+
+ ret = set.delete(2)
+ assert_equal(set, ret)
+ assert_equal(Set[1,3], set)
+
+ ret = set.delete?(1)
+ assert_equal(set, ret)
+ assert_equal(Set[3], set)
+ end
+
+ def test_delete_if
+ set = Set.new(1..10)
+ ret = set.delete_if { |i| i > 10 }
+ assert_same(set, ret)
+ assert_equal(Set.new(1..10), set)
+
+ set = Set.new(1..10)
+ ret = set.delete_if { |i| i % 3 == 0 }
+ assert_same(set, ret)
+ assert_equal(Set[1,2,4,5,7,8,10], set)
+
+ set = Set.new(1..10)
+ enum = set.delete_if
+ assert_equal(set.size, enum.size)
+ assert_same(set, enum.each { |i| i % 3 == 0 })
+ assert_equal(Set[1,2,4,5,7,8,10], set)
+ end
+
+ def test_keep_if
+ set = Set.new(1..10)
+ ret = set.keep_if { |i| i <= 10 }
+ assert_same(set, ret)
+ assert_equal(Set.new(1..10), set)
+
+ set = Set.new(1..10)
+ ret = set.keep_if { |i| i % 3 != 0 }
+ assert_same(set, ret)
+ assert_equal(Set[1,2,4,5,7,8,10], set)
+
+ set = Set.new(1..10)
+ enum = set.keep_if
+ assert_equal(set.size, enum.size)
+ assert_same(set, enum.each { |i| i % 3 != 0 })
+ assert_equal(Set[1,2,4,5,7,8,10], set)
+ end
+
+ def test_collect!
+ set = Set[1,2,3,'a','b','c',-1..1,2..4]
+
+ ret = set.collect! { |i|
+ case i
+ when Numeric
+ i * 2
+ when String
+ i.upcase
+ else
+ nil
+ end
+ }
+
+ assert_same(set, ret)
+ assert_equal(Set[2,4,6,'A','B','C',nil], set)
+
+ set = Set[1,2,3,'a','b','c',-1..1,2..4]
+ enum = set.collect!
+
+ assert_equal(set.size, enum.size)
+ assert_same(set, enum.each { |i|
+ case i
+ when Numeric
+ i * 2
+ when String
+ i.upcase
+ else
+ nil
+ end
+ })
+ assert_equal(Set[2,4,6,'A','B','C',nil], set)
+ end
+
+ def test_reject!
+ set = Set.new(1..10)
+
+ ret = set.reject! { |i| i > 10 }
+ assert_nil(ret)
+ assert_equal(Set.new(1..10), set)
+
+ ret = set.reject! { |i| i % 3 == 0 }
+ assert_same(set, ret)
+ assert_equal(Set[1,2,4,5,7,8,10], set)
+
+ set = Set.new(1..10)
+ enum = set.reject!
+ assert_equal(set.size, enum.size)
+ assert_same(set, enum.each { |i| i % 3 == 0 })
+ assert_equal(Set[1,2,4,5,7,8,10], set)
+ end
+
+ def test_select!
+ set = Set.new(1..10)
+ ret = set.select! { |i| i <= 10 }
+ assert_equal(nil, ret)
+ assert_equal(Set.new(1..10), set)
+
+ set = Set.new(1..10)
+ ret = set.select! { |i| i % 3 != 0 }
+ assert_same(set, ret)
+ assert_equal(Set[1,2,4,5,7,8,10], set)
+
+ set = Set.new(1..10)
+ enum = set.select!
+ assert_equal(set.size, enum.size)
+ assert_equal(nil, enum.each { |i| i <= 10 })
+ assert_equal(Set.new(1..10), set)
+ end
+
+ def test_filter!
+ set = Set.new(1..10)
+ ret = set.filter! { |i| i <= 10 }
+ assert_equal(nil, ret)
+ assert_equal(Set.new(1..10), set)
+
+ set = Set.new(1..10)
+ ret = set.filter! { |i| i % 3 != 0 }
+ assert_same(set, ret)
+ assert_equal(Set[1,2,4,5,7,8,10], set)
+
+ set = Set.new(1..10)
+ enum = set.filter!
+ assert_equal(set.size, enum.size)
+ assert_equal(nil, enum.each { |i| i <= 10 })
+ assert_equal(Set.new(1..10), set)
+ end
+
+ def test_merge
+ set = Set[1,2,3]
+ ret = set.merge([2,4,6])
+ assert_same(set, ret)
+ assert_equal(Set[1,2,3,4,6], set)
+
+ set = Set[1,2,3]
+ ret = set.merge()
+ assert_same(set, ret)
+ assert_equal(Set[1,2,3], set)
+
+ set = Set[1,2,3]
+ ret = set.merge([2,4,6], Set[4,5,6])
+ assert_same(set, ret)
+ assert_equal(Set[1,2,3,4,5,6], set)
+
+ assert_raise(ArgumentError) {
+ Set[].merge(a: 1)
+ }
+ end
+
+ def test_merge_mutating_hash_bug_21305
+ a = (1..100).to_a
+ o = Object.new
+ o.define_singleton_method(:hash) do
+ a.clear
+ 0
+ end
+ a.unshift o
+ assert_equal([o], Set.new.merge(a).to_a)
+ end
+
+ def test_initialize_mutating_array_bug_21306
+ a = (1..100).to_a
+ assert_equal(Set[0], Set.new(a){a.clear; 0})
+ end
+
+ def test_subtract
+ set = Set[1,2,3]
+
+ ret = set.subtract([2,4,6])
+ assert_same(set, ret)
+ assert_equal(Set[1,3], set)
+ end
+
+ def test_plus
+ set = Set[1,2,3]
+
+ ret = set + [2,4,6]
+ assert_not_same(set, ret)
+ assert_equal(Set[1,2,3,4,6], ret)
+ end
+
+ def test_minus
+ set = Set[1,2,3]
+
+ ret = set - [2,4,6]
+ assert_not_same(set, ret)
+ assert_equal(Set[1,3], ret)
+ end
+
+ def test_and
+ set = Set[1,2,3,4]
+
+ ret = set & [2,4,6]
+ assert_not_same(set, ret)
+ assert_equal(Set[2,4], ret)
+ end
+
+ def test_xor
+ 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
+
+ assert_equal(original_b, b)
+ assert_equal(Set[2], result)
+ end
+
+ def test_eq
+ set1 = Set[2,3,1]
+ set2 = Set[1,2,3]
+
+ assert_equal(set1, set1)
+ assert_equal(set1, set2)
+ assert_not_equal(Set[1], [1])
+
+ set1 = Class.new(Set)["a", "b"]
+ set1.add(set1).reset # Make recursive
+ set2 = Set["a", "b", Set["a", "b", set1]]
+
+ assert_equal(set1, set2)
+
+ assert_not_equal(Set[Exception.new,nil], Set[Exception.new,Exception.new], "[ruby-dev:26127]")
+ end
+
+ def test_classify
+ set = Set.new(1..10)
+ ret = set.classify { |i| i % 3 }
+
+ assert_equal(3, ret.size)
+ assert_instance_of(Hash, ret)
+ ret.each_value { |value| assert_instance_of(Set, value) }
+ assert_equal(Set[3,6,9], ret[0])
+ assert_equal(Set[1,4,7,10], ret[1])
+ assert_equal(Set[2,5,8], ret[2])
+
+ set = Set.new(1..10)
+ enum = set.classify
+
+ assert_equal(set.size, enum.size)
+ ret = enum.each { |i| i % 3 }
+ assert_equal(3, ret.size)
+ assert_instance_of(Hash, ret)
+ ret.each_value { |value| assert_instance_of(Set, value) }
+ assert_equal(Set[3,6,9], ret[0])
+ assert_equal(Set[1,4,7,10], ret[1])
+ assert_equal(Set[2,5,8], ret[2])
+ end
+
+ def test_divide
+ set = Set.new(1..10)
+ ret = set.divide { |i| i % 3 }
+
+ assert_equal(3, ret.size)
+ n = 0
+ ret.each { |s| n += s.size }
+ assert_equal(set.size, n)
+ assert_equal(set, ret.flatten)
+
+ set = Set[7,10,5,11,1,3,4,9,0]
+ ret = set.divide { |a,b| (a - b).abs == 1 }
+
+ assert_equal(4, ret.size)
+ n = 0
+ ret.each { |s| n += s.size }
+ assert_equal(set.size, n)
+ assert_equal(set, ret.flatten)
+ ret.each { |s|
+ if s.include?(0)
+ assert_equal(Set[0,1], s)
+ elsif s.include?(3)
+ assert_equal(Set[3,4,5], s)
+ elsif s.include?(7)
+ assert_equal(Set[7], s)
+ elsif s.include?(9)
+ assert_equal(Set[9,10,11], s)
+ else
+ raise "unexpected group: #{s.inspect}"
+ end
+ }
+
+ set = Set.new(1..10)
+ enum = set.divide
+ ret = enum.each { |i| i % 3 }
+
+ assert_equal(set.size, enum.size)
+ assert_equal(3, ret.size)
+ n = 0
+ 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
+ orig = set = Set[1,2,3]
+ assert_equal false, set.frozen?
+ set << 4
+ assert_same orig, set.freeze
+ assert_equal true, set.frozen?
+ assert_raise(FrozenError) {
+ set << 5
+ }
+ assert_equal 4, set.size
+ end
+
+ def test_freeze_dup
+ set1 = Set[1,2,3]
+ set1.freeze
+ set2 = set1.dup
+
+ assert_not_predicate set2, :frozen?
+ assert_nothing_raised {
+ set2.add 4
+ }
+ end
+
+ def test_freeze_clone
+ set1 = Set[1,2,3]
+ set1.freeze
+ set2 = set1.clone
+
+ assert_predicate set2, :frozen?
+ assert_raise(FrozenError) {
+ set2.add 5
+ }
+ end
+
+ def test_freeze_clone_false
+ set1 = Set[1,2,3]
+ set1.freeze
+ set2 = set1.clone(freeze: false)
+
+ assert_not_predicate set2, :frozen?
+ set2.add 5
+ assert_equal Set[1,2,3,5], set2
+ assert_equal Set[1,2,3], set1
+ end if Kernel.instance_method(:initialize_clone).arity != 1
+
+ def test_join
+ assert_equal('123', Set[1, 2, 3].join)
+ assert_equal('1 & 2 & 3', Set[1, 2, 3].join(' & '))
+ end
+
+ def test_inspect
+ set1 = Set[1, 2]
+ 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)
+
+ set1.add(set2)
+ 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)
+
+ set2 = Set[Set[0], 1, 2, set1]
+ 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)
+ end
+
+ def test_compare_by_identity
+ a1, a2 = "a", "a"
+ b1, b2 = "b", "b"
+ c = "c"
+ array = [a1, b1, c, a2, b2]
+
+ iset = Set.new.compare_by_identity
+ assert_send([iset, :compare_by_identity?])
+ iset.merge(array)
+ assert_equal(5, iset.size)
+ assert_equal(array.map(&:object_id).sort, iset.map(&:object_id).sort)
+
+ set = Set.new
+ assert_not_send([set, :compare_by_identity?])
+ set.merge(array)
+ assert_equal(3, set.size)
+ assert_equal(array.uniq.sort, set.sort)
+ end
+
+ def test_reset
+ [Set, Class.new(Set)].each { |klass|
+ a = [1, 2]
+ b = [1]
+ set = klass.new([a, b])
+
+ b << 2
+ set.reset
+
+ assert_equal(klass.new([a]), set, klass.name)
+ }
+ end
+
+ def test_set_gc_compact_does_not_allocate
+ assert_in_out_err([], <<-"end;", [], [])
+ def x
+ s = Set.new
+ s << Object.new
+ s
+ end
+
+ x
+ begin
+ GC.compact
+ rescue NotImplementedError
+ 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
+ def test_to_set
+ ary = [2,5,4,3,2,1,3]
+
+ set = ary.to_set
+ assert_instance_of(Set, set)
+ assert_equal([1,2,3,4,5], set.sort)
+
+ set = ary.to_set { |o| o * -2 }
+ assert_instance_of(Set, set)
+ assert_equal([-10,-8,-6,-4,-2], set.sort)
+
+ 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
+ private def should_omit?
+ (RUBY_VERSION.scan(/\d+/).map(&:to_i) <=> [3, 2]) < 0 ||
+ !File.exist?(File.expand_path('../prelude.rb', __dir__))
+ end
+
+ def test_Set
+ omit "skipping the test for the builtin Set" if should_omit?
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_nothing_raised do
+ set = Set.new([1, 2])
+ assert_equal('Set', set.class.name)
+ end
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_nothing_raised do
+ set = Set[1, 2]
+ assert_equal('Set', set.class.name)
+ end
+ end;
+ end
+
+ def test_to_set
+ omit "skipping the test for the builtin Enumerable#to_set" if should_omit?
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_nothing_raised do
+ set = [1, 2].to_set
+ assert_equal('Set', set.class.name)
+ end
+ end;
+ end
+end
diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb
index 3085c0902a..d3b2441e21 100644
--- a/test/ruby/test_settracefunc.rb
+++ b/test/ruby/test_settracefunc.rb
@@ -1,19 +1,24 @@
# frozen_string_literal: false
require 'test/unit'
+EnvUtil.suppress_warning {require 'continuation'}
class TestSetTraceFunc < Test::Unit::TestCase
def setup
- @original_compile_option = RubyVM::InstructionSequence.compile_option
- RubyVM::InstructionSequence.compile_option = {
- :trace_instruction => true,
- :specialized_instruction => false
- }
+ if defined?(RubyVM)
+ @original_compile_option = RubyVM::InstructionSequence.compile_option
+ RubyVM::InstructionSequence.compile_option = {
+ :trace_instruction => true,
+ :specialized_instruction => false
+ }
+ end
@target_thread = Thread.current
end
def teardown
set_trace_func(nil)
- RubyVM::InstructionSequence.compile_option = @original_compile_option
+ if defined?(RubyVM)
+ RubyVM::InstructionSequence.compile_option = @original_compile_option
+ end
@target_thread = nil
end
@@ -46,6 +51,65 @@ class TestSetTraceFunc < Test::Unit::TestCase
assert_equal([], events)
end
+ def test_c_return_no_binding
+ binding = :none
+ TracePoint.new(:c_return){|tp|
+ binding = tp.binding
+ }.enable{
+ 1.object_id
+ }
+ assert_nil(binding)
+ end
+
+ def test_c_call_no_binding
+ binding = :none
+ TracePoint.new(:c_call){|tp|
+ binding = tp.binding
+ }.enable{
+ 1.object_id
+ }
+ assert_nil(binding)
+ end
+
+ def test_c_call_removed_method
+ # [Bug #19305]
+ klass = Class.new do
+ attr_writer :bar
+ alias_method :set_bar, :bar=
+ remove_method :bar=
+ end
+
+ obj = klass.new
+ method_id = nil
+ parameters = nil
+
+ TracePoint.new(:c_call) { |tp|
+ method_id = tp.method_id
+ parameters = tp.parameters
+ }.enable {
+ obj.set_bar(1)
+ }
+
+ assert_equal(:bar=, method_id)
+ assert_equal([[:req]], parameters)
+ end
+
+ def test_c_call_aliased_method
+ # [Bug #20915]
+ klass = Class.new do
+ alias_method :new_method, :method
+ end
+
+ instance = klass.new
+ parameters = nil
+
+ TracePoint.new(:c_call) do |tp|
+ parameters = tp.parameters
+ end.enable { instance.new_method(:to_s) }
+
+ assert_equal([[:req]], parameters)
+ end
+
def test_call
events = []
name = "#{self.class}\##{__method__}"
@@ -104,6 +168,10 @@ class TestSetTraceFunc < Test::Unit::TestCase
events.shift)
assert_equal(["line", 4, __method__, self.class],
events.shift)
+ assert_equal(["c-call", 4, :const_added, Module],
+ events.shift)
+ assert_equal(["c-return", 4, :const_added, Module],
+ events.shift)
assert_equal(["c-call", 4, :inherited, Class],
events.shift)
assert_equal(["c-return", 4, :inherited, Class],
@@ -137,6 +205,10 @@ class TestSetTraceFunc < Test::Unit::TestCase
assert_equal(["c-call", 9, :set_trace_func, Kernel],
events.shift)
assert_equal([], events)
+
+ self.class.class_eval do
+ remove_const :Foo
+ end
end
def test_return # [ruby-dev:38701]
@@ -176,7 +248,9 @@ class TestSetTraceFunc < Test::Unit::TestCase
events.shift)
assert_equal(["line", 5, :meth_return, self.class],
events.shift)
- assert_equal(["return", 7, :meth_return, self.class],
+ assert_equal(["line", 6, :meth_return, self.class],
+ events.shift)
+ assert_equal(["return", 6, :meth_return, self.class],
events.shift)
assert_equal(["line", 10, :test_return, self.class],
events.shift)
@@ -215,7 +289,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
events.shift)
assert_equal(["line", 6, :meth_return2, self.class],
events.shift)
- assert_equal(["return", 7, :meth_return2, self.class],
+ assert_equal(["return", 6, :meth_return2, self.class],
events.shift)
assert_equal(["line", 9, :test_return2, self.class],
events.shift)
@@ -303,18 +377,18 @@ class TestSetTraceFunc < Test::Unit::TestCase
def test_thread_trace
events = {:set => [], :add => []}
+ name = "#{self.class}\##{__method__}"
prc = Proc.new { |event, file, lineno, mid, binding, klass|
- events[:set] << [event, lineno, mid, klass, :set]
+ events[:set] << [event, lineno, mid, klass, :set] if file == name
}
prc = prc # suppress warning
prc2 = Proc.new { |event, file, lineno, mid, binding, klass|
- events[:add] << [event, lineno, mid, klass, :add]
+ events[:add] << [event, lineno, mid, klass, :add] if file == name
}
prc2 = prc2 # suppress warning
th = Thread.new do
th = Thread.current
- name = "#{self.class}\##{__method__}"
eval <<-EOF.gsub(/^.*?: /, ""), nil, name
1: th.set_trace_func(prc)
2: th.add_trace_func(prc2)
@@ -337,6 +411,8 @@ class TestSetTraceFunc < Test::Unit::TestCase
[["c-return", 2, :add_trace_func, Thread],
["line", 3, __method__, self.class],
+ ["c-call", 3, :const_added, Module],
+ ["c-return", 3, :const_added, Module],
["c-call", 3, :inherited, Class],
["c-return", 3, :inherited, Class],
["class", 3, nil, nil],
@@ -362,6 +438,11 @@ class TestSetTraceFunc < Test::Unit::TestCase
end
assert_equal([], events[:set])
assert_equal([], events[:add])
+
+ # cleanup
+ self.class.class_eval do
+ remove_const :ThreadTraceInnerClass
+ end
end
def test_trace_defined_method
@@ -380,7 +461,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
[["c-return", 3, :set_trace_func, Kernel],
["line", 6, __method__, self.class],
["call", 1, :foobar, FooBar],
- ["return", 6, :foobar, FooBar],
+ ["return", 1, :foobar, FooBar],
["line", 7, __method__, self.class],
["c-call", 7, :set_trace_func, Kernel]].each{|e|
assert_equal(e, events.shift)
@@ -391,6 +472,9 @@ class TestSetTraceFunc < Test::Unit::TestCase
bug3921 = '[ruby-dev:42350]'
ok = false
func = lambda{|e, f, l, i, b, k|
+ # In parallel testing, unexpected events like IO operations may be traced,
+ # so we filter out events here.
+ next unless f == __FILE__
set_trace_func(nil)
ok = eval("self", b)
}
@@ -440,9 +524,9 @@ class TestSetTraceFunc < Test::Unit::TestCase
begin
eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread?
- 2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy'
+ 2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding&.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy'
3: }
- 4: 1.times{|;_local_var| _local_var = :inner
+ 4: [1].reverse_each{|;_local_var| _local_var = :inner
5: tap{}
6: }
7: class XYZZY
@@ -468,31 +552,30 @@ class TestSetTraceFunc < Test::Unit::TestCase
answer_events = [
#
- [:c_return, 1, "xyzzy", TracePoint, :trace, TracePoint, :outer, trace],
[:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing],
- [:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing],
+ [:c_call, 4, 'xyzzy', Array, :reverse_each, [1], nil, :nothing],
[:line, 4, 'xyzzy', self.class, method, self, nil, :nothing],
[:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing],
- [:c_call, 5, 'xyzzy', Kernel, :tap, self, :inner, :nothing],
- [:c_return, 5, "xyzzy", Kernel, :tap, self, :inner, self],
- [:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1],
+ [:c_return, 4, "xyzzy", Array, :reverse_each, [1], nil, [1]],
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
- [:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing],
- [:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil],
+ [:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, :nothing],
+ [:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, nil],
+ [:c_call, 7, "xyzzy", Class, :inherited, Object, nil, :nothing],
+ [:c_return, 7, "xyzzy", Class, :inherited, Object, nil, nil],
[:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
[:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
[:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil],
+ [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, :nothing],
+ [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
[:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil],
+ [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, nil, :nothing],
+ [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
[:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
[:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
- [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, :outer, :nothing],
- [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, :nothing],
- [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, nil],
- [:c_return,18, "xyzzy", Class, :new, xyzzy.class, :outer, xyzzy],
+ [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, nil, :nothing],
+ [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, nil, :nothing],
+ [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil],
+ [:c_return,18, "xyzzy", Class, :new, xyzzy.class, nil, xyzzy],
[:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
[:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
[:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
@@ -500,29 +583,83 @@ class TestSetTraceFunc < Test::Unit::TestCase
[:call, 13, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing],
[:line, 14, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing],
[:line, 15, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, :nothing],
- [:c_call, 15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, :nothing],
- [:c_return,15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, xyzzy],
[:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy],
[:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy],
[:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
- [:c_call, 20, "xyzzy", Kernel, :raise, self, :outer, :nothing],
- [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, :outer, :nothing],
- [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, :outer, :nothing],
- [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, :outer, raised_exc],
- [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, :outer, raised_exc],
- [:c_return,20, "xyzzy", Kernel, :raise, self, :outer, nil],
- [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, :outer, :nothing],
- [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, :outer, nil],
+ [:c_call, 20, "xyzzy", Kernel, :raise, self, nil, :nothing],
+ [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, nil, :nothing],
+ [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, nil, :nothing],
+ [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, nil, raised_exc],
+ [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, nil, raised_exc],
+ [:c_return,20, "xyzzy", Kernel, :raise, self, nil, nil],
+ [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, nil, :nothing],
+ [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil],
[:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc],
- [:c_call, 20, "xyzzy", Module, :===, RuntimeError,:outer, :nothing],
- [:c_return,20, "xyzzy", Module, :===, RuntimeError,:outer, true],
+ [:c_call, 20, "xyzzy", Module, :===, RuntimeError, nil, :nothing],
+ [:c_return,20, "xyzzy", Module, :===, RuntimeError, nil, true],
[:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
- [:c_call, 21, "xyzzy", TracePoint, :disable, trace, :outer, :nothing],
]
return events, answer_events
end
+ def test_tracepoint
+ events1, answer_events = *trace_by_tracepoint(:line, :class, :end, :call, :return, :c_call, :c_return, :raise)
+
+ ms = [events1, answer_events].map{|evs|
+ evs.map{|e|
+ "#{e[0]} - #{e[2]}:#{e[1]} id: #{e[4]}"
+ }
+ }
+
+ if false # show all events
+ printf(" %-60s | %-60s\n", "actual", "expected")
+ ms[0].zip(ms[1]){|a, b|
+ printf("%s%-60s | %-60s\n", a==b ? ' ' : '!', a, b)
+ }
+ end
+
+ mesg = ms[0].zip(ms[1]).map{|a, b|
+ if a != b
+ "actual: #{a} <-> expected: #{b}"
+ end
+ }.compact.join("\n")
+
+ answer_events.zip(events1){|answer, event|
+ assert_equal answer, event, mesg
+ }
+
+ [:line, :class, :end, :call, :return, :c_call, :c_return, :raise].each{|event|
+ events1, answer_events = *trace_by_tracepoint(event)
+ answer_events.find_all{|e| e[0] == event}.zip(events1){|answer_line, event_line|
+ assert_equal answer_line, event_line
+ }
+ }
+ end
+
+ # Bug #18264
+ def test_tracepoint_memory_leak
+ assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true)
+code = proc { TracePoint.new(:line) { } }
+1_000.times(&code)
+PREP
+1_000_000.times(&code)
+CODE
+ end
+
+ def test_tracepoint_bmethod_memory_leak
+ assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #20194]", rss: true)
+ obj = Object.new
+ obj.define_singleton_method(:foo) {}
+ bmethod = obj.method(:foo)
+ tp = TracePoint.new(:return) {}
+ begin;
+ 1_000_000.times do
+ tp.enable(target: bmethod) {}
+ end
+ end;
+ end
+
def trace_by_set_trace_func
events = []
trace = nil
@@ -530,11 +667,14 @@ class TestSetTraceFunc < Test::Unit::TestCase
xyzzy = nil
xyzzy = xyzzy
_local_var = :outer
+ method = :trace_by_set_trace_func
+ raised_exc = nil
+
eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
1: set_trace_func(lambda{|event, file, line, id, binding, klass|
- 2: events << [event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")] if file == 'xyzzy'
+ 2: events << [event, line, file, klass, id, binding&.eval('self'), binding&.eval("_local_var")] if file == 'xyzzy'
3: })
- 4: 1.times{|;_local_var| _local_var = :inner
+ 4: [1].map!{|;_local_var| _local_var = :inner
5: tap{}
6: }
7: class XYZZY
@@ -554,44 +694,77 @@ class TestSetTraceFunc < Test::Unit::TestCase
21: set_trace_func(nil)
EOF
self.class.class_eval{remove_const(:XYZZY)}
- return events
- end
- def test_tracepoint
- events1, answer_events = *trace_by_tracepoint(:line, :class, :end, :call, :return, :c_call, :c_return, :raise)
-
- ms = [events1, answer_events].map{|evs|
- evs.map{|e|
- "#{e[0]} - #{e[2]}:#{e[1]} id: #{e[4]}"
- }
- }
-
- mesg = ms[0].zip(ms[1]).map{|a, b|
- if a != b
- "#{a} <-> #{b}"
- end
- }.compact.join("\n")
+ answer_events = [
+ #
+ [:c_return, 1, "xyzzy", TracePoint, :trace, TracePoint, nil, nil],
+ [:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing],
+ [:c_call, 4, 'xyzzy', Integer, :times, 1, nil, nil],
+ [:line, 4, 'xyzzy', self.class, method, self, nil, :nothing],
+ [:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing],
+ [:c_return, 4, "xyzzy", Integer, :times, 1, nil, nil],
+ [:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
+ [:c_call, 7, "xyzzy", Class, :inherited, Object, nil, nil],
+ [:c_return, 7, "xyzzy", Class, :inherited, Object, nil, nil],
+ [:c_call, 7, "xyzzy", Class, :const_added, Object, nil, nil],
+ [:c_return, 7, "xyzzy", Class, :const_added, Object, nil, nil],
+ [:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
+ [:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
+ [:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
+ [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
+ [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
+ [:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
+ [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
+ [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
+ [:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
+ [:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
+ [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, nil, nil],
+ [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil],
+ [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil],
+ [:c_return,18, "xyzzy", Class, :new, xyzzy.class, nil, nil],
+ [:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
+ [:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
+ [:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
+ [:line, 11, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, :nothing],
+ [:call, 13, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing],
+ [:line, 14, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing],
+ [:line, 15, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, :nothing],
+ [:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy],
+ [:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy],
+ [:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
+ [:c_call, 20, "xyzzy", Kernel, :raise, self, nil, nil],
+ [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, nil, nil],
+ [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, nil, nil],
+ [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, nil, nil],
+ [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, nil, nil],
+ [:c_return,20, "xyzzy", Kernel, :raise, self, nil, nil],
+ [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil],
+ [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil],
+ [:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc],
+ [:c_call, 20, "xyzzy", Module, :===, RuntimeError, nil, nil],
+ [:c_return,20, "xyzzy", Module, :===, RuntimeError, nil, nil],
+ [:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
+ [:c_call, 21, "xyzzy", TracePoint, :disable, trace, nil, nil],
+ ]
+ return events, answer_events
+ end
- answer_events.zip(events1){|answer, event|
- assert_equal answer, event, mesg
- }
+ def test_set_trace_func_curry_argument_error
+ b = lambda {|x, y, z| (x||0) + (y||0) + (z||0) }.curry[1, 2]
+ set_trace_func(proc {})
+ assert_raise(ArgumentError) {b[3, 4]}
+ end
- events2 = trace_by_set_trace_func
- events1.zip(events2){|ev1, ev2|
- ev2[0] = ev2[0].sub('-', '_').to_sym
- assert_equal ev1[0..2], ev2[0..2], ev1.inspect
+ def test_set_trace_func
+ actual_events, expected_events = trace_by_set_trace_func
+ expected_events.zip(actual_events){|e, a|
+ a[0] = a[0].to_s.sub('-', '_').to_sym
+ assert_equal e[0..2], a[0..2], a.inspect
# event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")
- assert_equal ev1[3].nil?, ev2[3].nil? # klass
- assert_equal ev1[4].nil?, ev2[4].nil? # id
- assert_equal ev1[6], ev2[6] # _local_var
- }
-
- [:line, :class, :end, :call, :return, :c_call, :c_return, :raise].each{|event|
- events1, answer_events = *trace_by_tracepoint(event)
- answer_events.find_all{|e| e[0] == event}.zip(events1){|answer_line, event_line|
- assert_equal answer_line, event_line
- }
+ assert_equal e[3].nil?, a[3].nil? # klass
+ assert_equal e[4].nil?, a[4].nil? # id
+ assert_equal e[6], a[6] # _local_var
}
end
@@ -638,25 +811,30 @@ class TestSetTraceFunc < Test::Unit::TestCase
def test_tracepoint_enable
ary = []
args = nil
- trace = TracePoint.new(:call){|tp|
- next if !target_thread?
- ary << tp.method_id
- }
- foo
- trace.enable{|*a|
- args = a
+ begin
+ trace = TracePoint.new(:call){|tp|
+ next if !target_thread?
+ ary << tp.method_id
+ }
foo
- }
- foo
- assert_equal([:foo], ary)
- assert_equal([], args)
+ trace.enable(target_thread: nil){|*a|
+ args = a
+ foo
+ }
+ foo
+ assert_equal([:foo], ary)
+ assert_equal([], args)
+ ensure
+ trace&.disable
+ end
trace = TracePoint.new{}
begin
assert_equal(false, trace.enable)
assert_equal(true, trace.enable)
- trace.enable{}
- assert_equal(true, trace.enable)
+ trace.enable(target_thread: nil){}
+ trace.disable
+ assert_equal(false, trace.enable)
ensure
trace.disable
end
@@ -667,6 +845,9 @@ class TestSetTraceFunc < Test::Unit::TestCase
args = nil
trace = TracePoint.trace(:call){|tp|
next if !target_thread?
+ # In parallel testing, unexpected events like IO operations may be traced,
+ # so we filter out events here.
+ next unless [TracePoint, TestSetTraceFunc].include?(tp.defined_class)
ary << tp.method_id
}
foo
@@ -676,7 +857,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
}
foo
trace.disable
- assert_equal([:foo, :foo], ary)
+ assert_equal([:foo, :disable, :foo, :disable], ary)
assert_equal([], args)
trace = TracePoint.new{}
@@ -703,6 +884,45 @@ class TestSetTraceFunc < Test::Unit::TestCase
assert_equal(false, trace.enabled?)
end
+ def parameter_test(a, b, c)
+ yield
+ end
+
+ def test_tracepoint_parameters
+ trace = TracePoint.new(:line, :class, :end, :call, :return, :b_call, :b_return, :c_call, :c_return, :raise){|tp|
+ next if !target_thread?
+ next if tp.path != __FILE__
+ case tp.event
+ when :call, :return
+ assert_equal([[:req, :a], [:req, :b], [:req, :c]], tp.parameters)
+ when :b_call, :b_return
+ next if tp.parameters == []
+ if tp.parameters.first == [:opt, :x]
+ assert_equal([[:opt, :x], [:opt, :y], [:opt, :z]], tp.parameters)
+ else
+ assert_equal([[:req, :p], [:req, :q], [:req, :r]], tp.parameters)
+ end
+ when :c_call, :c_return
+ assert_equal([[:req]], tp.parameters) if tp.method_id == :getbyte
+ when :line, :class, :end, :raise
+ assert_raise(RuntimeError) { tp.parameters }
+ end
+ }
+ obj = Object.new
+ trace.enable{
+ parameter_test(1, 2, 3) {|x, y, z|
+ }
+ lambda {|p, q, r| }.call(4, 5, 6)
+ "".getbyte(0)
+ class << obj
+ end
+ begin
+ raise
+ rescue
+ end
+ }
+ end
+
def method_test_tracepoint_return_value obj
obj
end
@@ -723,19 +943,118 @@ class TestSetTraceFunc < Test::Unit::TestCase
}
end
+ def test_tracepoint_attr
+ c = Class.new do
+ attr_accessor :x
+ alias y x
+ alias y= x=
+ end
+ obj = c.new
+
+ ar_meth = obj.method(:x)
+ aw_meth = obj.method(:x=)
+ aar_meth = obj.method(:y)
+ aaw_meth = obj.method(:y=)
+ events = []
+ trace = TracePoint.new(:c_call, :c_return){|tp|
+ next if !target_thread?
+ next if tp.path != __FILE__
+ next if tp.method_id == :call
+ case tp.event
+ when :c_call
+ assert_raise(RuntimeError) {tp.return_value}
+ events << [tp.event, tp.method_id, tp.callee_id]
+ when :c_return
+ events << [tp.event, tp.method_id, tp.callee_id, tp.return_value]
+ end
+ }
+ test_proc = proc do
+ obj.x = 1
+ obj.x
+ obj.y = 2
+ obj.y
+ aw_meth.call(1)
+ ar_meth.call
+ aaw_meth.call(2)
+ aar_meth.call
+ end
+ test_proc.call # populate call caches
+ trace.enable(&test_proc)
+ expected = [
+ [:c_call, :x=, :x=],
+ [:c_return, :x=, :x=, 1],
+ [:c_call, :x, :x],
+ [:c_return, :x, :x, 1],
+ [:c_call, :x=, :y=],
+ [:c_return, :x=, :y=, 2],
+ [:c_call, :x, :y],
+ [:c_return, :x, :y, 2],
+ ]
+ assert_equal(expected*2, events)
+ end
+
+ def test_tracepoint_struct
+ c = Struct.new(:x) do
+ alias y x
+ alias y= x=
+ end
+ obj = c.new
+
+ ar_meth = obj.method(:x)
+ aw_meth = obj.method(:x=)
+ aar_meth = obj.method(:y)
+ aaw_meth = obj.method(:y=)
+ events = []
+ trace = TracePoint.new(:c_call, :c_return){|tp|
+ next if !target_thread?
+ next if tp.path != __FILE__
+ next if tp.method_id == :call
+ case tp.event
+ when :c_call
+ assert_raise(RuntimeError) {tp.return_value}
+ events << [tp.event, tp.method_id, tp.callee_id]
+ when :c_return
+ events << [tp.event, tp.method_id, tp.callee_id, tp.return_value]
+ end
+ }
+ test_proc = proc do
+ obj.x = 1
+ obj.x
+ obj.y = 2
+ obj.y
+ aw_meth.call(1)
+ ar_meth.call
+ aaw_meth.call(2)
+ aar_meth.call
+ end
+ test_proc.call # populate call caches
+ trace.enable(&test_proc)
+ expected = [
+ [:c_call, :x=, :x=],
+ [:c_return, :x=, :x=, 1],
+ [:c_call, :x, :x],
+ [:c_return, :x, :x, 1],
+ [:c_call, :x=, :y=],
+ [:c_return, :x=, :y=, 2],
+ [:c_call, :x, :y],
+ [:c_return, :x, :y, 2],
+ ]
+ assert_equal(expected*2, events)
+ end
+
class XYZZYException < Exception; end
def method_test_tracepoint_raised_exception err
raise err
end
def test_tracepoint_raised_exception
- trace = TracePoint.new(:call, :return){|tp|
+ trace = TracePoint.new(:call, :return, :raise){|tp|
next if !target_thread?
case tp.event
when :call, :return
assert_raise(RuntimeError) { tp.raised_exception }
when :raise
- assert_equal(XYZZYError, tp.raised_exception)
+ assert_kind_of(XYZZYException, tp.raised_exception)
end
}
trace.enable{
@@ -762,7 +1081,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
/return/ =~ tp.event ? tp.return_value : nil
]
}.enable{
- 1.times{
+ [1].map!{
3
}
method_for_test_tracepoint_block{
@@ -772,10 +1091,10 @@ class TestSetTraceFunc < Test::Unit::TestCase
# pp events
# expected_events =
[[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
- [:c_call, :times, Integer, Integer, nil],
+ [:c_call, :map!, Array, Array, nil],
[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
[:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 3],
- [:c_return, :times, Integer, Integer, 1],
+ [:c_return, :map!, Array, Array, [3]],
[:call, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
[:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4],
@@ -799,7 +1118,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
tp.defined_class, #=> nil,
tp.self.class # tp.self return creating/ending thread
]
- }.enable{
+ }.enable(target_thread: nil){
created_thread = Thread.new{thread_self = self}
created_thread.join
}
@@ -812,14 +1131,16 @@ class TestSetTraceFunc < Test::Unit::TestCase
def test_tracepoint_inspect
events = []
+ th = nil
trace = TracePoint.new{|tp|
- next if !target_thread?
+ next if !target_thread? && th != Thread.current
events << [tp.event, tp.inspect]
}
assert_equal("#<TracePoint:disabled>", trace.inspect)
trace.enable{
assert_equal("#<TracePoint:enabled>", trace.inspect)
- Thread.new{}.join
+ th = Thread.new{}
+ th.join
}
assert_equal("#<TracePoint:disabled>", trace.inspect)
events.each{|(ev, str)|
@@ -827,9 +1148,9 @@ class TestSetTraceFunc < Test::Unit::TestCase
when :line
assert_match(/ in /, str)
when :call, :c_call
- assert_match(/call \`/, str) # #<TracePoint:c_call `inherited'@../trunk/test.rb:11>
+ assert_match(/call \'/, str) # #<TracePoint:c_call 'inherited' ../trunk/test.rb:11>
when :return, :c_return
- assert_match(/return \`/, str) # #<TracePoint:return `m'@../trunk/test.rb:3>
+ assert_match(/return \'/, str) # #<TracePoint:return 'm' ../trunk/test.rb:3>
when /thread/
assert_match(/\#<Thread:/, str) # #<TracePoint:thread_end of #<Thread:0x87076c0>>
else
@@ -872,20 +1193,24 @@ class TestSetTraceFunc < Test::Unit::TestCase
def test_tracepoint_with_multithreads
assert_nothing_raised do
- TracePoint.new{
+ TracePoint.new(:line){
10.times{
Thread.pass
}
}.enable do
(1..10).map{
Thread.new{
- 1000.times{
+ 1_000.times{|i|
+ _a = i
}
}
}.each{|th|
th.join
}
end
+ _a = 1
+ _b = 2
+ _c = 3 # to make sure the deletion of unused TracePoints
end
end
@@ -958,15 +1283,17 @@ class TestSetTraceFunc < Test::Unit::TestCase
end
}
assert_normal_exit src % %q{obj.zip({}) {}}, bug7774
- assert_normal_exit src % %q{
- require 'continuation'
- begin
- c = nil
- obj.sort_by {|x| callcc {|c2| c ||= c2 }; x }
- c.call
- rescue RuntimeError
- end
- }, bug7774
+ if respond_to?(:callcc)
+ assert_normal_exit src % %q{
+ require 'continuation'
+ begin
+ c = nil
+ obj.sort_by {|x| callcc {|c2| c ||= c2 }; x }
+ c.call
+ rescue RuntimeError
+ end
+ }, bug7774
+ end
# TracePoint
tp_b = nil
@@ -1026,7 +1353,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
}.enable{
3.times{
next
- } # 3 times b_retun
+ } # 3 times b_return
} # 1 time b_return
assert_equal 4, n
@@ -1068,11 +1395,13 @@ class TestSetTraceFunc < Test::Unit::TestCase
def test_a_call
events = []
+ log = []
TracePoint.new(:a_call){|tp|
next if !target_thread?
events << tp.event
+ log << "| event:#{ tp.event } method_id:#{ tp.method_id } #{ tp.path }:#{ tp.lineno }"
}.enable{
- 1.times{
+ [1].map!{
3
}
method_for_test_tracepoint_block{
@@ -1085,16 +1414,18 @@ class TestSetTraceFunc < Test::Unit::TestCase
:b_call,
:call,
:b_call,
- ], events)
+ ], events, "TracePoint log:\n#{ log.join("\n") }\n")
end
def test_a_return
events = []
+ log = []
TracePoint.new(:a_return){|tp|
next if !target_thread?
events << tp.event
+ log << "| event:#{ tp.event } method_id:#{ tp.method_id } #{ tp.path }:#{ tp.lineno }"
}.enable{
- 1.times{
+ [1].map!{
3
}
method_for_test_tracepoint_block{
@@ -1107,7 +1438,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
:b_return,
:return,
:b_return
- ], events)
+ ], events, "TracePoint log:\n#{ log.join("\n") }\n")
end
def test_const_missing
@@ -1444,7 +1775,6 @@ class TestSetTraceFunc < Test::Unit::TestCase
end
def test_throwing_return_with_finish_frame
- target_th = Thread.current
evs = []
TracePoint.new(:call, :return){|tp|
@@ -1454,7 +1784,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
Bug10724.new
}
- assert_equal([:call, :return], evs)
+ assert_equal([:call, :call, :return, :return], evs)
end
require 'fiber'
@@ -1484,6 +1814,33 @@ class TestSetTraceFunc < Test::Unit::TestCase
assert_equal ev, :fiber_switch
}
+ # test for raise into resumable fiber
+ evs = []
+ f = nil
+ TracePoint.new(:raise, :fiber_switch){|tp|
+ next unless target_thread?
+ evs << [tp.event, Fiber.current]
+ }.enable{
+ f = Fiber.new{
+ Fiber.yield # will raise
+ Fiber.yield # unreachable
+ }
+ begin
+ f.resume
+ f.raise StopIteration
+ rescue StopIteration
+ evs << :rescued
+ end
+ }
+ assert_equal [:fiber_switch, f], evs[0], "initial resume"
+ assert_equal [:fiber_switch, Fiber.current], evs[1], "Fiber.yield"
+ assert_equal [:fiber_switch, f], evs[2], "fiber.raise"
+ assert_equal [:raise, f], evs[3], "fiber.raise"
+ assert_equal [:fiber_switch, Fiber.current], evs[4], "terminated with raise"
+ assert_equal [:raise, Fiber.current], evs[5], "terminated with raise"
+ assert_equal :rescued, evs[6]
+ assert_equal 7, evs.size
+
# test for transfer
evs = []
TracePoint.new(:fiber_switch){|tp|
@@ -1506,6 +1863,41 @@ class TestSetTraceFunc < Test::Unit::TestCase
evs.each{|ev|
assert_equal ev, :fiber_switch
}
+
+ # test for raise and from transferring fibers
+ evs = []
+ f1 = f2 = nil
+ TracePoint.new(:raise, :fiber_switch){|tp|
+ next unless target_thread?
+ evs << [tp.event, Fiber.current]
+ }.enable{
+ f1 = Fiber.new{
+ f2.transfer
+ f2.raise ScriptError
+ Fiber.yield :ok
+ }
+ f2 = Fiber.new{
+ f1.transfer
+ f1.transfer
+ }
+ begin
+ f1.resume
+ rescue ScriptError
+ evs << :rescued
+ end
+ }
+ assert_equal [:fiber_switch, f1], evs[0], "initial resume"
+ assert_equal [:fiber_switch, f2], evs[1], "f2.transfer"
+ assert_equal [:fiber_switch, f1], evs[2], "f1.transfer"
+ assert_equal [:fiber_switch, f2], evs[3], "f2.raise ScriptError"
+ assert_equal [:raise, f2], evs[4], "f2.raise ScriptError"
+ assert_equal [:fiber_switch, f1], evs[5], "f2 unhandled exception"
+ assert_equal [:raise, f1], evs[6], "f2 unhandled exception"
+ assert_equal [:fiber_switch, Fiber.current], evs[7], "f1 unhandled exception"
+ assert_equal [:raise, Fiber.current], evs[8], "f1 unhandled exception"
+ assert_equal :rescued, evs[9], "rescued everything"
+ assert_equal 10, evs.size
+
end
def test_tracepoint_callee_id
@@ -1568,7 +1960,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
TracePoint.new(:return, &capture_events).enable{
o.alias_m
}
- assert_equal [[:return, :m, :alias_m]], events
+ assert_equal [[:return, :tap, :tap], [:return, :m, :alias_m]], events
events.clear
o = Class.new{
@@ -1592,13 +1984,13 @@ class TestSetTraceFunc < Test::Unit::TestCase
events.clear
o = Class.new{
- alias alias_tap tap
- define_method(:m){alias_tap{return}}
+ alias alias_singleton_class singleton_class
+ define_method(:m){alias_singleton_class}
}.new
TracePoint.new(:c_return, &capture_events).enable{
o.m
}
- assert_equal [[:c_return, :tap, :alias_tap]], events
+ assert_equal [[:c_return, :singleton_class, :alias_singleton_class]], events
events.clear
c = Class.new{
@@ -1607,7 +1999,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
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{
@@ -1624,13 +2016,36 @@ class TestSetTraceFunc < Test::Unit::TestCase
def tp_return_value mid
ary = []
- TracePoint.new(:return, :b_return){|tp| next if !target_thread?; ary << [tp.event, tp.method_id, tp.return_value]}.enable{
+ TracePoint.new(:return, :b_return){|tp|
+ next if !target_thread?
+ next if tp.path != __FILE__
+ ary << [tp.event, tp.method_id, tp.return_value]
+ }.enable{
send mid
}
ary.pop # last b_return event is not required.
ary
end
+ def test_single_raise_inside_load
+ events = []
+ tmpdir = Dir.mktmpdir
+ path = "#{tmpdir}/hola.rb"
+ File.open(path, "w") { |f| f.write("raise") }
+ tp = TracePoint.new(:raise) {|tp| events << [tp.event] if target_thread?}
+ tp.enable{
+ load path rescue nil
+ }
+ assert_equal [[:raise]], events
+ events.clear
+ tp.enable{
+ require path rescue nil
+ }
+ assert_equal [[:raise]], events
+ ensure
+ FileUtils.rmtree(tmpdir)
+ end
+
def f_raise
raise
rescue
@@ -1697,7 +2112,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
end
define_method(:f_break_defined) do
- return :f_break_defined
+ break :f_break_defined
end
define_method(:f_raise_defined) do
@@ -1718,27 +2133,44 @@ class TestSetTraceFunc < Test::Unit::TestCase
tp_return_value(:f_last_defined),
'[Bug #13369]'
- assert_equal [[:b_return, :f_return_defined, nil], # current limitation
+ assert_equal [[:b_return, :f_return_defined, :f_return_defined],
[:return, :f_return_defined, :f_return_defined]],
tp_return_value(:f_return_defined),
'[Bug #13369]'
- assert_equal [[:b_return, :f_break_defined, nil],
+ assert_equal [[:b_return, :f_break_defined, :f_break_defined],
[:return, :f_break_defined, :f_break_defined]],
tp_return_value(:f_break_defined),
'[Bug #13369]'
- assert_equal [[:b_return, :f_raise_defined, nil],
+ assert_equal [[:b_return, :f_raise_defined, f_raise_defined],
[:return, :f_raise_defined, f_raise_defined]],
tp_return_value(:f_raise_defined),
'[Bug #13369]'
- assert_equal [[:b_return, :f_break_in_rescue_defined, nil],
+ assert_equal [[:b_return, :f_break_in_rescue_defined, f_break_in_rescue_defined],
[:return, :f_break_in_rescue_defined, f_break_in_rescue_defined]],
tp_return_value(:f_break_in_rescue_defined),
'[Bug #13369]'
end
+ define_method(:just_yield) do |&block|
+ block.call
+ end
+
+ define_method(:unwind_multiple_bmethods) do
+ just_yield { return :unwind_multiple_bmethods }
+ end
+
+ def test_non_local_return_across_multiple_define_methods
+ assert_equal [[:b_return, :unwind_multiple_bmethods, nil],
+ [:b_return, :just_yield, nil],
+ [:return, :just_yield, nil],
+ [:b_return, :unwind_multiple_bmethods, :unwind_multiple_bmethods],
+ [:return, :unwind_multiple_bmethods, :unwind_multiple_bmethods]],
+ tp_return_value(:unwind_multiple_bmethods)
+ end
+
def f_iter
yield
end
@@ -1768,7 +2200,6 @@ class TestSetTraceFunc < Test::Unit::TestCase
define_method(:m) {}
tp = TracePoint.new(:call) do
- next unless target_thread?
raise ''
end
@@ -1795,10 +2226,10 @@ class TestSetTraceFunc < Test::Unit::TestCase
def test_thread_add_trace_func
events = []
base_line = __LINE__
- q = Queue.new
+ q = []
t = Thread.new{
Thread.current.add_trace_func proc{|ev, file, line, *args|
- events << [ev, line]
+ events << [ev, line] if file == __FILE__
} # do not stop trace. They will be stopped at Thread termination.
q.push 1
_x = 1
@@ -1821,7 +2252,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
# other thread
events = []
- m2t_q = Queue.new
+ m2t_q = Thread::Queue.new
t = Thread.new{
Thread.current.abort_on_exception = true
@@ -1846,16 +2277,890 @@ class TestSetTraceFunc < Test::Unit::TestCase
m2t_q.push 1
t.join
- assert_equal ["c-return", base_line + 31], events[0]
- assert_equal ["line", base_line + 32], events[1]
- assert_equal ["line", base_line + 33], events[2]
- assert_equal ["call", base_line + -6], events[3]
- assert_equal ["return", base_line + -4], events[4]
- assert_equal ["line", base_line + 34], events[5]
- assert_equal ["line", base_line + 35], events[6]
- assert_equal ["c-call", base_line + 35], events[7] # Thread.current
- assert_equal ["c-return", base_line + 35], events[8] # Thread.current
- assert_equal ["c-call", base_line + 35], events[9] # Thread#set_trace_func
- assert_equal nil, events[10]
+ assert_equal ["line", base_line + 32], events[0]
+ assert_equal ["line", base_line + 33], events[1]
+ assert_equal ["call", base_line + -6], events[2]
+ assert_equal ["return", base_line + -4], events[3]
+ assert_equal ["line", base_line + 34], events[4]
+ assert_equal ["line", base_line + 35], events[5]
+ assert_equal ["c-call", base_line + 35], events[6] # Thread.current
+ assert_equal ["c-return", base_line + 35], events[7] # Thread.current
+ assert_equal ["c-call", base_line + 35], events[8] # Thread#set_trace_func
+ assert_equal nil, events[9]
+ end
+
+ def test_lineno_in_optimized_insn
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $loc = nil
+ class String
+ undef -@
+ def -@
+ $loc = caller_locations(1, 1)[0].lineno
+ end
+ end
+
+ assert_predicate(-"", :frozen?)
+ assert_equal(__LINE__-1, $loc, '[Bug #14809]')
+ end;
+ end
+
+ def method_for_enable_target1
+ a = 1
+ b = 2
+ 1.times{|i|
+ _x = i
+ }
+ _c = a + b
+ end
+
+ def method_for_enable_target2
+ a = 1
+ b = 2
+ 1.times{|i|
+ _x = i
+ }
+ _c = a + b
+ end
+
+ def check_with_events(trace_point_events, expected_events = trace_point_events)
+ all_events = [[:call, :method_for_enable_target1],
+ [:line, :method_for_enable_target1],
+ [:line, :method_for_enable_target1],
+ [:line, :method_for_enable_target1],
+ [:b_call, :method_for_enable_target1],
+ [:line, :method_for_enable_target1],
+ [:b_return, :method_for_enable_target1],
+ [:line, :method_for_enable_target1],
+ [:return, :method_for_enable_target1],
+ # repeat
+ [:call, :method_for_enable_target1],
+ [:line, :method_for_enable_target1],
+ [:line, :method_for_enable_target1],
+ [:line, :method_for_enable_target1],
+ [:b_call, :method_for_enable_target1],
+ [:line, :method_for_enable_target1],
+ [:b_return, :method_for_enable_target1],
+ [:line, :method_for_enable_target1],
+ [:return, :method_for_enable_target1],
+ ]
+ events = []
+ TracePoint.new(*trace_point_events) do |tp|
+ next unless target_thread?
+ events << [tp.event, tp.method_id]
+ end.enable(target: method(:method_for_enable_target1)) do
+ method_for_enable_target1
+ method_for_enable_target2
+ method_for_enable_target1
+ end
+
+ assert_equal all_events.keep_if { |(ev)| expected_events.include? ev }, events
+ end
+
+ def test_tracepoint_enable_target
+ check_with_events([:line])
+ check_with_events([:call, :return])
+ check_with_events([:line, :call, :return])
+ check_with_events([:call, :return, :b_call, :b_return])
+ check_with_events([:line, :call, :return, :b_call, :b_return])
+
+ # No arguments passed into TracePoint.new enables all ISEQ_TRACE_EVENTS
+ check_with_events([], [:line, :class, :end, :call, :return, :c_call, :c_return, :b_call, :b_return, :rescue])
+
+ # Raise event should be ignored
+ check_with_events([:line, :raise])
+ end
+
+ def test_tracepoint_nested_enabled_with_target
+ code1 = proc{
+ _a = 1
+ }
+ code2 = proc{
+ _b = 2
+ }
+
+ ## error
+
+ # targeted TP and targeted TP
+ ex = assert_raise(ArgumentError) do
+ tp = TracePoint.new(:line){}
+ tp.enable(target: code1){
+ tp.enable(target: code2){}
+ }
+ end
+ assert_equal "can't nest-enable a targeting TracePoint", ex.message
+
+ # global TP and targeted TP
+ ex = assert_raise(ArgumentError) do
+ tp = TracePoint.new(:line){}
+ tp.enable(target_thread: nil){
+ tp.enable(target: code2){}
+ }
+ end
+ assert_equal "can't nest-enable a targeting TracePoint", ex.message
+
+ # targeted TP and global TP
+ ex = assert_raise(ArgumentError) do
+ tp = TracePoint.new(:line){}
+ tp.enable(target: code1){
+ tp.enable{}
+ }
+ end
+ assert_equal "can't nest-enable a targeting TracePoint", ex.message
+
+ # targeted TP and disable
+ ex = assert_raise(ArgumentError) do
+ tp = TracePoint.new(:line){}
+ tp.enable(target: code1){
+ tp.disable{}
+ }
+ end
+ assert_equal "can't disable a targeting TracePoint in a block", ex.message
+
+ ## success with two nesting targeting tracepoints
+ events = []
+ tp1 = TracePoint.new(:line){|tp| events << :tp1}
+ tp2 = TracePoint.new(:line){|tp| events << :tp2}
+ tp1.enable(target: code1) do
+ tp2.enable(target: code1) do
+ code1.call
+ events << :___
+ end
+ end
+ assert_equal [:tp2, :tp1, :___], events
+
+ # success with two tracepoints (global/targeting)
+ events = []
+ tp1 = TracePoint.new(:line){|tp| events << :tp1}
+ tp2 = TracePoint.new(:line){|tp| events << :tp2}
+ tp1.enable do
+ tp2.enable(target: code1) do
+ code1.call
+ events << :___
+ end
+ end
+ assert_equal [:tp1, :tp1, :tp1, :tp1, :tp2, :tp1, :___], events
+
+ # success with two tracepoints (targeting/global)
+ events = []
+ tp1 = TracePoint.new(:line){|tp| events << :tp1}
+ tp2 = TracePoint.new(:line){|tp| events << :tp2}
+ tp1.enable(target: code1) do
+ tp2.enable do
+ code1.call
+ events << :___
+ end
+ end
+ assert_equal [:tp2, :tp2, :tp1, :tp2, :___], events
+ end
+
+ def test_tracepoint_enable_with_target_line
+ events = []
+ line_0 = __LINE__
+ code1 = proc{
+ events << 1
+ events << 2
+ events << 3
+ }
+ tp = TracePoint.new(:line) do |tp|
+ events << :tp
+ end
+ tp.enable(target: code1, target_line: line_0 + 3) do
+ code1.call
+ end
+ assert_equal [1, :tp, 2, 3], events
+
+
+ e = assert_raise(ArgumentError) do
+ TracePoint.new(:line){}.enable(target_line: 10){}
+ end
+ assert_equal 'only target_line is specified', e.message
+
+ e = assert_raise(ArgumentError) do
+ TracePoint.new(:call){}.enable(target: code1, target_line: 10){}
+ end
+ assert_equal 'target_line is specified, but line event is not specified', e.message
+ end
+
+ def test_tracepoint_enable_with_target_line_two_times
+ events = []
+ line_0 = __LINE__
+ code1 = proc{
+ events << 1 # tp1
+ events << 2
+ events << 3 # tp2
+ }
+
+ tp1 = TracePoint.new(:line) do |tp|
+ events << :tp1
+ end
+ tp2 = TracePoint.new(:line) do |tp|
+ events << :tp2
+ end
+
+ tp1.enable(target: code1, target_line: line_0 + 2) do
+ tp2.enable(target: code1, target_line: line_0 + 4) do
+ # two hooks
+ code1.call
+ end
+ end
+ assert_equal [:tp1, 1, 2, :tp2, 3], events
+ end
+
+ def test_multiple_enable
+ ary = []
+ trace = TracePoint.new(:call) do |tp|
+ ary << tp.method_id
+ end
+ trace.enable
+ trace.enable
+ foo
+ trace.disable
+ assert_equal(1, ary.count(:foo), '[Bug #19114]')
+ end
+
+ def test_multiple_tracepoints_same_bmethod
+ events = []
+ tp1 = TracePoint.new(:return) do |tp|
+ events << :tp1
+ end
+ tp2 = TracePoint.new(:return) do |tp|
+ events << :tp2
+ end
+
+ obj = Object.new
+ obj.define_singleton_method(:foo) {}
+ bmethod = obj.method(:foo)
+
+ tp1.enable(target: bmethod) do
+ tp2.enable(target: bmethod) do
+ obj.foo
+ end
+ end
+
+ assert_equal([:tp2, :tp1], events, '[Bug #18031]')
+ end
+
+ def test_script_compiled
+ events = []
+ tp = TracePoint.new(:script_compiled){|tp|
+ next unless target_thread?
+ events << [tp.instruction_sequence.path,
+ tp.eval_script]
+ }
+
+ eval_script = 'a = 1'
+ tp.enable{
+ eval(eval_script, nil, __FILE__+"/eval")
+ nil.instance_eval(eval_script, __FILE__+"/instance_eval")
+ Object.class_eval(eval_script, __FILE__+"/class_eval")
+ }
+ assert_equal [[__FILE__+"/eval", eval_script],
+ [__FILE__+"/instance_eval", eval_script],
+ [__FILE__+"/class_eval", eval_script],
+ ], events
+
+ events.clear
+ tp.enable{
+ begin
+ eval('a=')
+ rescue SyntaxError
+ end
+ }
+ assert_equal [], events, 'script_compiled event should not be invoked on compile error'
+
+ omit "TODO: test for requires"
+
+ events.clear
+ tp.enable{
+ require ''
+ require_relative ''
+ load ''
+ }
+ assert_equal [], events
+ end
+
+ def test_enable_target_thread
+ events = []
+ TracePoint.new(:line) do |tp|
+ events << Thread.current
+ end.enable(target_thread: Thread.current) do
+ _a = 1
+ Thread.new{
+ _b = 2
+ _c = 3
+ }.join
+ _d = 4
+ end
+ assert_equal Array.new(3){Thread.current}, events
+
+ events = []
+ tp = TracePoint.new(:line) do |tp|
+ events << Thread.current
+ end
+
+ q1 = Thread::Queue.new
+ q2 = Thread::Queue.new
+
+ th = Thread.new{
+ q1 << :ok; q2.pop
+ _t1 = 1
+ _t2 = 2
+ }
+ q1.pop
+ tp.enable(target_thread: th) do
+ q2 << 1
+ _a = 1
+ _b = 2
+ th.join
+ end
+
+ assert_equal Array.new(2){th}, events
+ end
+
+ def test_return_bmethod_location
+ bug13392 = "[ruby-core:80515] incorrect bmethod return location"
+ actual = nil
+ obj = Object.new
+ expected = __LINE__ + 1
+ obj.define_singleton_method(:t){}
+ tp = TracePoint.new(:return) do
+ next unless target_thread?
+ actual = tp.lineno
+ end
+ tp.enable {obj.t}
+ assert_equal(expected, actual, bug13392)
+ end
+
+ def test_b_tracepoints_going_away
+ # test that call and return TracePoints continue to work
+ # when b_call and b_return TracePoints stop
+ events = []
+ record_events = ->(tp) do
+ next unless target_thread?
+ events << [tp.event, tp.method_id]
+ end
+
+ call_ret_tp = TracePoint.new(:call, :return, &record_events)
+ block_call_ret_tp = TracePoint.new(:b_call, :b_return, &record_events)
+
+ obj = Object.new
+ obj.define_singleton_method(:foo) {} # a bmethod
+
+ foo = obj.method(:foo)
+ call_ret_tp.enable(target: foo) do
+ block_call_ret_tp.enable(target: foo) do
+ obj.foo
+ end
+ obj.foo
+ end
+
+ assert_equal(
+ [
+ [:call, :foo],
+ [:b_call, :foo],
+ [:b_return, :foo],
+ [:return, :foo],
+ [:call, :foo],
+ [:return, :foo],
+ ],
+ events,
+ )
+ end
+
+ def test_target_different_bmethod_same_iseq
+ # make two bmethods that share the same block iseq
+ block = Proc.new {}
+ obj = Object.new
+ obj.define_singleton_method(:one, &block)
+ obj.define_singleton_method(:two, &block)
+
+ events = []
+ record_events = ->(tp) do
+ next unless target_thread?
+ events << [tp.event, tp.method_id]
+ end
+ tp_one = TracePoint.new(:call, :return, &record_events)
+ tp_two = TracePoint.new(:call, :return, &record_events)
+
+ tp_one.enable(target: obj.method(:one)) do
+ obj.one
+ obj.two # not targeted
+ end
+ assert_equal([[:call, :one], [:return, :one]], events)
+ events.clear
+
+ tp_one.enable(target: obj.method(:one)) do
+ obj.one
+ tp_two.enable(target: obj.method(:two)) do
+ obj.two
+ end
+ obj.two
+ obj.one
+ end
+ assert_equal(
+ [
+ [:call, :one],
+ [:return, :one],
+ [:call, :two],
+ [:return, :two],
+ [:call, :one],
+ [:return, :one],
+ ],
+ events
+ )
+ end
+
+ def test_return_event_with_rescue
+ obj = Object.new
+ def obj.example
+ 1 if 1 == 1
+ rescue
+ end
+ ok = false
+ tp = TracePoint.new(:return) {ok = true}
+ tp.enable {obj.example}
+ assert ok, "return event should be emitted"
+ end
+
+ def test_disable_local_tracepoint_in_trace
+ assert_normal_exit(<<-EOS, timeout: 60)
+ def foo
+ trace = TracePoint.new(:b_return){|tp|
+ tp.disable
+ }
+ trace.enable(target: method(:bar))
+ end
+ def bar
+ 100.times{|i|
+ foo; foo
+ }
+ end
+ bar
+ EOS
+
+ assert_normal_exit(<<-EOS, 'Bug #18730')
+ def bar
+ 42
+ end
+ tp_line = TracePoint.new(:line) do |tp0|
+ tp_multi1 = TracePoint.new(:return, :b_return, :line) do |tp|
+ tp0.disable
+ end
+ tp_multi1.enable
+ end
+ tp_line.enable(target: method(:bar))
+ bar
+ EOS
+ end
+
+ def test_stat_exists
+ assert_instance_of Hash, TracePoint.stat
+ end
+
+ def test_tracepoint_opt_invokebuiltin_delegate_leave
+ code = 'puts RubyVM::InstructionSequence.of("\x00".method(:unpack)).disasm'
+ out = EnvUtil.invoke_ruby(['-e', code], '', true).first
+ assert_match(/^0000 opt_invokebuiltin_delegate_leave /, out)
+
+ event = eval(EnvUtil.invoke_ruby(['-e', <<~'EOS'], '', true).first)
+ TracePoint.new(:return) do |tp|
+ p [tp.event, tp.method_id]
+ end.enable do
+ "\x00".unpack("c")
+ end
+ EOS
+ assert_equal [:return, :unpack], event
+ end
+
+ def test_while_in_while
+ lines = []
+
+ TracePoint.new(:line){|tp|
+ next unless target_thread?
+ lines << tp.lineno
+ }.enable{
+ n = 3
+ while n > 0
+ n -= 1 while n > 0
+ end
+ }
+ assert_equal [__LINE__ - 5, __LINE__ - 4, __LINE__ - 3], lines, 'Bug #17868'
+ end
+
+ def test_allow_reentry
+ event_lines = []
+ _l1 = _l2 = _l3 = _l4 = nil
+ TracePoint.new(:line) do |tp|
+ next unless target_thread?
+
+ event_lines << tp.lineno
+ next if (__LINE__ + 2 .. __LINE__ + 4).cover?(tp.lineno)
+ TracePoint.allow_reentry do
+ _a = 1; _l3 = __LINE__
+ _b = 2; _l4 = __LINE__
+ end
+ end.enable do
+ _c = 3; _l1 = __LINE__
+ _d = 4; _l2 = __LINE__
+ end
+
+ assert_equal [_l1, _l3, _l4, _l2, _l3, _l4], event_lines
+
+ assert_raise RuntimeError do
+ TracePoint.allow_reentry{}
+ end
+ end
+
+ def test_raising_from_b_return_tp_tracing_bmethod
+ assert_normal_exit(<<~RUBY, '[Bug #18060]', timeout: 3)
+ class Foo
+ define_singleton_method(:foo) { return } # a bmethod
+ end
+
+ TracePoint.trace(:b_return) do |tp|
+ raise
+ end
+
+ Foo.foo
+ RUBY
+
+ # Same thing but with a target
+ assert_normal_exit(<<~RUBY, '[Bug #18060]', timeout: 3)
+ class Foo
+ define_singleton_method(:foo) { return } # a bmethod
+ end
+
+ TracePoint.new(:b_return) do |tp|
+ raise
+ end.enable(target: Foo.method(:foo))
+
+ Foo.foo
+ RUBY
+ end
+
+ def helper_cant_rescue
+ begin
+ raise SyntaxError
+ rescue
+ cant_rescue
+ end
+ end
+
+ def test_tp_rescue
+ lines = []
+ TracePoint.new(:line){|tp|
+ next unless target_thread?
+ lines << tp.lineno
+ }.enable{
+ begin
+ helper_cant_rescue
+ rescue SyntaxError
+ end
+ }
+ _call_line = lines.shift
+ _raise_line = lines.shift
+ assert_equal [], lines
+ end
+
+ def helper_can_rescue
+ begin
+ raise __LINE__.to_s
+ rescue SyntaxError
+ :ng
+ rescue
+ :ok
+ end
+ end
+
+ def helper_can_rescue_empty_body
+ begin
+ raise __LINE__.to_s
+ rescue SyntaxError
+ :ng
+ rescue
+ end
+ end
+
+ def test_tp_rescue_event
+ lines = []
+ TracePoint.new(:rescue){|tp|
+ next unless target_thread?
+ lines << [tp.lineno, tp.raised_exception]
+ }.enable{
+ helper_can_rescue
+ }
+
+ line, err, = lines.pop
+ assert_equal [], lines
+ assert err.kind_of?(RuntimeError)
+ assert_equal err.message.to_i + 4, line
+
+ lines = []
+ TracePoint.new(:rescue){|tp|
+ next unless target_thread?
+ lines << [tp.lineno, tp.raised_exception]
+ }.enable{
+ helper_can_rescue_empty_body
+ }
+
+ line, err, = lines.pop
+ assert_equal [], lines
+ assert err.kind_of?(RuntimeError)
+ assert_equal err.message.to_i + 3, line
+ end
+
+ def test_tracepoint_thread_begin
+ target_thread = nil
+
+ trace = TracePoint.new(:thread_begin) do |tp|
+ target_thread = tp.self
+ end
+
+ trace.enable(target_thread: nil) do
+ Thread.new{}.join
+ end
+
+ assert_kind_of(Thread, target_thread)
+ end
+
+ def test_tracepoint_thread_end
+ target_thread = nil
+
+ trace = TracePoint.new(:thread_end) do |tp|
+ target_thread = tp.self
+ end
+
+ trace.enable(target_thread: nil) do
+ Thread.new{}.join
+ end
+
+ assert_kind_of(Thread, target_thread)
+ end
+
+ def test_tracepoint_thread_end_with_exception
+ target_thread = nil
+
+ trace = TracePoint.new(:thread_end) do |tp|
+ target_thread = tp.self
+ end
+
+ trace.enable(target_thread: nil) do
+ thread = Thread.new do
+ Thread.current.report_on_exception = false
+ raise
+ end
+
+ # Ignore the exception raised by the thread:
+ thread.join rescue nil
+ end
+
+ 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
new file mode 100644
index 0000000000..67e2c543a3
--- /dev/null
+++ b/test/ruby/test_shapes.rb
@@ -0,0 +1,1212 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'objspace'
+require 'json'
+require 'securerandom'
+
+# These test the functionality of object shapes
+class TestShapes < Test::Unit::TestCase
+ MANY_IVS = 80
+
+ class IVOrder
+ def expected_ivs
+ %w{ @a @b @c @d @e @f @g @h @i @j @k }
+ end
+
+ def set_ivs
+ expected_ivs.each { instance_variable_set(_1, 1) }
+ self
+ end
+ end
+
+ class ShapeOrder
+ def initialize
+ @b = :b # 5 => 6
+ end
+
+ def set_b
+ @b = :b # 5 => 6
+ end
+
+ def set_c
+ @c = :c # 5 => 7
+ end
+ end
+
+ class OrderedAlloc
+ def add_ivars
+ 10.times do |i|
+ instance_variable_set("@foo" + i.to_s, 0)
+ end
+ end
+ end
+
+ class Example
+ def initialize
+ @a = 1
+ end
+ end
+
+ class RemoveAndAdd
+ def add_foo
+ @foo = 1
+ end
+
+ def remove_foo
+ remove_instance_variable(:@foo)
+ end
+
+ def add_bar
+ @bar = 1
+ end
+ end
+
+ class TooComplex
+ attr_reader :hopefully_unique_name, :b
+
+ def initialize
+ @hopefully_unique_name = "a"
+ @b = "b"
+ end
+
+ # Make enough lazily defined accessors to allow us to force
+ # polymorphism
+ class_eval (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times.map {
+ "def a#{_1}_m; @a#{_1} ||= #{_1}; end"
+ }.join(" ; ")
+
+ class_eval "attr_accessor " + (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times.map {
+ ":a#{_1}"
+ }.join(", ")
+
+ def iv_not_defined; @not_defined; end
+
+ def write_iv_method
+ self.a3 = 12345
+ end
+
+ def write_iv
+ @a3 = 12345
+ end
+ end
+
+ # 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(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(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
+ (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times {
+ IVOrder.new.instance_variable_set("@a#{_1}", 1)
+ }
+
+ obj = IVOrder.new
+ iv_list = obj.set_ivs.instance_variables
+ assert_equal obj.expected_ivs, iv_list.map(&:to_s)
+ end
+
+ def test_too_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ end
+
+ def test_ordered_alloc_is_not_complex
+ 5.times { OrderedAlloc.new.add_ivars }
+ obj = JSON.parse(ObjectSpace.dump(OrderedAlloc))
+ assert_operator obj["variation_count"], :<, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+
+ def test_too_many_ivs_on_obj
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Hi; end
+
+ RubyVM::Shape.exhaust_shapes(2)
+
+ obj = Hi.new
+ obj.instance_variable_set(:@b, 1)
+ obj.instance_variable_set(:@c, 1)
+ obj.instance_variable_set(:@d, 1)
+
+ assert_predicate RubyVM::Shape.of(obj), :too_complex?
+ end;
+ end
+
+ def test_too_many_ivs_on_class
+ obj = Class.new
+
+ 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
+
+ refute_predicate RubyVM::Shape.of(obj), :too_complex?
+ end
+
+ def test_removing_when_too_many_ivs_on_class
+ obj = Class.new
+
+ (MANY_IVS + 2).times do
+ obj.instance_variable_set(:"@a#{_1}", 1)
+ end
+ (MANY_IVS + 2).times do
+ obj.remove_instance_variable(:"@a#{_1}")
+ end
+
+ assert_empty obj.instance_variables
+ end
+
+ def test_removing_when_too_many_ivs_on_module
+ obj = Module.new
+
+ (MANY_IVS + 2).times do
+ obj.instance_variable_set(:"@a#{_1}", 1)
+ end
+ (MANY_IVS + 2).times do
+ obj.remove_instance_variable(:"@a#{_1}")
+ end
+
+ assert_empty obj.instance_variables
+ end
+
+ def test_too_complex_geniv
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class TooComplex < Hash
+ attr_reader :very_unique
+ end
+
+ RubyVM::Shape.exhaust_shapes
+
+ (RubyVM::Shape::SHAPE_MAX_VARIATIONS * 2).times do
+ TooComplex.new.instance_variable_set(:"@unique_#{_1}", 1)
+ end
+
+ tc = TooComplex.new
+ tc.instance_variable_set(:@very_unique, 3)
+ tc.instance_variable_set(:@very_unique2, 4)
+ assert_equal 3, tc.instance_variable_get(:@very_unique)
+ assert_equal 4, tc.instance_variable_get(:@very_unique2)
+
+ assert_equal [:@very_unique, :@very_unique2], tc.instance_variables
+ end;
+ end
+
+ def test_use_all_shapes_then_freeze
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Hi; end
+ RubyVM::Shape.exhaust_shapes(3)
+
+ obj = Hi.new
+ i = 0
+ while RubyVM::Shape.shapes_available > 0
+ obj.instance_variable_set(:"@b#{i}", 1)
+ i += 1
+ end
+ obj.freeze
+
+ assert obj.frozen?
+ end;
+ end
+
+ def test_run_out_of_shape_for_object
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class A
+ def initialize
+ @a = 1
+ end
+ end
+ RubyVM::Shape.exhaust_shapes
+
+ A.new
+ end;
+ end
+
+ def test_run_out_of_shape_for_class_ivar
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ RubyVM::Shape.exhaust_shapes
+
+ c = Class.new
+ c.instance_variable_set(:@a, 1)
+ assert_equal(1, c.instance_variable_get(:@a))
+
+ c.remove_instance_variable(:@a)
+ assert_nil(c.instance_variable_get(:@a))
+
+ assert_raise(NameError) do
+ c.remove_instance_variable(:@a)
+ end
+ end;
+ end
+
+ def test_evacuate_class_ivar_and_compaction
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ count = 20
+
+ c = Class.new
+ count.times do |ivar|
+ c.instance_variable_set("@i#{ivar}", "ivar-#{ivar}")
+ end
+
+ RubyVM::Shape.exhaust_shapes
+
+ GC.auto_compact = true
+ GC.stress = true
+ # Cause evacuation
+ c.instance_variable_set(:@a, o = Object.new)
+ assert_equal(o, c.instance_variable_get(:@a))
+ GC.stress = false
+
+ count.times do |ivar|
+ assert_equal "ivar-#{ivar}", c.instance_variable_get("@i#{ivar}")
+ end
+ end;
+ end
+
+ def test_evacuate_generic_ivar_and_compaction
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ count = 20
+
+ c = Hash.new
+ count.times do |ivar|
+ c.instance_variable_set("@i#{ivar}", "ivar-#{ivar}")
+ end
+
+ RubyVM::Shape.exhaust_shapes
+
+ GC.auto_compact = true
+ GC.stress = true
+
+ # Cause evacuation
+ c.instance_variable_set(:@a, o = Object.new)
+ assert_equal(o, c.instance_variable_get(:@a))
+
+ GC.stress = false
+
+ count.times do |ivar|
+ assert_equal "ivar-#{ivar}", c.instance_variable_get("@i#{ivar}")
+ end
+ end;
+ end
+
+ def test_evacuate_object_ivar_and_compaction
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ count = 20
+
+ c = Object.new
+ count.times do |ivar|
+ c.instance_variable_set("@i#{ivar}", "ivar-#{ivar}")
+ end
+
+ RubyVM::Shape.exhaust_shapes
+
+ GC.auto_compact = true
+ GC.stress = true
+
+ # Cause evacuation
+ c.instance_variable_set(:@a, o = Object.new)
+ assert_equal(o, c.instance_variable_get(:@a))
+
+ GC.stress = false
+
+ count.times do |ivar|
+ assert_equal "ivar-#{ivar}", c.instance_variable_get("@i#{ivar}")
+ end
+ end;
+ end
+
+ def test_gc_stress_during_evacuate_generic_ivar
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ [].instance_variable_set(:@a, 1)
+
+ RubyVM::Shape.exhaust_shapes
+
+ ary = 10.times.map { [] }
+
+ GC.stress = true
+ ary.each do |o|
+ o.instance_variable_set(:@a, 1)
+ o.instance_variable_set(:@b, 1)
+ end
+ end;
+ end
+
+ def test_run_out_of_shape_for_module_ivar
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ RubyVM::Shape.exhaust_shapes
+
+ module Foo
+ @a = 1
+ @b = 2
+ assert_equal 1, @a
+ assert_equal 2, @b
+ end
+ end;
+ end
+
+ def test_run_out_of_shape_for_class_cvar
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ RubyVM::Shape.exhaust_shapes
+
+ c = Class.new
+
+ c.class_variable_set(:@@a, 1)
+ assert_equal(1, c.class_variable_get(:@@a))
+
+ c.class_eval { remove_class_variable(:@@a) }
+ assert_false(c.class_variable_defined?(:@@a))
+
+ assert_raise(NameError) do
+ c.class_eval { remove_class_variable(:@@a) }
+ end
+ end;
+ end
+
+ def test_run_out_of_shape_generic_instance_variable_set
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class TooComplex < Hash
+ end
+
+ RubyVM::Shape.exhaust_shapes
+
+ tc = TooComplex.new
+ tc.instance_variable_set(:@a, 1)
+ tc.instance_variable_set(:@b, 2)
+
+ tc.remove_instance_variable(:@a)
+ assert_nil(tc.instance_variable_get(:@a))
+
+ assert_raise(NameError) do
+ tc.remove_instance_variable(:@a)
+ end
+ end;
+ end
+
+ def test_run_out_of_shape_generic_ivar_set
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Hi < String
+ def initialize
+ 8.times do |i|
+ instance_variable_set("@ivar_#{i}", i)
+ end
+ end
+
+ def transition
+ @hi_transition ||= 1
+ end
+ end
+
+ a = Hi.new
+
+ # Try to run out of shapes
+ RubyVM::Shape.exhaust_shapes
+
+ assert_equal 1, a.transition
+ assert_equal 1, a.transition
+ end;
+ end
+
+ def test_run_out_of_shape_instance_variable_defined
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class A
+ attr_reader :a, :b, :c, :d
+ def initialize
+ @a = @b = @c = @d = 1
+ end
+ end
+
+ RubyVM::Shape.exhaust_shapes
+
+ a = A.new
+ assert_equal true, a.instance_variable_defined?(:@a)
+ end;
+ end
+
+ def test_run_out_of_shape_instance_variable_defined_on_module
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ RubyVM::Shape.exhaust_shapes
+
+ module A
+ @a = @b = @c = @d = 1
+ end
+
+ assert_equal true, A.instance_variable_defined?(:@a)
+ end;
+ end
+
+ def test_run_out_of_shape_during_remove_instance_variable
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ o = Object.new
+ 10.times { |i| o.instance_variable_set(:"@a#{i}", i) }
+
+ RubyVM::Shape.exhaust_shapes
+
+ o.remove_instance_variable(:@a0)
+ (1...10).each do |i|
+ assert_equal(i, o.instance_variable_get(:"@a#{i}"))
+ end
+ end;
+ end
+
+ def test_run_out_of_shape_remove_instance_variable
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class A
+ attr_reader :a, :b, :c, :d
+ def initialize
+ @a = @b = @c = @d = 1
+ end
+ end
+
+ a = A.new
+
+ RubyVM::Shape.exhaust_shapes
+
+ a.remove_instance_variable(:@b)
+ assert_nil a.b
+
+ a.remove_instance_variable(:@a)
+ assert_nil a.a
+
+ a.remove_instance_variable(:@c)
+ assert_nil a.c
+
+ assert_equal 1, a.d
+ end;
+ end
+
+ def test_run_out_of_shape_rb_obj_copy_ivar
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class A
+ def initialize
+ init # Avoid right sizing
+ end
+
+ def init
+ @a = @b = @c = @d = @e = @f = 1
+ end
+ end
+
+ a = A.new
+
+ RubyVM::Shape.exhaust_shapes
+
+ a.dup
+ end;
+ end
+
+ def test_evacuate_generic_ivar_memory_leak
+ assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", rss: true)
+ o = []
+ o.instance_variable_set(:@a, 1)
+
+ RubyVM::Shape.exhaust_shapes
+
+ ary = 1_000_000.times.map { [] }
+ begin;
+ ary.each do |o|
+ o.instance_variable_set(:@a, 1)
+ o.instance_variable_set(:@b, 1)
+ end
+ ary.clear
+ ary = nil
+ GC.start
+ end;
+ end
+
+ def test_use_all_shapes_module
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Hi; end
+
+ RubyVM::Shape.exhaust_shapes(2)
+
+ obj = Module.new
+ 3.times do
+ obj.instance_variable_set(:"@a#{_1}", _1)
+ end
+
+ ivs = 3.times.map do
+ obj.instance_variable_get(:"@a#{_1}")
+ end
+
+ assert_equal [0, 1, 2], ivs
+ end;
+ end
+
+ def test_complex_freeze_after_clone
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Hi; end
+
+ RubyVM::Shape.exhaust_shapes(2)
+
+ obj = Object.new
+ i = 0
+ while RubyVM::Shape.shapes_available > 0
+ obj.instance_variable_set(:"@b#{i}", i)
+ i += 1
+ end
+
+ v = obj.clone(freeze: true)
+ assert_predicate v, :frozen?
+ assert_equal 0, v.instance_variable_get(:@b0)
+ end;
+ end
+
+ def test_too_complex_ractor
+ 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)
+
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal 3, tc.very_unique
+ 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
+
+ def test_too_complex_ractor_shareable
+ 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)
+
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal 3, tc.very_unique
+ assert_equal 3, Ractor.make_shareable(tc).very_unique
+ 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;
+ $VERBOSE = nil
+
+ RubyVM::Shape.exhaust_shapes
+
+ r = Ractor.new do
+ o = Object.new
+ o.instance_variable_set(:@a, "hello")
+ o
+ end
+
+ o = r.value
+ assert_equal "hello", o.instance_variable_get(:@a)
+ end;
+ end
+
+ def test_too_complex_generic_ivar_ractor_share
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $VERBOSE = nil
+
+ RubyVM::Shape.exhaust_shapes
+
+ r = Ractor.new do
+ o = []
+ o.instance_variable_set(:@a, "hello")
+ o
+ end
+
+ o = r.value
+ assert_equal "hello", o.instance_variable_get(:@a)
+ end;
+ end
+
+ def test_read_iv_after_complex
+ 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
+ end
+
+ def test_read_method_after_complex
+ 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
+ assert_equal 3, tc.a3
+ end
+
+ def test_write_method_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ tc.write_iv_method
+ tc.write_iv_method
+ assert_equal 12345, tc.a3_m
+ assert_equal 12345, tc.a3
+ end
+
+ def test_write_iv_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ tc.write_iv
+ tc.write_iv
+ assert_equal 12345, tc.a3_m
+ assert_equal 12345, tc.a3
+ end
+
+ def test_iv_read_via_method_after_complex
+ 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
+ assert_equal 3, tc.instance_variable_get(:@a3)
+ end
+
+ def test_delete_iv_after_complex
+ 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.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
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+
+ refute tc.instance_variable_defined?(:@a3)
+ assert_raise(NameError) do
+ tc.remove_instance_variable(:@a3)
+ end
+ assert_nil tc.a3
+ end
+
+ def test_remove_instance_variable
+ ivars_count = 5
+ object = Object.new
+ ivars_count.times do |i|
+ object.instance_variable_set("@ivar_#{i}", i)
+ end
+
+ ivars = ivars_count.times.map do |i|
+ object.instance_variable_get("@ivar_#{i}")
+ end
+ assert_equal [0, 1, 2, 3, 4], ivars
+
+ object.remove_instance_variable(:@ivar_2)
+
+ ivars = ivars_count.times.map do |i|
+ object.instance_variable_get("@ivar_#{i}")
+ end
+ assert_equal [0, 1, nil, 3, 4], ivars
+ end
+
+ def test_remove_instance_variable_when_out_of_shapes
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ ivars_count = 5
+ object = Object.new
+ ivars_count.times do |i|
+ object.instance_variable_set("@ivar_#{i}", i)
+ end
+
+ ivars = ivars_count.times.map do |i|
+ object.instance_variable_get("@ivar_#{i}")
+ end
+ assert_equal [0, 1, 2, 3, 4], ivars
+
+ RubyVM::Shape.exhaust_shapes
+
+ object.remove_instance_variable(:@ivar_2)
+
+ ivars = ivars_count.times.map do |i|
+ object.instance_variable_get("@ivar_#{i}")
+ end
+ assert_equal [0, 1, nil, 3, 4], ivars
+ end;
+ end
+
+ def test_remove_instance_variable_capacity_transition
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+
+ # 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
+
+ # b transitions in capacity
+ b = Class.new.new
+ (initial_capacity + 1).times do |i|
+ b.instance_variable_set(:"@ivar#{i}", i)
+ end
+
+ assert_operator(RubyVM::Shape.of(a).capacity, :<, RubyVM::Shape.of(b).capacity)
+
+ # b will now have the same tree as a
+ b.remove_instance_variable(:@ivar0)
+
+ a.instance_variable_set(:@foo, 1)
+ a.instance_variable_set(:@bar, 1)
+
+ # Check that there is no heap corruption
+ GC.verify_internal_consistency
+ end;
+ end
+
+ def test_freeze_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ tc.freeze
+ assert_raise(FrozenError) { tc.a3_m }
+ # doesn't transition to frozen shape in this case
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ end
+
+ def test_read_undefined_iv_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal nil, tc.iv_not_defined
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ end
+
+ def test_shape_order
+ bar = ShapeOrder.new # 0 => 1
+ bar.set_c # 1 => 2
+ bar.set_b # 2 => 2
+
+ foo = ShapeOrder.new # 0 => 1
+ shape_id = RubyVM::Shape.of(foo).id
+ foo.set_b # should not transition
+ assert_equal shape_id, RubyVM::Shape.of(foo).id
+ end
+
+ def test_iv_index
+ example = RemoveAndAdd.new
+ initial_shape = RubyVM::Shape.of(example)
+ 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.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)
+ assert_equal([], example.instance_variables)
+ assert_shape_equal(initial_shape, remove_foo_shape)
+
+ example.add_bar # makes a transition
+ bar_shape = RubyVM::Shape.of(example)
+ assert_equal([:@bar], example.instance_variables)
+ 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
+ example = RemoveAndAdd.new
+ _initial_shape = RubyVM::Shape.of(example)
+
+ example.add_foo # makes a transition
+ add_foo_shape = RubyVM::Shape.of(example)
+ example.remove_foo # makes a transition
+ example.add_foo # makes a transition
+ assert_shape_equal(add_foo_shape, RubyVM::Shape.of(example))
+ end
+
+ class TestObject; end
+
+ def test_new_obj_has_t_object_shape
+ obj = TestObject.new
+ shape = RubyVM::Shape.of(obj)
+ assert_equal RubyVM::Shape::SHAPE_ROOT, shape.type
+ assert_nil shape.parent
+ end
+
+ def test_str_has_root_shape
+ assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(""))
+ end
+
+ def test_array_has_root_shape
+ assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of([]))
+ end
+
+ 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_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
+ obj = Example.new
+ shape = RubyVM::Shape.of(obj)
+ refute_equal(RubyVM::Shape.root_shape, shape)
+ assert_equal :@a, shape.edge_name
+ assert_equal RubyVM::Shape::SHAPE_IVAR, shape.type
+
+ shape = shape.parent
+ assert_equal RubyVM::Shape::SHAPE_ROOT, shape.type
+ assert_nil shape.parent
+
+ assert_equal(1, obj.instance_variable_get(:@a))
+ end
+
+ def test_different_objects_make_same_transition
+ obj = []
+ obj2 = ""
+ obj.instance_variable_set(:@a, 1)
+ obj2.instance_variable_set(:@a, 1)
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ end
+
+ def test_duplicating_objects
+ obj = Example.new
+ obj2 = obj.dup
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ end
+
+ def test_duplicating_too_complex_objects_memory_leak
+ assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", "[Bug #20162]", rss: true)
+ RubyVM::Shape.exhaust_shapes
+
+ o = Object.new
+ o.instance_variable_set(:@a, 0)
+ begin;
+ 1_000_000.times do
+ o.dup
+ end
+ end;
+ end
+
+ 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?)
+ refute_predicate(RubyVM::Shape.of(obj2), :shape_frozen?)
+ end
+
+ def test_freezing_and_duplicating_object_with_ivars
+ obj = Example.new.freeze
+ obj2 = obj.dup
+ refute_predicate(obj2, :frozen?)
+ refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ assert_equal(obj2.instance_variable_get(:@a), 1)
+ end
+
+ def test_freezing_and_duplicating_string_with_ivars
+ str = "str"
+ str.instance_variable_set(:@a, 1)
+ 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
+
+ def test_freezing_and_cloning_objects
+ obj = Object.new.freeze
+ obj2 = obj.clone(freeze: true)
+ assert_predicate(obj2, :frozen?)
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ end
+
+ def test_cloning_with_freeze_option
+ obj = Object.new
+ obj2 = obj.clone(freeze: true)
+ assert_predicate(obj2, :frozen?)
+ refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ assert_predicate(RubyVM::Shape.of(obj2), :shape_frozen?)
+ end
+
+ def test_freezing_and_cloning_object_with_ivars
+ obj = Example.new.freeze
+ obj2 = obj.clone(freeze: true)
+ assert_predicate(obj2, :frozen?)
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ assert_equal(obj2.instance_variable_get(:@a), 1)
+ end
+
+ def test_freezing_and_cloning_string
+ str = ("str" + "str").freeze
+ str2 = str.clone(freeze: true)
+ assert_predicate(str2, :frozen?)
+ assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2))
+ end
+
+ def test_freezing_and_cloning_string_with_ivars
+ str = "str"
+ str.instance_variable_set(:@a, 1)
+ str.freeze
+ str2 = str.clone(freeze: true)
+ assert_predicate(str2, :frozen?)
+ assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2))
+ assert_equal(str2.instance_variable_get(:@a), 1)
+ end
+
+ def test_out_of_bounds_shape
+ assert_raise ArgumentError do
+ RubyVM::Shape.find_by_id(RubyVM.stat[:next_shape_id])
+ end
+ assert_raise ArgumentError do
+ RubyVM::Shape.find_by_id(-1)
+ end
+ end
+
+ def ensure_complex
+ RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
+ tc = TooComplex.new
+ 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 2d98b0b564..091a66d5da 100644
--- a/test/ruby/test_signal.rb
+++ b/test/ruby/test_signal.rb
@@ -28,7 +28,8 @@ class TestSignal < Test::Unit::TestCase
def test_signal_process_group
bug4362 = '[ruby-dev:43169]'
assert_nothing_raised(bug4362) do
- pid = Process.spawn(EnvUtil.rubybin, '-e', 'sleep 10', :pgroup => true)
+ cmd = [ EnvUtil.rubybin, '--disable=gems' '-e', 'sleep 10' ]
+ pid = Process.spawn(*cmd, :pgroup => true)
Process.kill(:"-TERM", pid)
Process.waitpid(pid)
assert_equal(true, $?.signaled?)
@@ -44,7 +45,7 @@ class TestSignal < Test::Unit::TestCase
sig = "INT"
term = :KILL
end
- IO.popen([EnvUtil.rubybin, '-e', <<-"End"], 'r+') do |io|
+ IO.popen([EnvUtil.rubybin, '--disable=gems', '-e', <<-"End"], 'r+') do |io|
Signal.trap(:#{sig}, "EXIT")
STDOUT.syswrite("a")
Thread.start { sleep(2) }
@@ -83,10 +84,12 @@ class TestSignal < Test::Unit::TestCase
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { SignalException.new("\u{30eb 30d3 30fc}") }
Signal.list.each do |signm, signo|
next if signm == "EXIT"
- assert_equal(SignalException.new(signm).signo, signo)
- assert_equal(SignalException.new(signm.to_sym).signo, signo)
- assert_equal(SignalException.new(signo).signo, signo)
+ assert_equal(signo, SignalException.new(signm).signo, signm)
+ assert_equal(signo, SignalException.new(signm.to_sym).signo, signm)
+ assert_equal(signo, SignalException.new(signo).signo, signo)
end
+ e = assert_raise(ArgumentError) {SignalException.new("-SIGEXIT")}
+ assert_not_match(/SIG-SIG/, e.message)
end
def test_interrupt
@@ -134,11 +137,6 @@ class TestSignal < Test::Unit::TestCase
assert_raise(ArgumentError) { Signal.trap }
- assert_raise(SecurityError) do
- s = proc {}.taint
- Signal.trap(:INT, s)
- end
-
# FIXME!
Signal.trap(:INT, nil)
Signal.trap(:INT, "")
@@ -164,6 +162,8 @@ class TestSignal < Test::Unit::TestCase
assert_raise(ArgumentError) { Signal.trap("XXXXXXXXXX", "SIG_DFL") }
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Signal.trap("\u{30eb 30d3 30fc}", "SIG_DFL") }
+
+ assert_raise(ArgumentError) { Signal.trap("EXIT\0") {} }
ensure
Signal.trap(:INT, oldtrap) if oldtrap
end
@@ -228,20 +228,20 @@ class TestSignal < Test::Unit::TestCase
end
def test_signame_delivered
- 10.times do
- IO.popen([EnvUtil.rubybin, "-e", <<EOS, :err => File::NULL]) do |child|
- Signal.trap("INT") do |signo|
- signame = Signal.signame(signo)
- Marshal.dump(signame, STDOUT)
- STDOUT.flush
- exit 0
- end
- Process.kill("INT", $$)
- sleep 1 # wait signal deliver
-EOS
+ args = [EnvUtil.rubybin, "--disable=gems", "-e", <<"", :err => File::NULL]
+ Signal.trap("INT") do |signo|
+ signame = Signal.signame(signo)
+ Marshal.dump(signame, STDOUT)
+ STDOUT.flush
+ exit 0
+ end
+ Process.kill("INT", $$)
+ sleep 1 # wait signal deliver
+ 10.times do
+ IO.popen(args) do |child|
signame = Marshal.load(child)
- assert_equal(signame, "INT")
+ assert_equal("INT", signame)
end
end
end if Process.respond_to?(:kill)
@@ -291,7 +291,8 @@ EOS
if trap = Signal.list['TRAP']
bug9820 = '[ruby-dev:48592] [Bug #9820]'
- status = assert_in_out_err(['-e', 'Process.kill(:TRAP, $$)'])
+ no_core = "Process.setrlimit(Process::RLIMIT_CORE, 0); " if defined?(Process.setrlimit) && defined?(Process::RLIMIT_CORE)
+ status = assert_in_out_err(['-e', "#{no_core}Process.kill(:TRAP, $$)"])
assert_predicate(status, :signaled?, bug9820)
assert_equal(trap, status.termsig, bug9820)
end
@@ -309,16 +310,44 @@ EOS
end
def test_self_stop
- assert_ruby_status([], <<-'end;')
- begin
- fork{
- sleep 1
- Process.kill(:CONT, Process.ppid)
- }
- Process.kill(:STOP, Process.pid)
- rescue NotImplementedError
- # ok
- end
- end;
+ omit unless Process.respond_to?(:fork)
+ omit unless defined?(Process::WUNTRACED)
+
+ # Make a process that stops itself
+ child_pid = fork do
+ Process.kill(:STOP, Process.pid)
+ end
+
+ # The parent should be notified about the stop
+ _, status = Process.waitpid2(child_pid, Process::WUNTRACED)
+ 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_predicate status, :exited?
+ assert_predicate status, :success?
end
+
+ def test_sigwait_fd_unused
+ t = EnvUtil.apply_timeout_scale(0.1)
+ assert_ruby_status([], <<-End)
+ tgt = $$
+ trap(:TERM) { exit(0) }
+ e = "Process.daemon; sleep #{t * 2}; Process.kill(:TERM,\#{tgt})"
+ term = [ '#{EnvUtil.rubybin}', '--disable=gems', '-e', e ]
+ t2 = Thread.new { sleep } # grab sigwait_fd
+ Thread.pass until t2.stop?
+ Thread.new do
+ sleep #{t}
+ t2.kill
+ t2.join
+ end
+ Process.spawn(*term)
+ # last thread remaining, ensure it can react to SIGTERM
+ loop { sleep }
+ End
+ end if Process.respond_to?(:kill) && Process.respond_to?(:daemon)
end
diff --git a/test/ruby/test_sleep.rb b/test/ruby/test_sleep.rb
index 61002b8b18..7ef962db4a 100644
--- a/test/ruby/test_sleep.rb
+++ b/test/ruby/test_sleep.rb
@@ -1,17 +1,34 @@
# frozen_string_literal: false
require 'test/unit'
require 'etc'
+require 'timeout'
class TestSleep < Test::Unit::TestCase
def test_sleep_5sec
- GC.disable
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
- sleep 5
- slept = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
- bottom = 5.0
- assert_operator(slept, :>=, bottom)
- assert_operator(slept, :<=, 6.0, "[ruby-core:18015]: longer than expected")
- ensure
- GC.enable
+ EnvUtil.without_gc do
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ sleep 5
+ slept = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
+ bottom = 5.0
+ assert_operator(slept, :>=, bottom)
+ 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_sprintf.rb b/test/ruby/test_sprintf.rb
index a07ac7908b..1c7e89c265 100644
--- a/test/ruby/test_sprintf.rb
+++ b/test/ruby/test_sprintf.rb
@@ -227,6 +227,10 @@ class TestSprintf < Test::Unit::TestCase
bug11766 = '[ruby-core:71806] [Bug #11766]'
assert_equal("x"*10+" 1.0", sprintf("x"*10+"%8.1f", 1r), bug11766)
+
+ require 'rbconfig/sizeof'
+ fmin, fmax = RbConfig::LIMITS.values_at("FIXNUM_MIN", "FIXNUM_MAX")
+ assert_match(/\A-\d+\.\d+\z/, sprintf("%f", Rational(fmin, fmax)))
end
def test_rational_precision
@@ -235,7 +239,13 @@ class TestSprintf < Test::Unit::TestCase
def test_hash
options = {:capture=>/\d+/}
- assert_equal("with options {:capture=>/\\d+/}", sprintf("with options %p" % options))
+ assert_equal("with options #{options.inspect}", sprintf("with options %p" % options))
+ end
+
+ def test_inspect
+ obj = Object.new
+ def obj.inspect; "TEST"; end
+ assert_equal("<TEST>", sprintf("<%p>", obj))
end
def test_invalid
@@ -260,8 +270,8 @@ class TestSprintf < Test::Unit::TestCase
# Specifying the precision multiple times with negative star arguments:
assert_raise(ArgumentError, "[ruby-core:11570]") {sprintf("%.*.*.*.*f", -1, -1, -1, 5, 1)}
- # Null bytes after percent signs are removed:
- assert_equal("%\0x hello", sprintf("%\0x hello"), "[ruby-core:11571]")
+ assert_raise(ArgumentError) {sprintf("%\0x hello")}
+ assert_raise(ArgumentError) {sprintf("%\nx hello")}
assert_raise(ArgumentError, "[ruby-core:11573]") {sprintf("%.25555555555555555555555555555555555555s", "hello")}
@@ -273,10 +283,9 @@ class TestSprintf < Test::Unit::TestCase
assert_raise_with_message(ArgumentError, /unnumbered\(1\) mixed with numbered/) { sprintf("%1$*d", 3) }
assert_raise_with_message(ArgumentError, /unnumbered\(1\) mixed with numbered/) { sprintf("%1$.*d", 3) }
- verbose, $VERBOSE = $VERBOSE, nil
- assert_nothing_raised { sprintf("", 1) }
- ensure
- $VERBOSE = verbose
+ assert_warning(/too many arguments/) do
+ sprintf("", 1)
+ end
end
def test_float
@@ -356,11 +365,16 @@ class TestSprintf < Test::Unit::TestCase
def test_char
assert_equal("a", sprintf("%c", 97))
assert_equal("a", sprintf("%c", ?a))
- assert_raise(ArgumentError) { sprintf("%c", sprintf("%c%c", ?a, ?a)) }
+ assert_equal("a", sprintf("%c", "a"))
+ assert_equal("a", sprintf("%c", sprintf("%c%c", ?a, ?a)))
assert_equal(" " * (BSIZ - 1) + "a", sprintf(" " * (BSIZ - 1) + "%c", ?a))
assert_equal(" " * (BSIZ - 1) + "a", sprintf(" " * (BSIZ - 1) + "%-1c", ?a))
assert_equal(" " * BSIZ + "a", sprintf("%#{ BSIZ + 1 }c", ?a))
assert_equal("a" + " " * BSIZ, sprintf("%-#{ BSIZ + 1 }c", ?a))
+ assert_raise(ArgumentError) { sprintf("%c", -1) }
+ s = sprintf("%c".encode(Encoding::US_ASCII), 0x80)
+ assert_equal("\x80".b, s)
+ assert_predicate(s, :valid_encoding?)
end
def test_string
@@ -501,6 +515,16 @@ class TestSprintf < Test::Unit::TestCase
end
end
+ def test_coderange
+ format_str = "wrong constant name %s"
+ interpolated_str = "\u3042"
+ assert_predicate format_str, :ascii_only?
+ refute_predicate interpolated_str, :ascii_only?
+
+ str = format_str % interpolated_str
+ refute_predicate str, :ascii_only?
+ end
+
def test_named_default
h = Hash.new('world')
assert_equal("hello world", "hello %{location}" % h)
@@ -523,13 +547,11 @@ class TestSprintf < Test::Unit::TestCase
end
end
- def test_no_hidden_garbage
- fmt = [4, 2, 2].map { |x| "%0#{x}d" }.join('-') # defeats optimization
- ObjectSpace.count_objects(res = {}) # creates strings on first call
- before = ObjectSpace.count_objects(res)[:T_STRING]
- val = sprintf(fmt, 1970, 1, 1)
- after = ObjectSpace.count_objects(res)[:T_STRING]
- assert_equal before + 1, after, 'only new string is the created one'
- assert_equal '1970-01-01', val
+ def test_binary_format_coderange
+ 1.upto(500) do |i|
+ str = sprintf("%*s".b, i, "\xe2".b)
+ refute_predicate str, :ascii_only?
+ assert_equal i, str.bytesize
+ end
end
end
diff --git a/test/ruby/test_stack.rb b/test/ruby/test_stack.rb
new file mode 100644
index 0000000000..8a78848322
--- /dev/null
+++ b/test/ruby/test_stack.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'tmpdir'
+
+class TestStack < Test::Unit::TestCase
+ LARGE_VM_STACK_SIZE = 1024*1024*5
+ LARGE_MACHINE_STACK_SIZE = 1024*1024*10
+
+ def initialize(*)
+ super
+
+ @h_default = nil
+ @h_0 = nil
+ @h_large = nil
+ end
+
+ def invoke_ruby script, vm_stack_size: nil, machine_stack_size: nil
+ env = {}
+ env['RUBY_FIBER_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size
+ env['RUBY_FIBER_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size
+
+ stdout, stderr, status = EnvUtil.invoke_ruby([env, '-e', script], '', true, true, timeout: 30)
+ assert(!status.signaled?, FailDesc[status, nil, stderr])
+
+ return stdout
+ end
+
+ def h_default
+ @h_default ||= eval(invoke_ruby('p RubyVM::DEFAULT_PARAMS'))
+ end
+
+ def h_0
+ @h_0 ||= eval(invoke_ruby('p RubyVM::DEFAULT_PARAMS',
+ vm_stack_size: 0,
+ machine_stack_size: 0
+ ))
+ end
+
+ def h_large
+ @h_large ||= eval(invoke_ruby('p RubyVM::DEFAULT_PARAMS',
+ vm_stack_size: LARGE_VM_STACK_SIZE,
+ machine_stack_size: LARGE_MACHINE_STACK_SIZE
+ ))
+ end
+
+ def test_relative_stack_sizes
+ assert_operator(h_default[:fiber_vm_stack_size], :>, h_0[:fiber_vm_stack_size])
+ assert_operator(h_default[:fiber_vm_stack_size], :<, h_large[:fiber_vm_stack_size])
+ assert_operator(h_default[:fiber_machine_stack_size], :>=, h_0[:fiber_machine_stack_size])
+ assert_operator(h_default[:fiber_machine_stack_size], :<=, h_large[:fiber_machine_stack_size])
+ end
+
+ def test_vm_stack_size
+ script = '$stdout.sync=true; def rec; print "."; rec; end; Fiber.new{rec}.resume'
+
+ size_default = invoke_ruby(script).bytesize
+ assert_operator(size_default, :>, 0)
+
+ size_0 = invoke_ruby(script, vm_stack_size: 0).bytesize
+ assert_operator(size_default, :>, size_0)
+
+ size_large = invoke_ruby(script, vm_stack_size: LARGE_VM_STACK_SIZE).bytesize
+ assert_operator(size_default, :<, size_large)
+ end
+
+ # Depending on OS, machine stack size may not change size.
+ def test_machine_stack_size
+ return if /mswin|mingw/ =~ RUBY_PLATFORM
+
+ script = '$stdout.sync=true; def rec; print "."; 1.times{1.times{1.times{rec}}}; end; Fiber.new{rec}.resume'
+
+ vm_stack_size = 1024 * 1024
+ size_default = invoke_ruby(script, vm_stack_size: vm_stack_size).bytesize
+
+ size_0 = invoke_ruby(script, vm_stack_size: vm_stack_size, machine_stack_size: 0).bytesize
+ assert_operator(size_default, :>=, size_0)
+
+ size_large = invoke_ruby(script, vm_stack_size: vm_stack_size, machine_stack_size: LARGE_MACHINE_STACK_SIZE).bytesize
+ assert_operator(size_default, :<=, size_large)
+ end
+end
diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb
index 9574ed31c9..2458d38ef4 100644
--- a/test/ruby/test_string.rb
+++ b/test/ruby/test_string.rb
@@ -2,18 +2,18 @@
require 'test/unit'
class TestString < Test::Unit::TestCase
- ENUMERATOR_WANTARRAY = RUBY_VERSION >= "3.0.0"
+ WIDE_ENCODINGS = [
+ Encoding::UTF_16BE, Encoding::UTF_16LE,
+ Encoding::UTF_32BE, Encoding::UTF_32LE,
+ ]
def initialize(*args)
@cls = String
- @aref_re_nth = true
- @aref_re_silent = false
- @aref_slicebang_silent = true
super
end
- def S(*args)
- @cls.new(*args)
+ def S(*args, **kw)
+ @cls.new(*args, **kw)
end
def test_s_new
@@ -77,10 +77,17 @@ class TestString < Test::Unit::TestCase
assert_equal("mystring", str.__send__(:initialize, "mystring", capacity: 1000))
str = S("mystring")
assert_equal("mystring", str.__send__(:initialize, str, capacity: 1000))
+
+ if @cls == String
+ 100.times {
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".
+ __send__(:initialize, capacity: -1)
+ }
+ end
end
def test_initialize_shared
- String.new(str = "mystring" * 10).__send__(:initialize, capacity: str.bytesize)
+ S(str = "mystring" * 10).__send__(:initialize, capacity: str.bytesize)
assert_equal("mystring", str[0, 8])
end
@@ -94,8 +101,22 @@ class TestString < Test::Unit::TestCase
end
def test_initialize_memory_leak
+ return unless @cls == String
+
assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true)
-code = proc {('x'*100000).__send__(:initialize, '')}
+code = proc {('x'*100_000).__send__(:initialize, '')}
+1_000.times(&code)
+PREP
+100_000.times(&code)
+CODE
+ end
+
+ # Bug #18154
+ def test_initialize_nofree_memory_leak
+ return unless @cls == String
+
+ assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true)
+code = proc {0.to_s.__send__(:initialize, capacity: 100_000)}
1_000.times(&code)
PREP
100_000.times(&code)
@@ -129,14 +150,12 @@ CODE
assert_equal(nil, S("FooBar")[S("xyzzy")])
assert_equal(nil, S("FooBar")[S("plugh")])
- if @aref_re_nth
- assert_equal(S("Foo"), S("FooBar")[/([A-Z]..)([A-Z]..)/, 1])
- assert_equal(S("Bar"), S("FooBar")[/([A-Z]..)([A-Z]..)/, 2])
- assert_equal(nil, S("FooBar")[/([A-Z]..)([A-Z]..)/, 3])
- assert_equal(S("Bar"), S("FooBar")[/([A-Z]..)([A-Z]..)/, -1])
- assert_equal(S("Foo"), S("FooBar")[/([A-Z]..)([A-Z]..)/, -2])
- assert_equal(nil, S("FooBar")[/([A-Z]..)([A-Z]..)/, -3])
- end
+ assert_equal(S("Foo"), S("FooBar")[/([A-Z]..)([A-Z]..)/, 1])
+ assert_equal(S("Bar"), S("FooBar")[/([A-Z]..)([A-Z]..)/, 2])
+ assert_equal(nil, S("FooBar")[/([A-Z]..)([A-Z]..)/, 3])
+ assert_equal(S("Bar"), S("FooBar")[/([A-Z]..)([A-Z]..)/, -1])
+ assert_equal(S("Foo"), S("FooBar")[/([A-Z]..)([A-Z]..)/, -2])
+ assert_equal(nil, S("FooBar")[/([A-Z]..)([A-Z]..)/, -3])
o = Object.new
def o.to_int; 2; end
@@ -145,6 +164,15 @@ CODE
assert_raise(ArgumentError) { "foo"[] }
end
+ def test_AREF_underflow
+ require "rbconfig/sizeof"
+ assert_equal(nil, S("\u{3042 3044 3046}")[RbConfig::LIMITS["LONG_MIN"], 1])
+ end
+
+ def test_AREF_invalid_encoding
+ assert_equal(S("\x80"), S("A"*39+"\x80")[-1, 1])
+ end
+
def test_ASET # '[]='
s = S("FooBar")
s[0] = S('A')
@@ -182,24 +210,18 @@ CODE
assert_equal(S("BarBar"), s)
s[/..r$/] = S("Foo")
assert_equal(S("BarFoo"), s)
- if @aref_re_silent
- s[/xyzzy/] = S("None")
- assert_equal(S("BarFoo"), s)
- else
- assert_raise(IndexError) { s[/xyzzy/] = S("None") }
- end
- if @aref_re_nth
- s[/([A-Z]..)([A-Z]..)/, 1] = S("Foo")
- assert_equal(S("FooFoo"), s)
- s[/([A-Z]..)([A-Z]..)/, 2] = S("Bar")
- assert_equal(S("FooBar"), s)
- assert_raise(IndexError) { s[/([A-Z]..)([A-Z]..)/, 3] = "None" }
- s[/([A-Z]..)([A-Z]..)/, -1] = S("Foo")
- assert_equal(S("FooFoo"), s)
- s[/([A-Z]..)([A-Z]..)/, -2] = S("Bar")
- assert_equal(S("BarFoo"), s)
- assert_raise(IndexError) { s[/([A-Z]..)([A-Z]..)/, -3] = "None" }
- end
+ assert_raise(IndexError) { s[/xyzzy/] = S("None") }
+
+ s[/([A-Z]..)([A-Z]..)/, 1] = S("Foo")
+ assert_equal(S("FooFoo"), s)
+ s[/([A-Z]..)([A-Z]..)/, 2] = S("Bar")
+ assert_equal(S("FooBar"), s)
+ assert_raise(IndexError) { s[/([A-Z]..)([A-Z]..)/, 3] = "None" }
+ s[/([A-Z]..)([A-Z]..)/, -1] = S("Foo")
+ assert_equal(S("FooFoo"), s)
+ s[/([A-Z]..)([A-Z]..)/, -2] = S("Bar")
+ assert_equal(S("BarFoo"), s)
+ assert_raise(IndexError) { s[/([A-Z]..)([A-Z]..)/, -3] = "None" }
s = S("FooBar")
s[S("Foo")] = S("Bar")
@@ -227,23 +249,23 @@ CODE
assert_equal(-1, S("ABCDEF") <=> S("abcdef"))
- assert_nil("foo" <=> Object.new)
+ assert_nil(S("foo") <=> Object.new)
o = Object.new
def o.to_str; "bar"; end
- assert_equal(1, "foo" <=> o)
+ assert_equal(1, S("foo") <=> o)
class << o;remove_method :to_str;end
def o.<=>(x); nil; end
- assert_nil("foo" <=> o)
+ assert_nil(S("foo") <=> o)
class << o;remove_method :<=>;end
def o.<=>(x); 1; end
- assert_equal(-1, "foo" <=> o)
+ assert_equal(-1, S("foo") <=> o)
class << o;remove_method :<=>;end
def o.<=>(x); 2**100; end
- assert_equal(-1, "foo" <=> o)
+ assert_equal(-1, S("foo") <=> o)
end
def test_EQUAL # '=='
@@ -252,14 +274,15 @@ CODE
assert_not_equal(S("CAT"), S('cat'))
assert_not_equal(S("CaT"), S('cAt'))
+ assert_not_equal(S("cat\0""dog"), S("cat\0"))
o = Object.new
def o.to_str; end
def o.==(x); false; end
- assert_equal(false, "foo" == o)
+ assert_equal(false, S("foo") == o)
class << o;remove_method :==;end
def o.==(x); true; end
- assert_equal(true, "foo" == o)
+ assert_equal(true, S("foo") == o)
end
def test_LSHIFT # '<<'
@@ -283,6 +306,9 @@ CODE
assert_raise(RangeError, bug) {S("a".force_encoding(Encoding::UTF_8)) << -1}
assert_raise(RangeError, bug) {S("a".force_encoding(Encoding::UTF_8)) << 0x81308130}
assert_nothing_raised {S("a".force_encoding(Encoding::GB18030)) << 0x81308130}
+
+ s = "\x95".force_encoding(Encoding::SJIS).tap(&:valid_encoding?)
+ assert_predicate(s << 0x5c, :valid_encoding?)
end
def test_MATCH # '=~'
@@ -382,6 +408,8 @@ CODE
end
def test_chomp
+ verbose, $VERBOSE = $VERBOSE, nil
+
assert_equal(S("hello"), S("hello").chomp("\n"))
assert_equal(S("hello"), S("hello\n").chomp("\n"))
save = $/
@@ -447,9 +475,12 @@ CODE
assert_equal("foo", s.chomp("\n"))
ensure
$/ = save
+ $VERBOSE = verbose
end
def test_chomp!
+ verbose, $VERBOSE = $VERBOSE, nil
+
a = S("hello")
a.chomp!(S("\n"))
@@ -506,6 +537,7 @@ CODE
s = S("").freeze
assert_raise_with_message(FrozenError, /frozen/) {s.chomp!}
+ $VERBOSE = nil # EnvUtil.suppress_warning resets $VERBOSE to the original state
s = S("ax")
o = Struct.new(:s).new(s)
@@ -514,6 +546,7 @@ CODE
"x"
end
assert_raise_with_message(FrozenError, /frozen/) {s.chomp!(o)}
+ $VERBOSE = nil # EnvUtil.suppress_warning resets $VERBOSE to the original state
s = S("hello")
assert_equal("hel", s.chomp!('lo'))
@@ -562,8 +595,11 @@ CODE
assert_equal("foo", s.chomp!("\n"))
s = "foo\r"
assert_equal("foo", s.chomp!("\n"))
+
+ assert_raise(ArgumentError) {String.new.chomp!("", "")}
ensure
$/ = save
+ $VERBOSE = verbose
end
def test_chop
@@ -602,18 +638,14 @@ CODE
end
def test_clone
- for taint in [ false, true ]
- for frozen in [ false, true ]
- a = S("Cool")
- a.taint if taint
- a.freeze if frozen
- b = a.clone
-
- assert_equal(a, b)
- assert_not_same(a, b)
- assert_equal(a.frozen?, b.frozen?)
- assert_equal(a.tainted?, b.tainted?)
- end
+ for frozen in [ false, true ]
+ a = S("Cool")
+ a.freeze if frozen
+ b = a.clone
+
+ assert_equal(a, b)
+ assert_not_same(a, b)
+ assert_equal(a.frozen?, b.frozen?)
end
assert_equal("", File.read(IO::NULL).clone, '[ruby-dev:32819] reported by Kazuhiro NISHIYAMA')
@@ -630,15 +662,36 @@ CODE
result << 0x0300
expected = S("\u0300".encode(Encoding::UTF_16LE))
assert_equal(expected, result, bug7090)
- assert_raise(TypeError) { 'foo' << :foo }
- assert_raise(FrozenError) { 'foo'.freeze.concat('bar') }
+ assert_raise(TypeError) { S('foo') << :foo }
+ assert_raise(FrozenError) { S('foo').freeze.concat('bar') }
end
def test_concat_literals
- s="." * 50
+ s=S("." * 50)
assert_equal(Encoding::UTF_8, "#{s}x".encoding)
end
+ def test_string_interpolations_across_heaps_get_embedded
+ omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1
+
+ require 'objspace'
+ base_slot_size = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]
+ small_obj_size = (base_slot_size / 2)
+ large_obj_size = base_slot_size * 2
+
+ a = "a" * small_obj_size
+ b = "a" * large_obj_size
+
+ res = "#{a}, #{b}"
+ dump_res = ObjectSpace.dump(res)
+ dump_orig = ObjectSpace.dump(a)
+ new_slot_size = Integer(dump_res.match(/"slot_size":(\d+)/)[1])
+ orig_slot_size = Integer(dump_orig.match(/"slot_size":(\d+)/)[1])
+
+ assert_match(/"embedded":true/, dump_res)
+ assert_operator(new_slot_size, :>, orig_slot_size)
+ end
+
def test_count
a = S("hello world")
assert_equal(5, a.count(S("lo")))
@@ -646,35 +699,50 @@ CODE
assert_equal(4, a.count(S("hello"), S("^l")))
assert_equal(4, a.count(S("ej-m")))
assert_equal(0, S("y").count(S("a\\-z")))
- assert_equal(5, "abc\u{3042 3044 3046}".count("^a"))
- assert_equal(1, "abc\u{3042 3044 3046}".count("\u3042"))
- assert_equal(5, "abc\u{3042 3044 3046}".count("^\u3042"))
- assert_equal(2, "abc\u{3042 3044 3046}".count("a-z", "^a"))
- assert_equal(0, "abc\u{3042 3044 3046}".count("a", "\u3042"))
- assert_equal(0, "abc\u{3042 3044 3046}".count("\u3042", "a"))
- assert_equal(0, "abc\u{3042 3044 3046}".count("\u3042", "\u3044"))
- assert_equal(4, "abc\u{3042 3044 3046}".count("^a", "^\u3044"))
- assert_equal(4, "abc\u{3042 3044 3046}".count("^\u3044", "^a"))
- assert_equal(4, "abc\u{3042 3044 3046}".count("^\u3042", "^\u3044"))
+ assert_equal(5, S("abc\u{3042 3044 3046}").count("^a"))
+ assert_equal(1, S("abc\u{3042 3044 3046}").count("\u3042"))
+ assert_equal(5, S("abc\u{3042 3044 3046}").count("^\u3042"))
+ assert_equal(2, S("abc\u{3042 3044 3046}").count("a-z", "^a"))
+ assert_equal(0, S("abc\u{3042 3044 3046}").count("a", "\u3042"))
+ assert_equal(0, S("abc\u{3042 3044 3046}").count("\u3042", "a"))
+ assert_equal(0, S("abc\u{3042 3044 3046}").count("\u3042", "\u3044"))
+ assert_equal(4, S("abc\u{3042 3044 3046}").count("^a", "^\u3044"))
+ assert_equal(4, S("abc\u{3042 3044 3046}").count("^\u3044", "^a"))
+ assert_equal(4, S("abc\u{3042 3044 3046}").count("^\u3042", "^\u3044"))
- assert_raise(ArgumentError) { "foo".count }
+ assert_raise(ArgumentError) { S("foo").count }
+ end
+
+ def crypt_supports_des_crypt?
+ /openbsd/ !~ RUBY_PLATFORM
end
def test_crypt
- assert_equal(S('aaGUC/JkO9/Sc'), S("mypassword").crypt(S("aa")))
- assert_not_equal(S('aaGUC/JkO9/Sc'), S("mypassword").crypt(S("ab")))
+ if crypt_supports_des_crypt?
+ pass = "aaGUC/JkO9/Sc"
+ good_salt = "aa"
+ bad_salt = "ab"
+ else
+ pass = "$2a$04$0WVaz0pV3jzfZ5G5tpmHWuBQGbkjzgtSc3gJbmdy0GAGMa45MFM2."
+ good_salt = "$2a$04$0WVaz0pV3jzfZ5G5tpmHWu"
+ bad_salt = "$2a$04$0WVaz0pV3jzfZ5G5tpmHXu"
+ end
+ assert_equal(S(pass), S("mypassword").crypt(S(good_salt)))
+ assert_not_equal(S(pass), S("mypassword").crypt(S(bad_salt)))
assert_raise(ArgumentError) {S("mypassword").crypt(S(""))}
assert_raise(ArgumentError) {S("mypassword").crypt(S("\0a"))}
assert_raise(ArgumentError) {S("mypassword").crypt(S("a\0"))}
assert_raise(ArgumentError) {S("poison\u0000null").crypt(S("aa"))}
- [Encoding::UTF_16BE, Encoding::UTF_16LE,
- Encoding::UTF_32BE, Encoding::UTF_32LE].each do |enc|
+ WIDE_ENCODINGS.each do |enc|
assert_raise(ArgumentError) {S("mypassword").crypt(S("aa".encode(enc)))}
assert_raise(ArgumentError) {S("mypassword".encode(enc)).crypt(S("aa"))}
end
- @cls == String and assert_no_memory_leak([], 's = ""', <<~'end;') # do
- 1000.times { s.crypt(-"..").clear }
+ @cls == String and
+ assert_no_memory_leak([], "s = ''; salt_proc = proc{#{(crypt_supports_des_crypt? ? '..' : good_salt).inspect}}", "#{<<~"begin;"}\n#{<<~'end;'}")
+
+ begin;
+ 1000.times { s.crypt(-salt_proc.call).clear }
end;
end
@@ -684,17 +752,17 @@ CODE
assert_equal(S("hell"), S("hello").delete(S("aeiou"), S("^e")))
assert_equal(S("ho"), S("hello").delete(S("ej-m")))
- assert_equal("a".hash, "a\u0101".delete("\u0101").hash, '[ruby-talk:329267]')
- assert_equal(true, "a\u0101".delete("\u0101").ascii_only?)
- assert_equal(true, "a\u3041".delete("\u3041").ascii_only?)
- assert_equal(false, "a\u3041\u3042".delete("\u3041").ascii_only?)
+ assert_equal(S("a").hash, S("a\u0101").delete("\u0101").hash, '[ruby-talk:329267]')
+ assert_equal(true, S("a\u0101").delete("\u0101").ascii_only?)
+ assert_equal(true, S("a\u3041").delete("\u3041").ascii_only?)
+ assert_equal(false, S("a\u3041\u3042").delete("\u3041").ascii_only?)
- assert_equal("a", "abc\u{3042 3044 3046}".delete("^a"))
- assert_equal("bc\u{3042 3044 3046}", "abc\u{3042 3044 3046}".delete("a"))
- assert_equal("\u3042", "abc\u{3042 3044 3046}".delete("^\u3042"))
+ assert_equal("a", S("abc\u{3042 3044 3046}").delete("^a"))
+ assert_equal("bc\u{3042 3044 3046}", S("abc\u{3042 3044 3046}").delete("a"))
+ assert_equal("\u3042", S("abc\u{3042 3044 3046}").delete("^\u3042"))
bug6160 = '[ruby-dev:45374]'
- assert_equal("", '\\'.delete('\\'), bug6160)
+ assert_equal("", S('\\').delete('\\'), bug6160)
end
def test_delete!
@@ -736,6 +804,7 @@ CODE
assert_equal(S("hello"), S("hello").downcase)
assert_equal(S("hello"), S("HELLO").downcase)
assert_equal(S("abc hello 123"), S("abc HELLO 123").downcase)
+ assert_equal(S("h\0""ello"), S("h\0""ELLO").downcase)
end
def test_downcase!
@@ -748,6 +817,12 @@ CODE
a=S("hello")
assert_nil(a.downcase!)
assert_equal(S("hello"), a)
+
+ a = S("h\0""ELLO")
+ b = a.dup
+ assert_equal(S("h\0""ello"), a.downcase!)
+ assert_equal(S("h\0""ello"), a)
+ assert_equal(S("h\0""ELLO"), b)
end
def test_dump
@@ -792,10 +867,14 @@ CODE
assert_equal(Encoding::UTF_8, S('"\\u3042"').encode(Encoding::EUC_JP).undump.encoding)
assert_equal("abc".encode(Encoding::UTF_16LE),
- '"a\x00b\x00c\x00".force_encoding("UTF-16LE")'.undump)
+ S('"a\x00b\x00c\x00".force_encoding("UTF-16LE")').undump)
+
+ assert_equal('\#', S('"\\\\#"').undump)
+ assert_equal('\#{', S('"\\\\\#{"').undump)
- assert_equal('\#', '"\\\\#"'.undump)
- assert_equal('\#{', '"\\\\\#{"'.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 }
@@ -826,25 +905,50 @@ CODE
assert_raise(RuntimeError) { S('"\xA"').undump }
assert_raise(RuntimeError) { S('"\\"').undump }
assert_raise(RuntimeError) { S(%("\0")).undump }
+ assert_raise_with_message(RuntimeError, /invalid/) {
+ S('"\\u{007F}".xxxxxx').undump
+ }
+ end
+
+ def test_undump_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ a = S("Test") << 1 << 2 << 3 << 9 << 13 << 10
+ EnvUtil.under_gc_compact_stress do
+ assert_equal(a, S('"Test\\x01\\x02\\x03\\t\\r\\n"').undump)
+ end
+
+ EnvUtil.under_gc_compact_stress do
+ assert_equal(S("\u{ABCDE 10ABCD}"), S('"\\u{ABCDE 10ABCD}"').undump)
+ end
end
def test_dup
- for taint in [ false, true ]
- for frozen in [ false, true ]
- a = S("hello")
- a.taint if taint
- a.freeze if frozen
- b = a.dup
-
- assert_equal(a, b)
- assert_not_same(a, b)
- assert_not_predicate(b, :frozen?)
- assert_equal(a.tainted?, b.tainted?)
- end
+ for frozen in [ false, true ]
+ a = S("hello")
+ a.freeze if frozen
+ b = a.dup
+
+ assert_equal(a, b)
+ assert_not_same(a, b)
+ assert_not_predicate(b, :frozen?)
+ end
+ end
+
+ class StringWithIVSet < String
+ def set_iv
+ @foo = 1
end
end
+ def test_ivar_set_after_frozen_dup
+ str = StringWithIVSet.new.freeze
+ str.dup.set_iv
+ assert_raise(FrozenError) { str.set_iv }
+ end
+
def test_each
+ verbose, $VERBOSE = $VERBOSE, nil
+
save = $/
$/ = "\n"
res=[]
@@ -864,6 +968,7 @@ CODE
assert_equal(S("world"), res[1])
ensure
$/ = save
+ $VERBOSE = verbose
end
def test_each_byte
@@ -882,26 +987,15 @@ CODE
s = S("ABC")
assert_equal [65, 66, 67], s.bytes
- if ENUMERATOR_WANTARRAY
- assert_warn(/block not used/) {
- assert_equal [65, 66, 67], s.bytes {}
- }
- else
- warning = /passing a block to String#bytes is deprecated/
- assert_warning(warning) {
- res = []
- assert_equal s.object_id, s.bytes {|x| res << x }.object_id
- assert_equal(65, res[0])
- assert_equal(66, res[1])
- assert_equal(67, res[2])
- }
- assert_warning(warning) {
- s = S("ABC")
- res = []
- assert_same s, s.bytes {|x| res << x }
- assert_equal [65, 66, 67], res
- }
- end
+ res = []
+ assert_equal s.object_id, s.bytes {|x| res << x }.object_id
+ assert_equal(65, res[0])
+ assert_equal(66, res[1])
+ assert_equal(67, res[2])
+ s = S("ABC")
+ res = []
+ assert_same s, s.bytes {|x| res << x }
+ assert_equal [65, 66, 67], res
end
def test_each_codepoint
@@ -926,26 +1020,15 @@ CODE
s = S("\u3042\u3044\u3046")
assert_equal [0x3042, 0x3044, 0x3046], s.codepoints
- if ENUMERATOR_WANTARRAY
- assert_warn(/block not used/) {
- assert_equal [0x3042, 0x3044, 0x3046], s.codepoints {}
- }
- else
- warning = /passing a block to String#codepoints is deprecated/
- assert_warning(warning) {
- res = []
- assert_equal s.object_id, s.codepoints {|x| res << x }.object_id
- assert_equal(0x3042, res[0])
- assert_equal(0x3044, res[1])
- assert_equal(0x3046, res[2])
- }
- assert_warning(warning) {
- s = S("ABC")
- res = []
- assert_same s, s.codepoints {|x| res << x }
- assert_equal [65, 66, 67], res
- }
- end
+ res = []
+ assert_equal s.object_id, s.codepoints {|x| res << x }.object_id
+ assert_equal(0x3042, res[0])
+ assert_equal(0x3044, res[1])
+ assert_equal(0x3046, res[2])
+ s = S("ABC")
+ res = []
+ assert_same s, s.codepoints {|x| res << x }
+ assert_equal [65, 66, 67], res
end
def test_each_char
@@ -964,20 +1047,11 @@ CODE
s = S("ABC")
assert_equal ["A", "B", "C"], s.chars
- if ENUMERATOR_WANTARRAY
- assert_warn(/block not used/) {
- assert_equal ["A", "B", "C"], s.chars {}
- }
- else
- warning = /passing a block to String#chars is deprecated/
- assert_warning(warning) {
- res = []
- assert_equal s.object_id, s.chars {|x| res << x }.object_id
- assert_equal("A", res[0])
- assert_equal("B", res[1])
- assert_equal("C", res[2])
- }
- end
+ res = []
+ assert_equal s.object_id, s.chars {|x| res << x }.object_id
+ assert_equal("A", res[0])
+ assert_equal("B", res[1])
+ assert_equal("C", res[2])
end
def test_each_grapheme_cluster
@@ -999,8 +1073,8 @@ CODE
end
[
- ["\u{a 308}", ["\u000A", "\u0308"]],
- ["\u{d 308}", ["\u000D", "\u0308"]],
+ ["\u{a 324}", ["\u000A", "\u0324"]],
+ ["\u{d 324}", ["\u000D", "\u0324"]],
["abc", ["a", "b", "c"]],
].each do |str, grapheme_clusters|
assert_equal grapheme_clusters, str.each_grapheme_cluster.to_a
@@ -1023,32 +1097,49 @@ CODE
"\u{1F468 200D 1F393}",
"\u{1F46F 200D 2642 FE0F}",
"\u{1f469 200d 2764 fe0f 200d 1f469}",
- ].each do |g|
+ ].product([Encoding::UTF_8, *WIDE_ENCODINGS]) do |g, enc|
+ g = g.encode(enc)
assert_equal [g], g.grapheme_clusters
end
- assert_equal ["\u000A", "\u0308"], "\u{a 308}".grapheme_clusters
- assert_equal ["\u000D", "\u0308"], "\u{d 308}".grapheme_clusters
- assert_equal ["a", "b", "c"], "abc".b.grapheme_clusters
-
- if ENUMERATOR_WANTARRAY
- assert_warn(/block not used/) {
- assert_equal ["A", "B", "C"], "ABC".grapheme_clusters {}
- }
- else
- warning = /passing a block to String#grapheme_clusters is deprecated/
- assert_warning(warning) {
- s = "ABC".b
- res = []
- assert_same s, s.grapheme_clusters {|x| res << x }
- assert_equal("A", res[0])
- assert_equal("B", res[1])
- assert_equal("C", res[2])
- }
+ [
+ "\u{a 324}",
+ "\u{d 324}",
+ "abc",
+ ].product([Encoding::UTF_8, *WIDE_ENCODINGS]) do |g, enc|
+ g = g.encode(enc)
+ assert_equal g.chars, g.grapheme_clusters
end
+ assert_equal ["a", "b", "c"], S("abc").b.grapheme_clusters
+
+ s = S("ABC").b
+ res = []
+ assert_same s, s.grapheme_clusters {|x| res << x }
+ assert_equal(3, res.size)
+ assert_equal("A", res[0])
+ assert_equal("B", res[1])
+ assert_equal("C", res[2])
+ end
+
+ def test_grapheme_clusters_memory_leak
+ assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #todo]", rss: true)
+ begin;
+ str = "hello world".encode(Encoding::UTF_32LE)
+
+ 10_000.times do
+ str.grapheme_clusters
+ end
+ end;
+ end
+
+ def test_byteslice_grapheme_clusters
+ string = "안녕"
+ assert_equal(["안"], string.byteslice(0,4).grapheme_clusters)
end
def test_each_line
+ verbose, $VERBOSE = $VERBOSE, nil
+
save = $/
$/ = "\n"
res=[]
@@ -1083,7 +1174,7 @@ CODE
$/ = save
s = nil
- "foo\nbar".each_line(nil) {|s2| s = s2 }
+ S("foo\nbar").each_line(nil) {|s2| s = s2 }
assert_equal("foo\nbar", s)
assert_equal "hello\n", S("hello\nworld").each_line.next
@@ -1091,10 +1182,11 @@ CODE
bug7646 = "[ruby-dev:46827]"
assert_nothing_raised(bug7646) do
- "\n\u0100".each_line("\n") {}
+ S("\n\u0100").each_line("\n") {}
end
ensure
$/ = save
+ $VERBOSE = verbose
end
def test_each_line_chomp
@@ -1104,14 +1196,19 @@ CODE
assert_equal(S("world"), res[1])
res = []
- S("hello\n\n\nworld").each_line(S(''), chomp: true) {|x| res << x}
- assert_equal(S("hello\n"), res[0])
- assert_equal(S("world"), res[1])
+ S("hello\n\n\nworld\n").each_line(S(''), chomp: true) {|x| res << x}
+ assert_equal(S("hello"), res[0])
+ assert_equal(S("world\n"), res[1])
res = []
- S("hello\r\n\r\nworld").each_line(S(''), chomp: true) {|x| res << x}
- assert_equal(S("hello\r\n"), res[0])
- assert_equal(S("world"), res[1])
+ S("hello\r\n\r\nworld\r\n").each_line(S(''), chomp: true) {|x| res << x}
+ assert_equal(S("hello"), res[0])
+ assert_equal(S("world\r\n"), res[1])
+
+ res = []
+ S("hello\r\n\n\nworld").each_line(S(''), chomp: true) {|x| res << x}
+ assert_equal(S("hello"), res[0])
+ assert_equal(S("world"), res[1])
res = []
S("hello!world").each_line(S('!'), chomp: true) {|x| res << x}
@@ -1124,7 +1221,7 @@ CODE
assert_equal(S("a"), res[0])
s = nil
- "foo\nbar".each_line(nil, chomp: true) {|s2| s = s2 }
+ S("foo\nbar").each_line(nil, chomp: true) {|s2| s = s2 }
assert_equal("foo\nbar", s)
assert_equal "hello", S("hello\nworld").each_line(chomp: true).next
@@ -1152,18 +1249,10 @@ CODE
assert_equal ["hello\n", "world"], s.lines
assert_equal ["hello\nworld"], s.lines(nil)
- if ENUMERATOR_WANTARRAY
- assert_warn(/block not used/) {
- assert_equal ["hello\n", "world"], s.lines {}
- }
- else
- assert_warning(/passing a block to String#lines is deprecated/) {
- res = []
- assert_equal s.object_id, s.lines {|x| res << x }.object_id
- assert_equal(S("hello\n"), res[0])
- assert_equal(S("world"), res[1])
- }
- end
+ res = []
+ assert_equal s.object_id, s.lines {|x| res << x }.object_id
+ assert_equal(S("hello\n"), res[0])
+ assert_equal(S("world"), res[1])
end
def test_empty?
@@ -1175,6 +1264,8 @@ CODE
assert_send([S("hello"), :end_with?, S("llo")])
assert_not_send([S("hello"), :end_with?, S("ll")])
assert_send([S("hello"), :end_with?, S("el"), S("lo")])
+ assert_send([S("hello"), :end_with?, S("")])
+ assert_not_send([S("hello"), :end_with?])
bug5536 = '[ruby-core:40623]'
assert_raise(TypeError, bug5536) {S("str").end_with? :not_convertible_to_string}
@@ -1195,13 +1286,14 @@ CODE
S("hello").gsub(/(hell)(.)/) { |s| $1.upcase + S('-') + $2 })
assert_equal(S("<>h<>e<>l<>l<>o<>"), S("hello").gsub(S(''), S('<\0>')))
- a = S("hello")
- a.taint
- assert_predicate(a.gsub(/./, S('X')), :tainted?)
+ assert_equal("z", S("abc").gsub(/./, "a" => "z"), "moved from btest/knownbug")
- assert_equal("z", "abc".gsub(/./, "a" => "z"), "moved from btest/knownbug")
+ assert_raise(ArgumentError) { S("foo").gsub }
+ end
- assert_raise(ArgumentError) { "foo".gsub }
+ def test_gsub_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress { assert_equal(S("h<e>ll<o>"), S("hello").gsub(/([aeiou])/, S('<\1>'))) }
end
def test_gsub_encoding
@@ -1243,33 +1335,37 @@ CODE
a.gsub!(/(hell)(.)/) { |s| $1.upcase + S('-') + $2 }
assert_equal(S("HELL-o"), a)
- r = S('X')
- r.taint
- a.gsub!(/./, r)
- assert_predicate(a, :tainted?)
-
a = S("hello")
assert_nil(a.sub!(S('X'), S('Y')))
end
+ def test_gsub_bang_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ a = S("hello")
+ a.gsub!(/([aeiou])/, S('<\1>'))
+ assert_equal(S("h<e>ll<o>"), a)
+ end
+ end
+
def test_sub_hash
- assert_equal('azc', 'abc'.sub(/b/, "b" => "z"))
- assert_equal('ac', 'abc'.sub(/b/, {}))
- assert_equal('a1c', 'abc'.sub(/b/, "b" => 1))
- assert_equal('aBc', 'abc'.sub(/b/, Hash.new {|h, k| k.upcase }))
- assert_equal('a[\&]c', 'abc'.sub(/b/, "b" => '[\&]'))
- assert_equal('aBcabc', 'abcabc'.sub(/b/, Hash.new {|h, k| h[k] = k.upcase }))
- assert_equal('aBcdef', 'abcdef'.sub(/de|b/, "b" => "B", "de" => "DE"))
+ assert_equal('azc', S('abc').sub(/b/, "b" => "z"))
+ assert_equal('ac', S('abc').sub(/b/, {}))
+ assert_equal('a1c', S('abc').sub(/b/, "b" => 1))
+ assert_equal('aBc', S('abc').sub(/b/, Hash.new {|h, k| k.upcase }))
+ assert_equal('a[\&]c', S('abc').sub(/b/, "b" => '[\&]'))
+ assert_equal('aBcabc', S('abcabc').sub(/b/, Hash.new {|h, k| h[k] = k.upcase }))
+ assert_equal('aBcdef', S('abcdef').sub(/de|b/, "b" => "B", "de" => "DE"))
end
def test_gsub_hash
- assert_equal('azc', 'abc'.gsub(/b/, "b" => "z"))
- assert_equal('ac', 'abc'.gsub(/b/, {}))
- assert_equal('a1c', 'abc'.gsub(/b/, "b" => 1))
- assert_equal('aBc', 'abc'.gsub(/b/, Hash.new {|h, k| k.upcase }))
- assert_equal('a[\&]c', 'abc'.gsub(/b/, "b" => '[\&]'))
- assert_equal('aBcaBc', 'abcabc'.gsub(/b/, Hash.new {|h, k| h[k] = k.upcase }))
- assert_equal('aBcDEf', 'abcdef'.gsub(/de|b/, "b" => "B", "de" => "DE"))
+ assert_equal('azc', S('abc').gsub(/b/, "b" => "z"))
+ assert_equal('ac', S('abc').gsub(/b/, {}))
+ assert_equal('a1c', S('abc').gsub(/b/, "b" => 1))
+ assert_equal('aBc', S('abc').gsub(/b/, Hash.new {|h, k| k.upcase }))
+ assert_equal('a[\&]c', S('abc').gsub(/b/, "b" => '[\&]'))
+ assert_equal('aBcaBc', S('abcabc').gsub(/b/, Hash.new {|h, k| h[k] = k.upcase }))
+ assert_equal('aBcDEf', S('abcdef').gsub(/de|b/, "b" => "B", "de" => "DE"))
end
def test_hash
@@ -1279,6 +1375,9 @@ CODE
assert_not_equal(S("a").hash, S("a\0").hash, bug4104)
bug9172 = '[ruby-core:58658] [Bug #9172]'
assert_not_equal(S("sub-setter").hash, S("discover").hash, bug9172)
+ assert_equal(S("").hash, S("".encode(Encoding::UTF_32BE)).hash)
+ h1, h2 = ["\x80", "\x81"].map {|c| c.b.hash ^ c.hash}
+ assert_not_equal(h1, h2)
end
def test_hex
@@ -1299,43 +1398,54 @@ CODE
end
def test_index
- assert_equal(0, S("hello").index(?h))
- assert_equal(1, S("hello").index(S("ell")))
- assert_equal(2, S("hello").index(/ll./))
+ assert_index(0, S("hello"), ?h)
+ assert_index(1, S("hello"), S("ell"))
+ assert_index(2, S("hello"), /ll./)
- assert_equal(3, S("hello").index(?l, 3))
- assert_equal(3, S("hello").index(S("l"), 3))
- assert_equal(3, S("hello").index(/l./, 3))
+ assert_index(3, S("hello"), ?l, 3)
+ assert_index(3, S("hello"), S("l"), 3)
+ assert_index(3, S("hello"), /l./, 3)
- assert_nil(S("hello").index(?z, 3))
- assert_nil(S("hello").index(S("z"), 3))
- assert_nil(S("hello").index(/z./, 3))
+ assert_index(nil, S("hello"), ?z, 3)
+ assert_index(nil, S("hello"), S("z"), 3)
+ assert_index(nil, S("hello"), /z./, 3)
- assert_nil(S("hello").index(?z))
- assert_nil(S("hello").index(S("z")))
- assert_nil(S("hello").index(/z./))
+ assert_index(nil, S("hello"), ?z)
+ assert_index(nil, S("hello"), S("z"))
+ assert_index(nil, S("hello"), /z./)
- assert_equal(0, S("").index(S("")))
- assert_equal(0, S("").index(//))
- assert_nil(S("").index(S("hello")))
- assert_nil(S("").index(/hello/))
- assert_equal(0, S("hello").index(S("")))
- assert_equal(0, S("hello").index(//))
+ assert_index(0, S(""), S(""))
+ assert_index(0, S(""), //)
+ assert_index(nil, S(""), S("hello"))
+ assert_index(nil, S(""), /hello/)
+ assert_index(0, S("hello"), S(""))
+ assert_index(0, S("hello"), //)
s = S("long") * 1000 << "x"
- assert_nil(s.index(S("y")))
- assert_equal(4 * 1000, s.index(S("x")))
+ assert_index(nil, s, S("y"))
+ assert_index(4 * 1000, s, S("x"))
s << "yx"
- assert_equal(4 * 1000, s.index(S("x")))
- assert_equal(4 * 1000, s.index(S("xyx")))
+ assert_index(4 * 1000, s, S("x"))
+ assert_index(4 * 1000, s, S("xyx"))
o = Object.new
def o.to_str; "bar"; end
- assert_equal(3, "foobarbarbaz".index(o))
- assert_raise(TypeError) { "foo".index(Object.new) }
+ assert_index(3, S("foobarbarbaz"), o)
+ assert_raise(TypeError) { S("foo").index(Object.new) }
- assert_nil("foo".index(//, -100))
- assert_nil($~)
+ assert_index(nil, S("foo"), //, -100)
+ assert_index(nil, S("foo"), //, 4)
+
+ assert_index(2, S("abcdbce"), /b\Kc/)
+
+ assert_index(0, S("ã“ã‚“ã«ã¡ã¯"), ?ã“)
+ assert_index(1, S("ã“ã‚“ã«ã¡ã¯"), S("ã‚“ã«ã¡"))
+ assert_index(2, S("ã“ã‚“ã«ã¡ã¯"), /ã«ã¡./)
+
+ assert_index(0, S("ã«ã‚“ã«ã¡ã¯"), ?ã«, 0)
+ assert_index(2, S("ã«ã‚“ã«ã¡ã¯"), ?ã«, 1)
+ assert_index(2, S("ã«ã‚“ã«ã¡ã¯"), ?ã«, 2)
+ assert_index(nil, S("ã«ã‚“ã«ã¡ã¯"), ?ã«, 3)
end
def test_insert
@@ -1439,21 +1549,19 @@ CODE
assert_equal(S("foobar"), a.replace(S("foobar")))
a = S("foo")
- a.taint
b = a.replace(S("xyz"))
assert_equal(S("xyz"), b)
- assert_predicate(b, :tainted?)
- s = "foo" * 100
+ s = S("foo") * 100
s2 = ("bar" * 100).dup
s.replace(s2)
assert_equal(s2, s)
- s2 = ["foo"].pack("p")
+ s2 = [S("foo")].pack("p")
s.replace(s2)
assert_equal(s2, s)
- fs = "".freeze
+ fs = S("").freeze
assert_raise(FrozenError) { fs.replace("a") }
assert_raise(FrozenError) { fs.replace(fs) }
assert_raise(ArgumentError) { fs.replace() }
@@ -1484,32 +1592,57 @@ CODE
end
def test_rindex
- assert_equal(3, S("hello").rindex(?l))
- assert_equal(6, S("ell, hello").rindex(S("ell")))
- assert_equal(7, S("ell, hello").rindex(/ll./))
+ assert_rindex(3, S("hello"), ?l)
+ assert_rindex(6, S("ell, hello"), S("ell"))
+ assert_rindex(7, S("ell, hello"), /ll./)
- assert_equal(3, S("hello,lo").rindex(?l, 3))
- assert_equal(3, S("hello,lo").rindex(S("l"), 3))
- assert_equal(3, S("hello,lo").rindex(/l./, 3))
+ assert_rindex(3, S("hello,lo"), ?l, 3)
+ assert_rindex(3, S("hello,lo"), S("l"), 3)
+ assert_rindex(3, S("hello,lo"), /l./, 3)
- assert_nil(S("hello").rindex(?z, 3))
- assert_nil(S("hello").rindex(S("z"), 3))
- assert_nil(S("hello").rindex(/z./, 3))
+ assert_rindex(nil, S("hello"), ?z, 3)
+ assert_rindex(nil, S("hello"), S("z"), 3)
+ assert_rindex(nil, S("hello"), /z./, 3)
- assert_nil(S("hello").rindex(?z))
- assert_nil(S("hello").rindex(S("z")))
- assert_nil(S("hello").rindex(/z./))
+ assert_rindex(nil, S("hello"), ?z)
+ assert_rindex(nil, S("hello"), S("z"))
+ assert_rindex(nil, S("hello"), /z./)
+
+ assert_rindex(5, S("hello"), S(""))
+ assert_rindex(5, S("hello"), S(""), 5)
+ assert_rindex(4, S("hello"), S(""), 4)
+ assert_rindex(0, S("hello"), S(""), 0)
o = Object.new
def o.to_str; "bar"; end
- assert_equal(6, "foobarbarbaz".rindex(o))
- assert_raise(TypeError) { "foo".rindex(Object.new) }
+ assert_rindex(6, S("foobarbarbaz"), o)
+ assert_raise(TypeError) { S("foo").rindex(Object.new) }
- assert_nil("foo".rindex(//, -100))
- assert_nil($~)
+ assert_rindex(nil, S("foo"), //, -100)
+
+ m = assert_rindex(3, S("foo"), //)
+ assert_equal([3, 3], m.offset(0))
+ assert_rindex(3, S("foo"), //, 4)
+
+ assert_rindex(5, S("abcdbce"), /b\Kc/)
+
+ assert_rindex(2, S("ã“ã‚“ã«ã¡ã¯"), ?ã«)
+ assert_rindex(6, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"))
+ assert_rindex(6, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), /ã«ã¡./)
+
+ assert_rindex(6, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 7)
+ assert_rindex(6, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), -2)
+ assert_rindex(6, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 6)
+ assert_rindex(6, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), -3)
+ assert_rindex(0, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 5)
+ assert_rindex(0, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), -4)
+ assert_rindex(0, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 1)
+ assert_rindex(0, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 0)
- assert_equal(3, "foo".rindex(//))
- assert_equal([3, 3], $~.offset(0))
+ assert_rindex(0, S("ã“ã‚“ã«ã¡ã¯"), S("ã“ã‚“ã«ã¡ã¯"))
+ assert_rindex(nil, S("ã“ã‚“ã«ã¡"), S("ã“ã‚“ã«ã¡ã¯"))
+ assert_rindex(nil, S("ã“"), S("ã“ã‚“ã«ã¡ã¯"))
+ assert_rindex(nil, S(""), S("ã“ã‚“ã«ã¡ã¯"))
end
def test_rjust
@@ -1537,12 +1670,6 @@ CODE
a.scan(/(...)/) { |w| res << w }
assert_equal([[S("cru")], [S("el ")], [S("wor")]],res)
- a = S("hello")
- a.taint
- res = []
- a.scan(/./) { |w| res << w }
- assert_predicate(res[0], :tainted?, '[ruby-core:33338] #4087')
-
/h/ =~ a
a.scan(/x/)
assert_nil($~)
@@ -1551,11 +1678,23 @@ CODE
a.scan('x')
assert_nil($~)
- assert_equal(3, S("hello hello hello").scan("hello".taint).count(&:tainted?))
-
assert_equal(%w[1 2 3], S("a1 a2 a3").scan(/a\K./))
end
+ def test_scan_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress { assert_equal([["1a"], ["2b"], ["3c"]], S("1a2b3c").scan(/(\d.)/)) }
+ end
+
+ def test_scan_segv
+ bug19159 = '[Bug #19159]'
+ assert_nothing_raised(Exception, bug19159) do
+ ObjectSpace.each_object(MatchData).to_a
+ "".scan(//)
+ ObjectSpace.each_object(MatchData).to_a.inspect
+ end
+ end
+
def test_size
assert_equal(0, S("").size)
assert_equal(4, S("1234").size)
@@ -1607,18 +1746,11 @@ CODE
assert_equal(S("FooBa"), a)
a = S("FooBar")
- if @aref_slicebang_silent
- assert_nil( a.slice!(6) )
- else
- assert_raise(IndexError) { a.slice!(6) }
- end
+ assert_nil( a.slice!(6) )
+ assert_nil( a.slice!(6r) )
assert_equal(S("FooBar"), a)
- if @aref_slicebang_silent
- assert_nil( a.slice!(-7) )
- else
- assert_raise(IndexError) { a.slice!(-7) }
- end
+ assert_nil( a.slice!(-7) )
assert_equal(S("FooBar"), a)
a = S("FooBar")
@@ -1630,17 +1762,9 @@ CODE
assert_equal(S("Foo"), a)
a=S("FooBar")
- if @aref_slicebang_silent
assert_nil(a.slice!(7,2)) # Maybe should be six?
- else
- assert_raise(IndexError) {a.slice!(7,2)} # Maybe should be six?
- end
assert_equal(S("FooBar"), a)
- if @aref_slicebang_silent
assert_nil(a.slice!(-7,10))
- else
- assert_raise(IndexError) {a.slice!(-7,10)}
- end
assert_equal(S("FooBar"), a)
a=S("FooBar")
@@ -1652,17 +1776,9 @@ CODE
assert_equal(S("Foo"), a)
a=S("FooBar")
- if @aref_slicebang_silent
assert_equal(S(""), a.slice!(6..2))
- else
- assert_raise(RangeError) {a.slice!(6..2)}
- end
assert_equal(S("FooBar"), a)
- if @aref_slicebang_silent
assert_nil(a.slice!(-10..-7))
- else
- assert_raise(RangeError) {a.slice!(-10..-7)}
- end
assert_equal(S("FooBar"), a)
a=S("FooBar")
@@ -1674,17 +1790,9 @@ CODE
assert_equal(S("Foo"), a)
a=S("FooBar")
- if @aref_slicebang_silent
- assert_nil(a.slice!(/xyzzy/))
- else
- assert_raise(IndexError) {a.slice!(/xyzzy/)}
- end
+ assert_nil(a.slice!(/xyzzy/))
assert_equal(S("FooBar"), a)
- if @aref_slicebang_silent
- assert_nil(a.slice!(/plugh/))
- else
- assert_raise(IndexError) {a.slice!(/plugh/)}
- end
+ assert_nil(a.slice!(/plugh/))
assert_equal(S("FooBar"), a)
a=S("FooBar")
@@ -1695,7 +1803,8 @@ CODE
assert_equal(S("Bar"), a.slice!(S("Bar")))
assert_equal(S("Foo"), a)
- assert_raise(ArgumentError) { "foo".slice! }
+ a = S("foo")
+ assert_raise(ArgumentError) { a.slice! }
end
def test_split
@@ -1720,19 +1829,84 @@ CODE
assert_equal([S("a"), S(""), S("b"), S("c")], S("a||b|c|").split(S('|')))
assert_equal([S("a"), S(""), S("b"), S("c"), S("")], S("a||b|c|").split(S('|'), -1))
- assert_equal([], "".split(//, 1))
+ assert_equal([], S("").split(//, 1))
+ ensure
+ EnvUtil.suppress_warning {$; = fs}
+ end
+
+ def test_split_with_block
+ fs, $; = $;, nil
+ result = []; S(" a b\t c ").split {|s| result << s}
+ assert_equal([S("a"), S("b"), S("c")], result)
+ result = []; S(" a b\t c ").split(S(" ")) {|s| result << s}
+ assert_equal([S("a"), S("b"), S("c")], result)
+
+ result = []; S(" a | b | c ").split(S("|")) {|s| result << s}
+ assert_equal([S(" a "), S(" b "), S(" c ")], result)
+
+ result = []; S("aXXbXXcXX").split(/X./) {|s| result << s}
+ assert_equal([S("a"), S("b"), S("c")], result)
+
+ result = []; S("abc").split(//) {|s| result << s}
+ assert_equal([S("a"), S("b"), S("c")], result)
+
+ result = []; S("a|b|c").split(S('|'), 1) {|s| result << s}
+ assert_equal([S("a|b|c")], result)
- assert_equal("[2, 3]", [1,2,3].slice!(1,10000).inspect, "moved from btest/knownbug")
+ result = []; S("a|b|c").split(S('|'), 2) {|s| result << s}
+ assert_equal([S("a"), S("b|c")], result)
+ result = []; S("a|b|c").split(S('|'), 3) {|s| result << s}
+ assert_equal([S("a"), S("b"), S("c")], result)
+
+ result = []; S("a|b|c|").split(S('|'), -1) {|s| result << s}
+ assert_equal([S("a"), S("b"), S("c"), S("")], result)
+ result = []; S("a|b|c||").split(S('|'), -1) {|s| result << s}
+ assert_equal([S("a"), S("b"), S("c"), S(""), S("")], result)
+
+ result = []; S("a||b|c|").split(S('|')) {|s| result << s}
+ assert_equal([S("a"), S(""), S("b"), S("c")], result)
+ result = []; S("a||b|c|").split(S('|'), -1) {|s| result << s}
+ assert_equal([S("a"), S(""), S("b"), S("c"), S("")], result)
+
+ result = []; S("").split(//, 1) {|s| result << s}
+ assert_equal([], result)
+
+ 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
- $; = fs
+ EnvUtil.suppress_warning {$; = fs}
end
def test_fs
- assert_raise_with_message(TypeError, /\$;/) {
- $; = []
- }
+ return unless @cls == String
+
+ 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([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_separately(%W[-W0], "#{<<~"begin;"}\n#{<<~'end;'}")
bug = '[ruby-core:79582] $; must not be GCed'
begin;
$; = " "
@@ -1755,10 +1929,7 @@ CODE
def test_split_wchar
bug8642 = '[ruby-core:56036] [Bug #8642]'
- [
- Encoding::UTF_16BE, Encoding::UTF_16LE,
- Encoding::UTF_32BE, Encoding::UTF_32LE,
- ].each do |enc|
+ WIDE_ENCODINGS.each do |enc|
s = S("abc,def".encode(enc))
assert_equal(["abc", "def"].map {|c| c.encode(enc)},
s.split(",".encode(enc)),
@@ -1785,6 +1956,12 @@ CODE
s.split("b", 1).map(&:upcase!)
assert_equal("abc", s)
end
+
+ def test_split_lookbehind
+ assert_equal([S("ab"), S("d")], S("abcd").split(/(?<=b)c/))
+ assert_equal([S("ab"), S("d")], S("abcd").split(/b\Kc/))
+ end
+
def test_squeeze
assert_equal(S("abc"), S("aaabbbbccc").squeeze)
assert_equal(S("aa bb cc"), S("aa bb cc").squeeze(S(" ")))
@@ -1814,24 +1991,46 @@ CODE
assert_send([S("hello"), :start_with?, S("hel")])
assert_not_send([S("hello"), :start_with?, S("el")])
assert_send([S("hello"), :start_with?, S("el"), S("he")])
+ assert_send([S("\xFF\xFE"), :start_with?, S("\xFF")])
+ assert_send([S("hello\xBE"), :start_with?, S("hello")])
+ assert_not_send([S("\u{c4}"), :start_with?, S("\xC3")])
bug5536 = '[ruby-core:40623]'
assert_raise(TypeError, bug5536) {S("str").start_with? :not_convertible_to_string}
+ end
- assert_equal(true, "hello".start_with?(/hel/))
+ def test_start_with_regexp
+ assert_equal(true, S("hello").start_with?(/hel/))
assert_equal("hel", $&)
- assert_equal(false, "hello".start_with?(/el/))
+ assert_equal(false, S("hello").start_with?(/el/))
assert_nil($&)
end
+ def test_start_with_timeout_memory_leak
+ assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", "[Bug #20653]", rss: true)
+ regex = Regexp.new("^#{"(a*)" * 10_000}x$", timeout: 0.000001)
+ str = "a" * 1_000_000 + "x"
+
+ code = proc do
+ str.start_with?(regex)
+ rescue
+ end
+
+ 10.times(&code)
+ begin;
+ 1_000.times(&code)
+ end;
+ end
+
def test_strip
assert_equal(S("x"), S(" x ").strip)
assert_equal(S("x"), S(" \n\r\t x \t\r\n\n ").strip)
+ assert_equal(S("x"), S("\x00x\x00").strip)
assert_equal("0b0 ".force_encoding("UTF-16BE"),
- "\x00 0b0 ".force_encoding("UTF-16BE").strip)
+ S("\x00 0b0 ").force_encoding("UTF-16BE").strip)
assert_equal("0\x000b0 ".force_encoding("UTF-16BE"),
- "0\x000b0 ".force_encoding("UTF-16BE").strip)
+ S("0\x000b0 ").force_encoding("UTF-16BE").strip)
end
def test_strip!
@@ -1845,11 +2044,126 @@ CODE
assert_equal(S("x"), a.strip!)
assert_equal(S("x"), a)
+ a = S("\x00x\x00")
+ assert_equal(S("x"), a.strip!)
+ assert_equal(S("x"), a)
+
a = S("x")
assert_nil(a.strip!)
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>')))
@@ -1890,24 +2204,19 @@ CODE
assert_equal(S("a\\&aba"), S("ababa").sub(/b/, '\\\\&'))
assert_equal(S("a\\baba"), S("ababa").sub(/b/, '\\\\\&'))
- a = S("hello")
- a.taint
- x = a.sub(/./, S('X'))
- assert_predicate(x, :tainted?)
-
o = Object.new
def o.to_str; "bar"; end
- assert_equal("fooBARbaz", "foobarbaz".sub(o, "BAR"))
+ assert_equal("fooBARbaz", S("foobarbaz").sub(o, "BAR"))
- assert_raise(TypeError) { "foo".sub(Object.new, "") }
+ assert_raise(TypeError) { S("foo").sub(Object.new, "") }
- assert_raise(ArgumentError) { "foo".sub }
+ assert_raise(ArgumentError) { S("foo").sub }
assert_raise(IndexError) { "foo"[/(?:(o$)|(x))/, 2] = 'bar' }
o = Object.new
def o.to_s; self; end
- assert_match(/^foo#<Object:0x.*>baz$/, "foobarbaz".sub("bar") { o })
+ assert_match(/^foo#<Object:0x.*>baz$/, S("foobarbaz").sub("bar") { o })
assert_equal(S("Abc"), S("abc").sub("a", "A"))
m = nil
@@ -1916,10 +2225,20 @@ CODE
assert_equal(/a/, m.regexp)
bug = '[ruby-core:78686] [Bug #13042] other than regexp has no name references'
assert_raise_with_message(IndexError, /oops/, bug) {
- 'hello'.gsub('hello', '\k<oops>')
+ S('hello').gsub('hello', '\k<oops>')
}
end
+ def test_sub_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ m = /&(?<foo>.*?);/.match(S("aaa &amp; yyy"))
+ assert_equal("amp", m["foo"])
+
+ assert_equal("aaa [amp] yyy", S("aaa &amp; yyy").sub(/&(?<foo>.*?);/, S('[\k<foo>]')))
+ end
+ end
+
def test_sub!
a = S("hello")
b = a.dup
@@ -1942,11 +2261,6 @@ CODE
a=S("hello")
assert_nil(a.sub!(/X/, S('Y')))
- r = S('X')
- r.taint
- a.sub!(/./, r)
- assert_predicate(a, :tainted?)
-
bug16105 = '[Bug #16105] heap-use-after-free'
a = S("ABCDEFGHIJKLMNOPQRSTUVWXYZ012345678")
b = a.dup
@@ -1968,18 +2282,18 @@ CODE
assert_equal(S("AAAAA000"), S("ZZZZ999").succ)
assert_equal(S("*+"), S("**").succ)
- assert_equal("abce", "abcd".succ)
- assert_equal("THX1139", "THX1138".succ)
- assert_equal("<\<koalb>>", "<\<koala>>".succ)
- assert_equal("2000aaa", "1999zzz".succ)
- assert_equal("AAAA0000", "ZZZ9999".succ)
- assert_equal("**+", "***".succ)
+ assert_equal("abce", S("abcd").succ)
+ assert_equal("THX1139", S("THX1138").succ)
+ assert_equal("<\<koalb>>", S("<\<koala>>").succ)
+ assert_equal("2000aaa", S("1999zzz").succ)
+ assert_equal("AAAA0000", S("ZZZ9999").succ)
+ assert_equal("**+", S("***").succ)
- assert_equal("!", " ".succ)
- assert_equal("", "".succ)
+ assert_equal("!", S(" ").succ)
+ assert_equal("", S("").succ)
bug = '[ruby-core:83062] [Bug #13952]'
- s = "\xff".b
+ s = S("\xff").b
assert_not_predicate(s, :ascii_only?)
assert_predicate(s.succ, :ascii_only?, bug)
end
@@ -2031,8 +2345,8 @@ CODE
assert_equal(S(""), a.succ!)
assert_equal(S(""), a)
- assert_equal("aaaaaaaaaaaa", "zzzzzzzzzzz".succ!)
- assert_equal("aaaaaaaaaaaaaaaaaaaaaaaa", "zzzzzzzzzzzzzzzzzzzzzzz".succ!)
+ assert_equal("aaaaaaaaaaaa", S("zzzzzzzzzzz").succ!)
+ assert_equal("aaaaaaaaaaaaaaaaaaaaaaaa", S("zzzzzzzzzzzzzzzzzzzzzzz").succ!)
end
def test_sum
@@ -2054,8 +2368,8 @@ CODE
end
def test_sum_2
- assert_equal(0, "".sum)
- assert_equal(294, "abc".sum)
+ assert_equal(0, S("").sum)
+ assert_equal(294, S("abc").sum)
check_sum("abc")
check_sum("\x80")
-3.upto(70) {|bits|
@@ -2074,6 +2388,8 @@ CODE
def test_swapcase
assert_equal(S("hi&LOW"), S("HI&low").swapcase)
+ s = S("")
+ assert_not_same(s, s.swapcase)
end
def test_swapcase!
@@ -2100,39 +2416,39 @@ CODE
def test_to_i
assert_equal(1480, S("1480ft/sec").to_i)
assert_equal(0, S("speed of sound in water @20C = 1480ft/sec)").to_i)
- assert_equal(0, " 0".to_i)
- assert_equal(0, "+0".to_i)
- assert_equal(0, "-0".to_i)
- assert_equal(0, "--0".to_i)
- assert_equal(16, "0x10".to_i(0))
- assert_equal(16, "0X10".to_i(0))
- assert_equal(2, "0b10".to_i(0))
- assert_equal(2, "0B10".to_i(0))
- assert_equal(8, "0o10".to_i(0))
- assert_equal(8, "0O10".to_i(0))
- assert_equal(10, "0d10".to_i(0))
- assert_equal(10, "0D10".to_i(0))
- assert_equal(8, "010".to_i(0))
- assert_raise(ArgumentError) { "010".to_i(-10) }
+ assert_equal(0, S(" 0").to_i)
+ assert_equal(0, S("+0").to_i)
+ assert_equal(0, S("-0").to_i)
+ assert_equal(0, S("--0").to_i)
+ assert_equal(16, S("0x10").to_i(0))
+ assert_equal(16, S("0X10").to_i(0))
+ assert_equal(2, S("0b10").to_i(0))
+ assert_equal(2, S("0B10").to_i(0))
+ assert_equal(8, S("0o10").to_i(0))
+ assert_equal(8, S("0O10").to_i(0))
+ assert_equal(10, S("0d10").to_i(0))
+ assert_equal(10, S("0D10").to_i(0))
+ assert_equal(8, S("010").to_i(0))
+ assert_raise(ArgumentError) { S("010").to_i(-10) }
2.upto(36) {|radix|
- assert_equal(radix, "10".to_i(radix))
- assert_equal(radix**2, "100".to_i(radix))
+ assert_equal(radix, S("10").to_i(radix))
+ assert_equal(radix**2, S("100").to_i(radix))
}
- assert_raise(ArgumentError) { "0".to_i(1) }
- assert_raise(ArgumentError) { "0".to_i(37) }
- assert_equal(0, "z".to_i(10))
- assert_equal(12, "1_2".to_i(10))
- assert_equal(0x40000000, "1073741824".to_i(10))
- assert_equal(0x4000000000000000, "4611686018427387904".to_i(10))
- assert_equal(1, "1__2".to_i(10))
- assert_equal(1, "1_z".to_i(10))
+ assert_raise(ArgumentError) { S("0").to_i(1) }
+ assert_raise(ArgumentError) { S("0").to_i(37) }
+ assert_equal(0, S("z").to_i(10))
+ assert_equal(12, S("1_2").to_i(10))
+ assert_equal(0x40000000, S("1073741824").to_i(10))
+ assert_equal(0x4000000000000000, S("4611686018427387904").to_i(10))
+ assert_equal(1, S("1__2").to_i(10))
+ assert_equal(1, S("1_z").to_i(10))
bug6192 = '[ruby-core:43566]'
- assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-16be").to_i}
- assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-16le").to_i}
- assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-32be").to_i}
- assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-32le").to_i}
- assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("iso-2022-jp").to_i}
+ assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-16be")).to_i}
+ assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-16le")).to_i}
+ assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-32be")).to_i}
+ assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-32le")).to_i}
+ assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("iso-2022-jp")).to_i}
end
def test_to_s
@@ -2164,13 +2480,13 @@ CODE
assert_equal(S("*e**o"), S("hello").tr(S("^aeiou"), S("*")))
assert_equal(S("hal"), S("ibm").tr(S("b-z"), S("a-z")))
- a = "abc".force_encoding(Encoding::US_ASCII)
+ a = S("abc".force_encoding(Encoding::US_ASCII))
assert_equal(Encoding::US_ASCII, a.tr(S("z"), S("\u0101")).encoding, '[ruby-core:22326]')
- assert_equal("a".hash, "a".tr("a", "\u0101").tr("\u0101", "a").hash, '[ruby-core:22328]')
- assert_equal(true, "\u0101".tr("\u0101", "a").ascii_only?)
- assert_equal(true, "\u3041".tr("\u3041", "a").ascii_only?)
- assert_equal(false, "\u3041\u3042".tr("\u3041", "a").ascii_only?)
+ assert_equal("a".hash, S("a").tr("a", "\u0101").tr("\u0101", "a").hash, '[ruby-core:22328]')
+ assert_equal(true, S("\u0101").tr("\u0101", "a").ascii_only?)
+ assert_equal(true, S("\u3041").tr("\u3041", "a").ascii_only?)
+ assert_equal(false, S("\u3041\u3042").tr("\u3041", "a").ascii_only?)
bug6156 = '[ruby-core:43335]'
bug13950 = '[ruby-core:83056] [Bug #13950]'
@@ -2180,6 +2496,8 @@ CODE
assert_not_predicate(str, :ascii_only?)
assert_not_predicate(star, :ascii_only?)
assert_not_predicate(result, :ascii_only?, bug13950)
+
+ assert_equal(S("XYC"), S("ABC").tr("A-AB", "XY"))
end
def test_tr!
@@ -2201,16 +2519,20 @@ CODE
assert_nil(a.tr!(S("B-Z"), S("A-Z")))
assert_equal(S("ibm"), a)
- a = "abc".force_encoding(Encoding::US_ASCII)
+ a = S("abc".force_encoding(Encoding::US_ASCII))
assert_nil(a.tr!(S("z"), S("\u0101")), '[ruby-core:22326]')
assert_equal(Encoding::US_ASCII, a.encoding, '[ruby-core:22326]')
+
+ assert_equal(S("XYC"), S("ABC").tr!("A-AB", "XY"))
end
def test_tr_s
assert_equal(S("hypo"), S("hello").tr_s(S("el"), S("yp")))
assert_equal(S("h*o"), S("hello").tr_s(S("el"), S("*")))
- assert_equal("a".hash, "\u0101\u0101".tr_s("\u0101", "a").hash)
- assert_equal(true, "\u3041\u3041".tr("\u3041", "a").ascii_only?)
+ assert_equal("a".hash, S("\u0101\u0101").tr_s("\u0101", "a").hash)
+ assert_equal(true, S("\u3041\u3041").tr("\u3041", "a").ascii_only?)
+
+ assert_equal(S("XYC"), S("ABC").tr_s("A-AB", "XY"))
end
def test_tr_s!
@@ -2223,6 +2545,8 @@ CODE
a = S("hello")
assert_equal(S("h*o"), a.tr_s!(S("el"), S("*")))
assert_equal(S("h*o"), a)
+
+ assert_equal(S("XYC"), S("ABC").tr_s!("A-AB", "XY"))
end
def test_unpack
@@ -2275,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
@@ -2309,6 +2607,8 @@ CODE
assert_equal(S("HELLO"), S("hello").upcase)
assert_equal(S("HELLO"), S("HELLO").upcase)
assert_equal(S("ABC HELLO 123"), S("abc HELLO 123").upcase)
+ assert_equal(S("H\0""ELLO"), S("H\0""ello").upcase)
+ assert_equal(S("\u{10574}"), S("\u{1059B}").upcase)
end
def test_upcase!
@@ -2321,6 +2621,12 @@ CODE
a = S("HELLO")
assert_nil(a.upcase!)
assert_equal(S("HELLO"), a)
+
+ a = S("H\0""ello")
+ b = a.dup
+ assert_equal(S("H\0""ELLO"), a.upcase!)
+ assert_equal(S("H\0""ELLO"), a)
+ assert_equal(S("H\0""ello"), b)
end
def test_upto
@@ -2376,6 +2682,8 @@ CODE
class S2 < String
end
def test_str_new4
+ return unless @cls == String
+
s = (0..54).to_a.join # length = 100
s2 = S2.new(s[10,90])
s3 = s2[10,80]
@@ -2384,7 +2692,7 @@ CODE
end
def test_rb_str_new4
- s = "a" * 100
+ s = S("a" * 100)
s2 = s[10,90]
assert_equal("a" * 90, s2)
s3 = s2[10,80]
@@ -2402,11 +2710,11 @@ CODE
end
def test_rb_str_to_str
- assert_equal("ab", "a" + StringLike.new("b"))
+ assert_equal("ab", S("a") + StringLike.new("b"))
end
def test_rb_str_shared_replace
- s = "a" * 100
+ s = S("a" * 100)
s.succ!
assert_equal("a" * 99 + "b", s)
s = ""
@@ -2430,12 +2738,12 @@ CODE
def test_times2
s1 = ''
100.times {|n|
- s2 = "a" * n
+ s2 = S("a") * n
assert_equal(s1, s2)
s1 << 'a'
}
- assert_raise(ArgumentError) { "foo" * (-1) }
+ assert_raise(ArgumentError) { S("foo") * (-1) }
end
def test_respond_to
@@ -2443,41 +2751,41 @@ CODE
def o.respond_to?(arg) [:to_str].include?(arg) ? nil : super end
def o.to_str() "" end
def o.==(other) "" == other end
- assert_equal(false, "" == o)
+ assert_equal(false, S("") == o)
end
def test_match_method
- assert_equal("bar", "foobarbaz".match(/bar/).to_s)
+ assert_equal("bar", S("foobarbaz").match(/bar/).to_s)
o = Regexp.new('foo')
def o.match(x, y, z); x + y + z; end
- assert_equal("foobarbaz", "foo".match(o, "bar", "baz"))
+ assert_equal("foobarbaz", S("foo").match(o, "bar", "baz"))
x = nil
- "foo".match(o, "bar", "baz") {|y| x = y }
+ S("foo").match(o, "bar", "baz") {|y| x = y }
assert_equal("foobarbaz", x)
- assert_raise(ArgumentError) { "foo".match }
+ assert_raise(ArgumentError) { S("foo").match }
end
def test_match_p_regexp
/backref/ =~ 'backref'
# must match here, but not in a separate method, e.g., assert_send,
# to check if $~ is affected or not.
- assert_equal(true, "".match?(//))
+ assert_equal(true, S("").match?(//))
assert_equal(true, :abc.match?(/.../))
- assert_equal(true, 'abc'.match?(/b/))
- assert_equal(true, 'abc'.match?(/b/, 1))
- assert_equal(true, 'abc'.match?(/../, 1))
- assert_equal(true, 'abc'.match?(/../, -2))
- assert_equal(false, 'abc'.match?(/../, -4))
- assert_equal(false, 'abc'.match?(/../, 4))
- assert_equal(true, "\u3042xx".match?(/../, 1))
- assert_equal(false, "\u3042x".match?(/../, 1))
- assert_equal(true, ''.match?(/\z/))
- assert_equal(true, 'abc'.match?(/\z/))
- assert_equal(true, 'Ruby'.match?(/R.../))
- assert_equal(false, 'Ruby'.match?(/R.../, 1))
- assert_equal(false, 'Ruby'.match?(/P.../))
+ assert_equal(true, S('abc').match?(/b/))
+ assert_equal(true, S('abc').match?(/b/, 1))
+ assert_equal(true, S('abc').match?(/../, 1))
+ assert_equal(true, S('abc').match?(/../, -2))
+ assert_equal(false, S('abc').match?(/../, -4))
+ assert_equal(false, S('abc').match?(/../, 4))
+ assert_equal(true, S("\u3042xx").match?(/../, 1))
+ assert_equal(false, S("\u3042x").match?(/../, 1))
+ assert_equal(true, S('').match?(/\z/))
+ assert_equal(true, S('abc').match?(/\z/))
+ assert_equal(true, S('Ruby').match?(/R.../))
+ assert_equal(false, S('Ruby').match?(/R.../, 1))
+ assert_equal(false, S('Ruby').match?(/P.../))
assert_equal('backref', $&)
end
@@ -2485,21 +2793,21 @@ CODE
/backref/ =~ 'backref'
# must match here, but not in a separate method, e.g., assert_send,
# to check if $~ is affected or not.
- assert_equal(true, "".match?(''))
+ assert_equal(true, S("").match?(''))
assert_equal(true, :abc.match?('...'))
- assert_equal(true, 'abc'.match?('b'))
- assert_equal(true, 'abc'.match?('b', 1))
- assert_equal(true, 'abc'.match?('..', 1))
- assert_equal(true, 'abc'.match?('..', -2))
- assert_equal(false, 'abc'.match?('..', -4))
- assert_equal(false, 'abc'.match?('..', 4))
- assert_equal(true, "\u3042xx".match?('..', 1))
- assert_equal(false, "\u3042x".match?('..', 1))
- assert_equal(true, ''.match?('\z'))
- assert_equal(true, 'abc'.match?('\z'))
- assert_equal(true, 'Ruby'.match?('R...'))
- assert_equal(false, 'Ruby'.match?('R...', 1))
- assert_equal(false, 'Ruby'.match?('P...'))
+ assert_equal(true, S('abc').match?('b'))
+ assert_equal(true, S('abc').match?('b', 1))
+ assert_equal(true, S('abc').match?('..', 1))
+ assert_equal(true, S('abc').match?('..', -2))
+ assert_equal(false, S('abc').match?('..', -4))
+ assert_equal(false, S('abc').match?('..', 4))
+ assert_equal(true, S("\u3042xx").match?('..', 1))
+ assert_equal(false, S("\u3042x").match?('..', 1))
+ assert_equal(true, S('').match?('\z'))
+ assert_equal(true, S('abc').match?('\z'))
+ assert_equal(true, S('Ruby').match?('R...'))
+ assert_equal(false, S('Ruby').match?('R...', 1))
+ assert_equal(false, S('Ruby').match?('P...'))
assert_equal('backref', $&)
end
@@ -2519,18 +2827,23 @@ CODE
def test_inspect_nul
bug8290 = '[ruby-core:54458]'
- s = "\0" + "12"
+ s = S("\0") + "12"
assert_equal '"\u000012"', s.inspect, bug8290
- s = "\0".b + "12"
+ s = S("\0".b) + "12"
assert_equal '"\x0012"', s.inspect, bug8290
end
+ def test_inspect_next_line
+ bug16842 = '[ruby-core:98231]'
+ assert_equal '"\\u0085"', 0x85.chr(Encoding::UTF_8).inspect, bug16842
+ end
+
def test_partition
- assert_equal(%w(he l lo), "hello".partition(/l/))
- assert_equal(%w(he l lo), "hello".partition("l"))
- assert_raise(TypeError) { "hello".partition(1) }
+ assert_equal(%w(he l lo), S("hello").partition(/l/))
+ assert_equal(%w(he l lo), S("hello").partition("l"))
+ assert_raise(TypeError) { S("hello").partition(1) }
def (hyphen = Object.new).to_str; "-"; end
- assert_equal(%w(foo - bar), "foo-bar".partition(hyphen), '[ruby-core:23540]')
+ assert_equal(%w(foo - bar), S("foo-bar").partition(hyphen), '[ruby-core:23540]')
bug6206 = '[ruby-dev:45441]'
Encoding.list.each do |enc|
@@ -2540,20 +2853,24 @@ CODE
end
assert_equal(["\u30E6\u30FC\u30B6", "@", "\u30C9\u30E1.\u30A4\u30F3"],
- "\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3".partition(/[@.]/))
+ S("\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3").partition(/[@.]/))
bug = '[ruby-core:82911]'
- hello = "hello"
+ hello = S("hello")
hello.partition("hi").map(&:upcase!)
assert_equal("hello", hello, bug)
+
+ assert_equal(["", "", "foo"], S("foo").partition(/^=*/))
+
+ assert_equal([S("ab"), S("c"), S("dbce")], S("abcdbce").partition(/b\Kc/))
end
def test_rpartition
- assert_equal(%w(hel l o), "hello".rpartition(/l/))
- assert_equal(%w(hel l o), "hello".rpartition("l"))
- assert_raise(TypeError) { "hello".rpartition(1) }
+ assert_equal(%w(hel l o), S("hello").rpartition(/l/))
+ assert_equal(%w(hel l o), S("hello").rpartition("l"))
+ assert_raise(TypeError) { S("hello").rpartition(1) }
def (hyphen = Object.new).to_str; "-"; end
- assert_equal(%w(foo - bar), "foo-bar".rpartition(hyphen), '[ruby-core:23540]')
+ assert_equal(%w(foo - bar), S("foo-bar").rpartition(hyphen), '[ruby-core:23540]')
bug6206 = '[ruby-dev:45441]'
Encoding.list.each do |enc|
@@ -2564,19 +2881,31 @@ CODE
bug8138 = '[ruby-dev:47183]'
assert_equal(["\u30E6\u30FC\u30B6@\u30C9\u30E1", ".", "\u30A4\u30F3"],
- "\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3".rpartition(/[@.]/), bug8138)
+ S("\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3").rpartition(/[@.]/), bug8138)
bug = '[ruby-core:82911]'
hello = "hello"
hello.rpartition("hi").map(&:upcase!)
assert_equal("hello", hello, bug)
+
+ assert_equal([S("abcdb"), S("c"), S("e")], S("abcdbce").rpartition(/b\Kc/))
end
- def test_setter
- assert_raise(TypeError) { $/ = 1 }
+ def test_rs
+ return unless @cls == String
+
+ begin
+ rs = $/
+ assert_deprecated_warning(/non-nil '\$\/'/) { $/ = "" }
+ assert_raise(TypeError) { $/ = 1 }
+ ensure
+ EnvUtil.suppress_warning { $/ = rs }
+ end
name = "\u{5206 884c}"
- assert_separately([], <<-"end;") # do
+ assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}")
+ do;
alias $#{name} $/
+ assert_deprecated_warning(/\\$#{name}/) { $#{name} = "" }
assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 }
end;
end
@@ -2608,70 +2937,101 @@ CODE
end
def test_gsub_enumerator
- assert_normal_exit %q{"abc".gsub(/./).next}, "[ruby-dev:34828]"
+ e = S("abc").gsub(/./)
+ assert_equal("a", e.next, "[ruby-dev:34828]")
+ assert_equal("b", e.next)
+ assert_equal("c", e.next)
end
def test_clear_nonasciicompat
- assert_equal("", "\u3042".encode("ISO-2022-JP").clear)
+ assert_equal("", S("\u3042".encode("ISO-2022-JP")).clear)
end
def test_try_convert
- assert_equal(nil, String.try_convert(1))
- assert_equal("foo", String.try_convert("foo"))
+ assert_equal(nil, @cls.try_convert(1))
+ assert_equal("foo", @cls.try_convert("foo"))
end
def test_substr_negative_begin
assert_equal("\u3042", ("\u3042" * 100)[-1])
end
-=begin
def test_compare_different_encoding_string
- s1 = "\xff".force_encoding("UTF-8")
- s2 = "\xff".force_encoding("ISO-2022-JP")
+ 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, "FoO".casecmp("fOO"))
- assert_equal(1, "FoO".casecmp("BaR"))
- assert_equal(-1, "baR".casecmp("FoO"))
- assert_equal(1, "\u3042B".casecmp("\u3042a"))
+ 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("foo".casecmp(:foo))
- assert_nil("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, "FoO".casecmp(o))
+ 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?
- assert_equal(true, 'FoO'.casecmp?('fOO'))
- assert_equal(false, 'FoO'.casecmp?('BaR'))
- assert_equal(false, 'baR'.casecmp?('FoO'))
- assert_equal(true, 'äöü'.casecmp?('ÄÖÜ'))
+ assert_equal(true, S('FoO').casecmp?('fOO'))
+ assert_equal(false, S('FoO').casecmp?('BaR'))
+ assert_equal(false, S('baR').casecmp?('FoO'))
+ assert_equal(true, S('äöü').casecmp?('ÄÖÜ'))
+ assert_equal(false, S("foo").casecmp?("foo\0"))
- assert_nil("foo".casecmp?(:foo))
- assert_nil("foo".casecmp?(Object.new))
+ 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, "FoO".casecmp?(o))
+ 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
- assert_equal("\u3042AB", "\u3042aB".upcase)
+ assert_equal("\u3042AB", S("\u3042aB").upcase)
end
def test_downcase2
- assert_equal("\u3042ab", "\u3042aB".downcase)
+ assert_equal("\u3042ab", S("\u3042aB").downcase)
end
def test_rstrip
- assert_equal(" hello", " hello ".rstrip)
- assert_equal("\u3042", "\u3042 ".rstrip)
- assert_raise(Encoding::CompatibilityError) { "\u3042".encode("ISO-2022-JP").rstrip }
+ assert_equal(" hello", S(" hello ").rstrip)
+ assert_equal("\u3042", S("\u3042 ").rstrip)
+ assert_equal("\u3042", S("\u3042\u0000").rstrip)
+ assert_raise(Encoding::CompatibilityError) { S("\u3042".encode("ISO-2022-JP")).rstrip }
end
def test_rstrip_bang
@@ -2691,12 +3051,22 @@ CODE
assert_equal(nil, s4.rstrip!)
assert_equal("\u3042", s4)
- assert_raise(Encoding::CompatibilityError) { "\u3042".encode("ISO-2022-JP").rstrip! }
+ s5 = S("\u3042\u0000")
+ assert_equal("\u3042", s5.rstrip!)
+ assert_equal("\u3042", s5)
+
+ assert_raise(Encoding::CompatibilityError) { S("\u3042".encode("ISO-2022-JP")).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("abc \x80 ".force_encoding('UTF-8')).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("abc\x80 ".force_encoding('UTF-8')).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("abc \x80".force_encoding('UTF-8')).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("\x80".force_encoding('UTF-8')).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S(" \x80 ".force_encoding('UTF-8')).rstrip! }
end
def test_lstrip
- assert_equal("hello ", " hello ".lstrip)
- assert_equal("\u3042", " \u3042".lstrip)
+ assert_equal("hello ", S(" hello ").lstrip)
+ assert_equal("\u3042", S(" \u3042").lstrip)
+ assert_equal("hello ", S("\x00hello ").lstrip)
end
def test_lstrip_bang
@@ -2715,13 +3085,19 @@ CODE
s4 = S("\u3042")
assert_equal(nil, s4.lstrip!)
assert_equal("\u3042", s4)
+
+ s5 = S("\u0000\u3042")
+ assert_equal("\u3042", s5.lstrip!)
+ assert_equal("\u3042", s5)
end
- def test_delete_prefix
- assert_raise(TypeError) { 'hello'.delete_prefix(nil) }
- assert_raise(TypeError) { 'hello'.delete_prefix(1) }
- assert_raise(TypeError) { 'hello'.delete_prefix(/hel/) }
+ def test_delete_prefix_type_error
+ assert_raise(TypeError) { S('hello').delete_prefix(nil) }
+ assert_raise(TypeError) { S('hello').delete_prefix(1) }
+ assert_raise(TypeError) { S('hello').delete_prefix(/hel/) }
+ end
+ def test_delete_prefix
s = S("hello")
assert_equal("lo", s.delete_prefix('hel'))
assert_equal("hello", s)
@@ -2741,8 +3117,9 @@ CODE
s = S("hello")
assert_equal("hello", s.delete_prefix("\u{3053 3093}"))
assert_equal("hello", s)
+ end
- # skip if argument is a broken string
+ def test_delete_prefix_broken_encoding
s = S("\xe3\x81\x82")
assert_equal("\xe3\x81\x82", s.delete_prefix("\xe3"))
assert_equal("\xe3\x81\x82", s)
@@ -2751,23 +3128,31 @@ CODE
assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s.delete_prefix("\x95"))
assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s)
- # clear coderange
+ assert_equal("\xFE", S("\xFF\xFE").delete_prefix("\xFF"))
+ assert_equal("\xBE", S("hello\xBE").delete_prefix("hello"))
+ assert_equal("\xBE", S("\xFFhello\xBE").delete_prefix("\xFFhello"))
+ end
+
+ def test_delete_prefix_clear_coderange
s = S("\u{3053 3093}hello")
assert_not_predicate(s, :ascii_only?)
assert_predicate(s.delete_prefix("\u{3053 3093}"), :ascii_only?)
+ end
- # argument should be converted to String
+ def test_delete_prefix_argument_conversion
klass = Class.new { def to_str; 'a'; end }
s = S("abba")
assert_equal("bba", s.delete_prefix(klass.new))
assert_equal("abba", s)
end
- def test_delete_prefix_bang
- assert_raise(TypeError) { 'hello'.delete_prefix!(nil) }
- assert_raise(TypeError) { 'hello'.delete_prefix!(1) }
- assert_raise(TypeError) { 'hello'.delete_prefix!(/hel/) }
+ def test_delete_prefix_bang_type_error
+ assert_raise(TypeError) { S('hello').delete_prefix!(nil) }
+ assert_raise(TypeError) { S('hello').delete_prefix!(1) }
+ assert_raise(TypeError) { S('hello').delete_prefix!(/hel/) }
+ end
+ def test_delete_prefix_bang
s = S("hello")
assert_equal("lo", s.delete_prefix!('hel'))
assert_equal("lo", s)
@@ -2787,23 +3172,32 @@ CODE
s = S("hello")
assert_equal(nil, s.delete_prefix!("\u{3053 3093}"))
assert_equal("hello", s)
+ end
- # skip if argument is a broken string
+ def test_delete_prefix_bang_broken_encoding
s = S("\xe3\x81\x82")
assert_equal(nil, s.delete_prefix!("\xe3"))
assert_equal("\xe3\x81\x82", s)
- # clear coderange
+ s = S("\xFF\xFE")
+ assert_equal("\xFE", s.delete_prefix!("\xFF"))
+ assert_equal("\xFE", s)
+ end
+
+ def test_delete_prefix_bang_clear_coderange
s = S("\u{3053 3093}hello")
assert_not_predicate(s, :ascii_only?)
assert_predicate(s.delete_prefix!("\u{3053 3093}"), :ascii_only?)
+ end
- # argument should be converted to String
+ def test_delete_prefix_bang_argument_conversion
klass = Class.new { def to_str; 'a'; end }
s = S("abba")
assert_equal("bba", s.delete_prefix!(klass.new))
assert_equal("bba", s)
+ end
+ def test_delete_prefix_bang_frozen_error
s = S("ax").freeze
assert_raise_with_message(FrozenError, /frozen/) {s.delete_prefix!("a")}
@@ -2816,11 +3210,13 @@ CODE
assert_raise_with_message(FrozenError, /frozen/) {s.delete_prefix!(o)}
end
- def test_delete_suffix
- assert_raise(TypeError) { 'hello'.delete_suffix(nil) }
- assert_raise(TypeError) { 'hello'.delete_suffix(1) }
- assert_raise(TypeError) { 'hello'.delete_suffix(/hel/) }
+ def test_delete_suffix_type_error
+ assert_raise(TypeError) { S('hello').delete_suffix(nil) }
+ assert_raise(TypeError) { S('hello').delete_suffix(1) }
+ assert_raise(TypeError) { S('hello').delete_suffix(/hel/) }
+ end
+ def test_delete_suffix
s = S("hello")
assert_equal("hel", s.delete_suffix('lo'))
assert_equal("hello", s)
@@ -2840,23 +3236,28 @@ CODE
s = S("hello")
assert_equal("hello", s.delete_suffix("\u{3061 306f}"))
assert_equal("hello", s)
+ end
- # skip if argument is a broken string
+ def test_delete_suffix_broken_encoding
s = S("\xe3\x81\x82")
assert_equal("\xe3\x81\x82", s.delete_suffix("\x82"))
assert_equal("\xe3\x81\x82", s)
+ end
- # clear coderange
+ def test_delete_suffix_clear_coderange
s = S("hello\u{3053 3093}")
assert_not_predicate(s, :ascii_only?)
assert_predicate(s.delete_suffix("\u{3053 3093}"), :ascii_only?)
+ end
- # argument should be converted to String
+ def test_delete_suffix_argument_conversion
klass = Class.new { def to_str; 'a'; end }
s = S("abba")
assert_equal("abb", s.delete_suffix(klass.new))
assert_equal("abba", s)
+ end
+ def test_delete_suffix_newline
# chomp removes any of "\n", "\r\n", "\r" when "\n" is specified,
# but delete_suffix does not
s = "foo\n"
@@ -2867,11 +3268,13 @@ CODE
assert_equal("foo\r", s.delete_suffix("\n"))
end
- def test_delete_suffix_bang
- assert_raise(TypeError) { 'hello'.delete_suffix!(nil) }
- assert_raise(TypeError) { 'hello'.delete_suffix!(1) }
- assert_raise(TypeError) { 'hello'.delete_suffix!(/hel/) }
+ def test_delete_suffix_bang_type_error
+ assert_raise(TypeError) { S('hello').delete_suffix!(nil) }
+ assert_raise(TypeError) { S('hello').delete_suffix!(1) }
+ assert_raise(TypeError) { S('hello').delete_suffix!(/hel/) }
+ end
+ def test_delete_suffix_bang_frozen_error
s = S("hello").freeze
assert_raise_with_message(FrozenError, /frozen/) {s.delete_suffix!('lo')}
@@ -2882,7 +3285,9 @@ CODE
"x"
end
assert_raise_with_message(FrozenError, /frozen/) {s.delete_suffix!(o)}
+ end
+ def test_delete_suffix_bang
s = S("hello")
assert_equal("hel", s.delete_suffix!('lo'))
assert_equal("hel", s)
@@ -2902,8 +3307,9 @@ CODE
s = S("hello")
assert_equal(nil, s.delete_suffix!("\u{3061 306f}"))
assert_equal("hello", s)
+ end
- # skip if argument is a broken string
+ def test_delete_suffix_bang_broken_encoding
s = S("\xe3\x81\x82")
assert_equal(nil, s.delete_suffix!("\x82"))
assert_equal("\xe3\x81\x82", s)
@@ -2911,18 +3317,22 @@ CODE
s = S("\x95\x5c").force_encoding("Shift_JIS")
assert_equal(nil, s.delete_suffix!("\x5c"))
assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s)
+ end
- # clear coderange
+ def test_delete_suffix_bang_clear_coderange
s = S("hello\u{3053 3093}")
assert_not_predicate(s, :ascii_only?)
assert_predicate(s.delete_suffix!("\u{3053 3093}"), :ascii_only?)
+ end
- # argument should be converted to String
+ def test_delete_suffix_bang_argument_conversion
klass = Class.new { def to_str; 'a'; end }
s = S("abba")
assert_equal("abb", s.delete_suffix!(klass.new))
assert_equal("abb", s)
+ end
+ def test_delete_suffix_bang_newline
# chomp removes any of "\n", "\r\n", "\r" when "\n" is specified,
# but delete_suffix does not
s = "foo\n"
@@ -2959,7 +3369,7 @@ CODE
end
def test_shared_force_encoding
- s = "\u{3066}\u{3059}\u{3068}".gsub(//, '')
+ s = S("\u{3066}\u{3059}\u{3068}").gsub(//, '')
h = {}
h[s] = nil
k = h.keys[0]
@@ -2973,33 +3383,26 @@ CODE
def test_ascii_incomat_inspect
bug4081 = '[ruby-core:33283]'
- [Encoding::UTF_16LE, Encoding::UTF_16BE,
- Encoding::UTF_32LE, Encoding::UTF_32BE].each do |e|
- assert_equal('"abc"', "abc".encode(e).inspect)
- assert_equal('"\\u3042\\u3044\\u3046"', "\u3042\u3044\u3046".encode(e).inspect)
- assert_equal('"ab\\"c"', "ab\"c".encode(e).inspect, bug4081)
+ WIDE_ENCODINGS.each do |e|
+ assert_equal('"abc"', S("abc".encode(e)).inspect)
+ 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
- i = "abc\"\\".force_encoding("utf-8").inspect
- ensure
- $VERBOSE = nil
- Encoding.default_external = ext
- $VERBOSE = verbose
+
+ EnvUtil.with_default_external(Encoding::US_ASCII) do
+ i = S("abc\"\\".force_encoding("utf-8")).inspect
+
+ assert_equal('"abc\\"\\\\"', i, bug4081)
end
- assert_equal('"abc\\"\\\\"', i, bug4081)
end
def test_dummy_inspect
assert_equal('"\e\x24\x42\x22\x4C\x22\x68\e\x28\x42"',
- "\u{ffe2}\u{2235}".encode("cp50220").inspect)
+ S("\u{ffe2}\u{2235}".encode("cp50220")).inspect)
end
def test_prepend
- assert_equal(S("hello world!"), "!".prepend("hello ", "world"))
+ assert_equal(S("hello world!"), S("!").prepend("hello ", "world"))
b = S("ue")
assert_equal(S("ueueue"), b.prepend(b, b))
@@ -3007,7 +3410,7 @@ CODE
def foo.to_str
"b"
end
- assert_equal(S("ba"), "a".prepend(foo))
+ assert_equal(S("ba"), S("a").prepend(foo))
a = S("world")
b = S("hello ")
@@ -3021,34 +3424,39 @@ CODE
end
def test_byteslice
- assert_equal("h", "hello".byteslice(0))
- assert_equal(nil, "hello".byteslice(5))
- assert_equal("o", "hello".byteslice(-1))
- assert_equal(nil, "hello".byteslice(-6))
-
- assert_equal("", "hello".byteslice(0, 0))
- assert_equal("hello", "hello".byteslice(0, 6))
- assert_equal("hello", "hello".byteslice(0, 6))
- assert_equal("", "hello".byteslice(5, 1))
- assert_equal("o", "hello".byteslice(-1, 6))
- assert_equal(nil, "hello".byteslice(-6, 1))
- assert_equal(nil, "hello".byteslice(0, -1))
-
- assert_equal("h", "hello".byteslice(0..0))
- assert_equal("", "hello".byteslice(5..0))
- assert_equal("o", "hello".byteslice(4..5))
- assert_equal(nil, "hello".byteslice(6..0))
- assert_equal("", "hello".byteslice(-1..0))
- assert_equal("llo", "hello".byteslice(-3..5))
-
- assert_equal(u("\x81"), "\u3042".byteslice(1))
- assert_equal(u("\x81\x82"), "\u3042".byteslice(1, 2))
- assert_equal(u("\x81\x82"), "\u3042".byteslice(1..2))
-
- assert_equal(u("\x82")+("\u3042"*9), ("\u3042"*10).byteslice(2, 28))
+ assert_equal("h", S("hello").byteslice(0))
+ assert_equal(nil, S("hello").byteslice(5))
+ assert_equal("o", S("hello").byteslice(-1))
+ assert_equal(nil, S("hello").byteslice(-6))
+
+ assert_equal("", S("hello").byteslice(0, 0))
+ assert_equal("hello", S("hello").byteslice(0, 6))
+ assert_equal("hello", S("hello").byteslice(0, 6))
+ assert_equal("", S("hello").byteslice(5, 1))
+ assert_equal("o", S("hello").byteslice(-1, 6))
+ assert_equal(nil, S("hello").byteslice(-6, 1))
+ assert_equal(nil, S("hello").byteslice(0, -1))
+
+ assert_equal("h", S("hello").byteslice(0..0))
+ assert_equal("", S("hello").byteslice(5..0))
+ assert_equal("o", S("hello").byteslice(4..5))
+ assert_equal(nil, S("hello").byteslice(6..0))
+ assert_equal("", S("hello").byteslice(-1..0))
+ assert_equal("llo", S("hello").byteslice(-3..5))
+
+ assert_equal(u("\x81"), S("\u3042").byteslice(1))
+ assert_equal(u("\x81\x82"), S("\u3042").byteslice(1, 2))
+ assert_equal(u("\x81\x82"), S("\u3042").byteslice(1..2))
+
+ 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, "\u3042".byteslice(0, 2).valid_encoding?, bug7954)
+ assert_equal(false, S("\u3042").byteslice(0, 2).valid_encoding?, bug7954)
assert_equal(false, ("\u3042"*10).byteslice(0, 20).valid_encoding?, bug7954)
end
@@ -3063,6 +3471,8 @@ CODE
end
def test_eq_tilde_can_be_overridden
+ return unless @cls == String
+
assert_separately([], <<-RUBY)
class String
undef =~
@@ -3080,7 +3490,7 @@ CODE
end
def test_regexp_match_subclass
- s = Bug9581.new("abc")
+ s = Bug9581.new(S("abc"))
r = /abc/
assert_equal(:foo, s =~ r)
assert_equal(:foo, s.send(:=~, r))
@@ -3090,6 +3500,7 @@ CODE
def test_LSHIFT_neary_long_max
return unless @cls == String
+
assert_ruby_status([], <<-'end;', '[ruby-core:61886] [Bug #9709]', timeout: 20)
begin
a = "a" * 0x4000_0000
@@ -3101,6 +3512,8 @@ CODE
# enable only when string size range is smaller than memory space
def test_uplus_minus
+ return unless @cls == String
+
str = "foo"
assert_not_predicate(str, :frozen?)
assert_not_predicate(+str, :frozen?)
@@ -3109,7 +3522,11 @@ CODE
assert_same(str, +str)
assert_not_same(str, -str)
- str = "bar".freeze
+ require 'objspace'
+
+ str = "test_uplus_minus_str".freeze
+ assert_includes ObjectSpace.dump(str), '"fstring":true'
+
assert_predicate(str, :frozen?)
assert_not_predicate(+str, :frozen?)
assert_predicate(-str, :frozen?)
@@ -3117,29 +3534,507 @@ CODE
assert_not_same(str, +str)
assert_same(str, -str)
- bar = %w(b a r).join('')
- assert_same(str, -bar, "uminus deduplicates [Feature #13077]")
+ bar = -%w(test uplus minus str).join('_')
+ assert_same(str, bar, "uminus deduplicates [Feature #13077] str: #{ObjectSpace.dump(str)} bar: #{ObjectSpace.dump(bar)}")
+ end
+
+ def test_uminus_dedup_in_place
+ dynamic = "this string is unique and frozen #{rand}".freeze
+ assert_same dynamic, -dynamic
+ assert_same dynamic, -dynamic.dup
+ end
+
+ def test_uminus_frozen
+ return unless @cls == String
+
+ # embedded
+ str1 = ("foobar" * 3).freeze
+ str2 = ("foobar" * 3).freeze
+ assert_not_same str1, str2
+ assert_same str1, -str1
+ assert_same str1, -str2
+
+ # regular
+ str1 = ("foobar" * 4).freeze
+ str2 = ("foobar" * 4).freeze
+ assert_not_same str1, str2
+ assert_same str1, -str1
+ assert_same str1, -str2
+ end
+
+ def test_uminus_no_freeze_not_bare
+ str = S("foo")
+ assert_instance_of(@cls, -str)
+ assert_equal(false, str.frozen?)
+
+ str = S("foo")
+ str.instance_variable_set(:@iv, 1)
+ assert_instance_of(@cls, -str)
+ assert_equal(false, str.frozen?)
+ assert_equal(1, str.instance_variable_get(:@iv))
+
+ str = S("foo")
+ assert_instance_of(@cls, -str)
+ 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, "a".ord)
- assert_equal(97, "abc".ord)
- assert_equal(0x3042, "\u3042\u3043".ord)
- assert_raise(ArgumentError) { "".ord }
+ assert_equal(97, S("a").ord)
+ assert_equal(97, S("abc").ord)
+ assert_equal(0x3042, S("\u3042\u3043").ord)
+ assert_raise(ArgumentError) { S("").ord }
end
def test_chr
- assert_equal("a", "abcde".chr)
- assert_equal("a", "a".chr)
- assert_equal("\u3042", "\u3042\u3043".chr)
- assert_equal('', ''.chr)
+ assert_equal("a", S("abcde").chr)
+ assert_equal("a", S("a").chr)
+ assert_equal("\u3042", S("\u3042\u3043").chr)
+ assert_equal('', S('').chr)
end
def test_substr_code_range
- data = "\xff" + "a"*200
+ data = S("\xff" + "a"*200)
assert_not_predicate(data, :valid_encoding?)
assert_predicate(data[100..-1], :valid_encoding?)
end
+
+ def test_byteindex
+ assert_byteindex(0, S("hello"), ?h)
+ assert_byteindex(1, S("hello"), S("ell"))
+ assert_byteindex(2, S("hello"), /ll./)
+
+ assert_byteindex(3, S("hello"), ?l, 3)
+ assert_byteindex(3, S("hello"), S("l"), 3)
+ assert_byteindex(3, S("hello"), /l./, 3)
+
+ assert_byteindex(nil, S("hello"), ?z, 3)
+ assert_byteindex(nil, S("hello"), S("z"), 3)
+ assert_byteindex(nil, S("hello"), /z./, 3)
+
+ assert_byteindex(nil, S("hello"), ?z)
+ assert_byteindex(nil, S("hello"), S("z"))
+ assert_byteindex(nil, S("hello"), /z./)
+
+ assert_byteindex(0, S(""), S(""))
+ assert_byteindex(0, S(""), //)
+ assert_byteindex(nil, S(""), S("hello"))
+ assert_byteindex(nil, S(""), /hello/)
+ assert_byteindex(0, S("hello"), S(""))
+ assert_byteindex(0, S("hello"), //)
+
+ s = S("long") * 1000 << "x"
+ assert_byteindex(nil, s, S("y"))
+ assert_byteindex(4 * 1000, s, S("x"))
+ s << "yx"
+ assert_byteindex(4 * 1000, s, S("x"))
+ assert_byteindex(4 * 1000, s, S("xyx"))
+
+ o = Object.new
+ def o.to_str; "bar"; end
+ assert_byteindex(3, S("foobarbarbaz"), o)
+ assert_raise(TypeError) { S("foo").byteindex(Object.new) }
+
+ assert_byteindex(nil, S("foo"), //, -100)
+ assert_byteindex(nil, S("foo"), //, -4)
+
+ assert_byteindex(2, S("abcdbce"), /b\Kc/)
+
+ assert_byteindex(0, S("ã“ã‚“ã«ã¡ã¯"), ?ã“)
+ assert_byteindex(3, S("ã“ã‚“ã«ã¡ã¯"), S("ã‚“ã«ã¡"))
+ assert_byteindex(6, S("ã“ã‚“ã«ã¡ã¯"), /ã«ã¡./)
+
+ assert_byteindex(0, S("ã«ã‚“ã«ã¡ã¯"), ?ã«, 0)
+ assert_raise(IndexError) { S("ã«ã‚“ã«ã¡ã¯").byteindex(?ã«, 1) }
+ assert_raise(IndexError) { S("ã«ã‚“ã«ã¡ã¯").byteindex(?ã«, 5) }
+ assert_byteindex(6, S("ã«ã‚“ã«ã¡ã¯"), ?ã«, 6)
+ assert_byteindex(6, S("ã«ã‚“ã«ã¡ã¯"), S("ã«"), 6)
+ assert_byteindex(6, S("ã«ã‚“ã«ã¡ã¯"), /ã«./, 6)
+ assert_raise(IndexError) { S("ã«ã‚“ã«ã¡ã¯").byteindex(?ã«, 7) }
+
+ s = S("foobarbarbaz")
+ assert !1000.times.any? {s.byteindex("", 100_000_000)}
+ end
+
+ def test_byterindex
+ assert_byterindex(3, S("hello"), ?l)
+ assert_byterindex(6, S("ell, hello"), S("ell"))
+ assert_byterindex(7, S("ell, hello"), /ll./)
+
+ assert_byterindex(3, S("hello,lo"), ?l, 3)
+ assert_byterindex(3, S("hello,lo"), S("l"), 3)
+ assert_byterindex(3, S("hello,lo"), /l./, 3)
+
+ assert_byterindex(nil, S("hello"), ?z, 3)
+ assert_byterindex(nil, S("hello"), S("z"), 3)
+ assert_byterindex(nil, S("hello"), /z./, 3)
+
+ assert_byterindex(nil, S("hello"), ?z)
+ assert_byterindex(nil, S("hello"), S("z"))
+ assert_byterindex(nil, S("hello"), /z./)
+
+ assert_byterindex(5, S("hello"), S(""))
+ assert_byterindex(5, S("hello"), S(""), 5)
+ assert_byterindex(4, S("hello"), S(""), 4)
+ assert_byterindex(0, S("hello"), S(""), 0)
+
+ o = Object.new
+ def o.to_str; "bar"; end
+ assert_byterindex(6, S("foobarbarbaz"), o)
+ assert_raise(TypeError) { S("foo").byterindex(Object.new) }
+
+ assert_byterindex(nil, S("foo"), //, -100)
+
+ m = assert_byterindex(3, S("foo"), //)
+ assert_equal([3, 3], m.offset(0))
+ assert_byterindex(3, S("foo"), //, 4)
+
+ assert_byterindex(5, S("abcdbce"), /b\Kc/)
+
+ assert_byterindex(6, S("ã“ã‚“ã«ã¡ã¯"), ?ã«)
+ assert_byterindex(18, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"))
+ assert_byterindex(18, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), /ã«ã¡./)
+
+ assert_raise(IndexError) { S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯").byterindex(S("ã«ã¡ã¯"), 19) }
+ assert_raise(IndexError) { S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯").byterindex(S("ã«ã¡ã¯"), -2) }
+ assert_byterindex(18, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 18)
+ assert_byterindex(18, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), -3)
+ assert_raise(IndexError) { S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯").byterindex(S("ã«ã¡ã¯"), 17) }
+ assert_raise(IndexError) { S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯").byterindex(S("ã«ã¡ã¯"), -4) }
+ assert_raise(IndexError) { S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯").byterindex(S("ã«ã¡ã¯"), 1) }
+ assert_byterindex(0, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 0)
+
+ assert_byterindex(0, S("ã“ã‚“ã«ã¡ã¯"), S("ã“ã‚“ã«ã¡ã¯"))
+ assert_byterindex(nil, S("ã“ã‚“ã«ã¡"), S("ã“ã‚“ã«ã¡ã¯"))
+ assert_byterindex(nil, S("ã“"), S("ã“ã‚“ã«ã¡ã¯"))
+ assert_byterindex(nil, S(""), S("ã“ã‚“ã«ã¡ã¯"))
+ end
+
+ def test_bytesplice
+ assert_bytesplice_raise(IndexError, S("hello"), -6, 0, "bye")
+ assert_bytesplice_result("byehello", S("hello"), -5, 0, "bye")
+ assert_bytesplice_result("byehello", S("hello"), 0, 0, "bye")
+ assert_bytesplice_result("byeello", S("hello"), 0, 1, "bye")
+ assert_bytesplice_result("bye", S("hello"), 0, 5, "bye")
+ assert_bytesplice_result("bye", S("hello"), 0, 6, "bye")
+
+ assert_bytesplice_raise(IndexError, S("hello"), -5, 0, "bye", -4, 0)
+ assert_bytesplice_result("byehello", S("hello"), 0, 0, "bye", 0, 3)
+ assert_bytesplice_result("yehello", S("hello"), 0, 0, "bye", 1, 3)
+ assert_bytesplice_result("yehello", S("hello"), 0, 0, "bye", 1, 2)
+ assert_bytesplice_result("ehello", S("hello"), 0, 0, "bye", 2, 1)
+ assert_bytesplice_result("hello", S("hello"), 0, 0, "bye", 3, 0)
+ assert_bytesplice_result("hello", s = S("hello"), 0, 5, s, 0, 5)
+ assert_bytesplice_result("elloo", s = S("hello"), 0, 4, s, 1, 4)
+ assert_bytesplice_result("llolo", s = S("hello"), 0, 3, s, 2, 3)
+ assert_bytesplice_result("lollo", s = S("hello"), 0, 2, s, 3, 2)
+ assert_bytesplice_result("oello", s = S("hello"), 0, 1, s, 4, 1)
+ assert_bytesplice_result("hhell", s = S("hello"), 1, 4, s, 0, 4)
+ assert_bytesplice_result("hehel", s = S("hello"), 2, 3, s, 0, 3)
+ assert_bytesplice_result("helhe", s = S("hello"), 3, 2, s, 0, 2)
+ assert_bytesplice_result("hellh", s = S("hello"), 4, 1, s, 0, 1)
+
+ assert_bytesplice_raise(RangeError, S("hello"), -6...-6, "bye")
+ assert_bytesplice_result("byehello", S("hello"), -5...-5, "bye")
+ assert_bytesplice_result("byehello", S("hello"), 0...0, "bye")
+ assert_bytesplice_result("byeello", S("hello"), 0..0, "bye")
+ assert_bytesplice_result("byeello", S("hello"), 0...1, "bye")
+ assert_bytesplice_result("byello", S("hello"), 0..1, "bye")
+ assert_bytesplice_result("bye", S("hello"), 0..-1, "bye")
+ assert_bytesplice_result("bye", S("hello"), 0...5, "bye")
+ assert_bytesplice_result("bye", S("hello"), 0...6, "bye")
+ assert_bytesplice_result("llolo", s = S("hello"), 0..2, s, 2..4)
+
+ assert_bytesplice_raise(RangeError, S("hello"), -5...-5, "bye", -6...-6)
+ assert_bytesplice_result("byehello", S("hello"), -5...-5, "bye", 0..-1)
+ assert_bytesplice_result("byehello", S("hello"), 0...0, "bye", 0..-1)
+ assert_bytesplice_result("bhello", S("hello"), 0...0, "bye", 0..0)
+ assert_bytesplice_result("byhello", S("hello"), 0...0, "bye", 0..1)
+ assert_bytesplice_result("byehello", S("hello"), 0...0, "bye", 0..2)
+ assert_bytesplice_result("yehello", S("hello"), 0...0, "bye", 1..2)
+
+ assert_bytesplice_raise(TypeError, S("hello"), 0, "bye")
+
+ assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), -16, 0, "bye")
+ assert_bytesplice_result("byeã“ã‚“ã«ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), -15, 0, "bye")
+ assert_bytesplice_result("byeã“ã‚“ã«ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 0, "bye")
+ assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 1, 0, "bye")
+ assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 1, "bye")
+ assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 2, "bye")
+ assert_bytesplice_result("byeã‚“ã«ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 3, "bye")
+ assert_bytesplice_result("ã“ã‚“ã«ã¡ã¯bye", S("ã“ã‚“ã«ã¡ã¯"), 15, 0, "bye")
+
+ assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 0, "ã•よã†ãªã‚‰", -16, 0)
+ assert_bytesplice_result("ã“ã‚“ã«ã¡ã¯ã•よã†ãªã‚‰", S("ã“ã‚“ã«ã¡ã¯"), 15, 0, "ã•よã†ãªã‚‰", 0, 15)
+ assert_bytesplice_result("ã•よã†ãªã‚‰", S("ã“ã‚“ã«ã¡ã¯"), 0, 15, "ã•よã†ãªã‚‰", 0, 15)
+ assert_bytesplice_result("ã•ã‚“ã«ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 3, "ã•よã†ãªã‚‰", 0, 3)
+ assert_bytesplice_result("ã•よã†ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 9, "ã•よã†ãªã‚‰", 0, 9)
+ assert_bytesplice_result("よã†ãªã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 9, "ã•よã†ãªã‚‰", 3, 9)
+ assert_bytesplice_result("よã†ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 9, "ã•よã†ãªã‚‰", 3, 6)
+ assert_bytesplice_result("よã†ãªã‚‰ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 9, "ã•よã†ãªã‚‰", 3, 12)
+ assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 15, "ã•よã†ãªã‚‰", -16, 0)
+ assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 15, "ã•よã†ãªã‚‰", 1, 0)
+ assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 15, "ã•よã†ãªã‚‰", 2, 0)
+ assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 15, "ã•よã†ãªã‚‰", 0, 1)
+ assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 15, "ã•よã†ãªã‚‰", 0, 2)
+ assert_bytesplice_result("ã«ã¡ã¯ã¡ã¯", s = S("ã“ã‚“ã«ã¡ã¯"), 0, 9, s, 6, 9)
+
+ assert_bytesplice_result("", S(""), 0, 0, "")
+ assert_bytesplice_result("xxx", S(""), 0, 0, "xxx")
+
+ assert_bytesplice_raise(ArgumentError, S("hello"), 0, 5, "bye", 0)
+ assert_bytesplice_raise(ArgumentError, S("hello"), 0, 5, "bye", 0..-1)
+ assert_bytesplice_raise(ArgumentError, S("hello"), 0..-1, "bye", 0, 3)
+ end
+
+ def test_append_bytes_into_binary
+ buf = S("".b)
+ assert_equal Encoding::BINARY, buf.encoding
+
+ buf.append_as_bytes(S("hello"))
+ assert_equal "hello".b, buf
+ assert_equal Encoding::BINARY, buf.encoding
+
+ buf.append_as_bytes(S("ã“ã‚“ã«ã¡ã¯"))
+ assert_equal S("helloã“ã‚“ã«ã¡ã¯".b), buf
+ assert_equal Encoding::BINARY, buf.encoding
+ end
+
+ def test_append_bytes_into_utf8
+ buf = S("")
+ assert_equal Encoding::UTF_8, buf.encoding
+
+ buf.append_as_bytes(S("hello"))
+ assert_equal S("hello"), buf
+ assert_equal Encoding::UTF_8, buf.encoding
+ assert_predicate buf, :ascii_only?
+ assert_predicate buf, :valid_encoding?
+
+ buf.append_as_bytes(S("ã“ã‚“ã«ã¡ã¯"))
+ assert_equal S("helloã“ã‚“ã«ã¡ã¯"), buf
+ assert_equal Encoding::UTF_8, buf.encoding
+ refute_predicate buf, :ascii_only?
+ assert_predicate buf, :valid_encoding?
+
+ buf.append_as_bytes(S("\xE2\x82".b))
+ assert_equal S("helloã“ã‚“ã«ã¡ã¯\xE2\x82"), buf
+ assert_equal Encoding::UTF_8, buf.encoding
+ refute_predicate buf, :valid_encoding?
+
+ buf.append_as_bytes(S("\xAC".b))
+ assert_equal S("helloã“ã‚“ã«ã¡ã¯â‚¬"), buf
+ assert_equal Encoding::UTF_8, buf.encoding
+ assert_predicate buf, :valid_encoding?
+ end
+
+ def test_append_bytes_into_utf32
+ buf = S("abc".encode(Encoding::UTF_32LE))
+ assert_equal Encoding::UTF_32LE, buf.encoding
+
+ buf.append_as_bytes("def")
+ assert_equal Encoding::UTF_32LE, buf.encoding
+ refute_predicate buf, :valid_encoding?
+ end
+
+ def test_chilled_string
+ chilled_string = eval('"chilled"')
+
+ assert_not_predicate chilled_string, :frozen?
+
+ assert_not_predicate chilled_string.dup, :frozen?
+ assert_not_predicate chilled_string.clone, :frozen?
+
+ # @+ treat the original string as frozen
+ assert_not_predicate(+chilled_string, :frozen?)
+ assert_not_same chilled_string, +chilled_string
+
+ # @- treat the original string as mutable
+ assert_predicate(-chilled_string, :frozen?)
+ assert_not_same chilled_string, -chilled_string
+ end
+
+ def test_chilled_string_setivar
+ deprecated = Warning[:deprecated]
+ Warning[:deprecated] = false
+
+ String.class_eval <<~RUBY, __FILE__, __LINE__ + 1
+ def setivar!
+ @ivar = 42
+ @ivar
+ end
+ RUBY
+ chilled_string = eval('"chilled"')
+ begin
+ assert_equal 42, chilled_string.setivar!
+ ensure
+ String.undef_method(:setivar!)
+ end
+ ensure
+ Warning[:deprecated] = deprecated
+ end
+
+ def test_chilled_string_substring
+ deprecated = Warning[:deprecated]
+ Warning[:deprecated] = false
+ chilled_string = eval('"a chilled string."')
+ substring = chilled_string[0..-1]
+ assert_equal("a chilled string.", substring)
+ chilled_string[0..-1] = "This string is defrosted."
+ assert_equal("a chilled string.", substring)
+ ensure
+ 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)
+ assert_equal(expected, s.send(:bytesplice, *args))
+ assert_equal(expected, s)
+ end
+
+ def assert_bytesplice_raise(e, s, *args)
+ assert_raise(e) { s.send(:bytesplice, *args) }
+ end
+
+ def assert_index_like(method, expected, string, match, *rest)
+ message = "#{method} with string does not affect $~"
+ /.*/ =~ message
+ md_before = $~
+ assert_equal(expected, string.__send__(method, match, *rest))
+ md_after = $~
+ case match
+ when Regexp
+ if expected
+ assert_not_nil(md_after)
+ assert_not_same(md_before, md_after)
+ else
+ assert_nil(md_after)
+ end
+ else
+ assert_same(md_before, md_after)
+ end
+ md_after
+ end
+
+ def assert_index(expected, string, match, *rest)
+ assert_index_like(:index, expected, string, match, *rest)
+ end
+
+ def assert_rindex(expected, string, match, *rest)
+ assert_index_like(:rindex, expected, string, match, *rest)
+ end
+
+ def assert_byteindex(expected, string, match, *rest)
+ assert_index_like(:byteindex, expected, string, match, *rest)
+ end
+
+ 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_string_memory.rb b/test/ruby/test_string_memory.rb
new file mode 100644
index 0000000000..a93a3bd54a
--- /dev/null
+++ b/test/ruby/test_string_memory.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'objspace'
+
+class TestStringMemory < Test::Unit::TestCase
+ def capture_allocations(klass)
+ allocations = []
+
+ EnvUtil.without_gc do
+ GC.start
+ generation = GC.count
+
+ ObjectSpace.trace_object_allocations do
+ yield
+
+ ObjectSpace.each_object(klass) do |instance|
+ allocations << instance if ObjectSpace.allocation_generation(instance) == generation
+ end
+ end
+
+ return allocations.map do |instance|
+ [
+ ObjectSpace.allocation_sourcefile(instance),
+ ObjectSpace.allocation_sourceline(instance),
+ instance.class,
+ instance,
+ ]
+ end.select do |path,|
+ # drop strings not created in this file
+ # (the parallel testing framework may create strings in a separate thread)
+ path == __FILE__
+ end
+ end
+ end
+
+ def test_byteslice_prefix
+ string = ("a" * 100_000).freeze
+
+ allocations = capture_allocations(String) do
+ string.byteslice(0, 50_000)
+ end
+
+ assert_equal 1, allocations.size, "One object allocation is expected, but allocated: #{ allocations.inspect }"
+ end
+
+ def test_byteslice_postfix
+ string = ("a" * 100_000).freeze
+
+ allocations = capture_allocations(String) do
+ string.byteslice(50_000, 100_000)
+ end
+
+ assert_equal 1, allocations.size, "One object allocation is expected, but allocated: #{ allocations.inspect }"
+ end
+
+ def test_byteslice_postfix_twice
+ string = ("a" * 100_000).freeze
+
+ allocations = capture_allocations(String) do
+ string.byteslice(50_000, 100_000).byteslice(25_000, 50_000)
+ end
+
+ assert_equal 2, allocations.size, "Two object allocations are expected, but allocated: #{ allocations.inspect }"
+ end
+end
diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb
index af68346442..01e5cc68f6 100644
--- a/test/ruby/test_struct.rb
+++ b/test/ruby/test_struct.rb
@@ -23,6 +23,10 @@ module TestStruct
test.bar = 47
assert_equal(47, test.bar)
+
+ @Struct.class_eval do
+ remove_const :Test
+ end
end
# [ruby-dev:26247] more than 10 struct members causes segmentation fault
@@ -37,6 +41,14 @@ module TestStruct
end
end
+ def test_larger_than_largest_pool
+ count = (GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] / RbConfig::SIZEOF["void*"]) + 1
+ list = Array(0..count)
+ klass = @Struct.new(*list.map { |i| :"a_#{i}"})
+ struct = klass.new(*list)
+ assert_equal 0, struct.a_0
+ end
+
def test_small_structs
names = [:a, :b, :c, :d]
1.upto(4) {|n|
@@ -60,6 +72,10 @@ module TestStruct
assert_equal(1, o.a)
end
+ def test_attrset_id
+ assert_raise(ArgumentError) { Struct.new(:x=) }
+ end
+
def test_members
klass = @Struct.new(:a)
o = klass.new(1)
@@ -92,8 +108,9 @@ module TestStruct
assert_equal([:utime, :stime, :cutime, :cstime], Process.times.members)
end
- def test_struct_new_with_empty_hash
- assert_equal({:a=>1}, Struct.new(:a, {}).new({:a=>1}).a)
+ def test_struct_new_with_hash
+ assert_raise_with_message(TypeError, /not a symbol/) {Struct.new(:a, {})}
+ assert_raise_with_message(TypeError, /not a symbol/) {Struct.new(:a, {name: "b"})}
end
def test_struct_new_with_keyword_init
@@ -101,6 +118,7 @@ module TestStruct
@Struct.new("KeywordInitFalse", :a, :b, keyword_init: false)
assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(1, 2) }
+ assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new({a: 100}, 2) }
assert_nothing_raised { @Struct::KeywordInitFalse.new(1, 2) }
assert_nothing_raised { @Struct::KeywordInitTrue.new(a: 1, b: 2) }
assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(1, b: 2) }
@@ -108,6 +126,10 @@ module TestStruct
assert_equal @Struct::KeywordInitTrue.new(a: 1, b: 2).values, @Struct::KeywordInitFalse.new(1, 2).values
assert_equal "#{@Struct}::KeywordInitFalse", @Struct::KeywordInitFalse.inspect
assert_equal "#{@Struct}::KeywordInitTrue(keyword_init: true)", @Struct::KeywordInitTrue.inspect
+ # eval is needed to prevent the warning duplication filter
+ k = Class.new(@Struct::KeywordInitTrue) {def initialize(b, options); super(a: options, b: b); end}
+ o = assert_warn('') { k.new(42, {foo: 1, bar: 2}) }
+ assert_equal(1, o.a[:foo])
@Struct.instance_eval do
remove_const(:KeywordInitTrue)
@@ -115,6 +137,27 @@ module TestStruct
end
end
+ def test_struct_new_with_keyword_init_and_block
+ struct = @Struct.new(:a, :b, keyword_init: true) do
+ def c
+ a + b
+ end
+ end
+
+ assert_equal(3, struct.new(a: 1, b: 2).c)
+ end
+
+ def test_struct_keyword_init_p
+ struct = @Struct.new(:a, :b, keyword_init: true)
+ assert_equal(true, struct.keyword_init?)
+
+ struct = @Struct.new(:a, :b, keyword_init: false)
+ assert_equal(false, struct.keyword_init?)
+
+ struct = @Struct.new(:a, :b)
+ assert_nil(struct.keyword_init?)
+ end
+
def test_initialize
klass = @Struct.new(:a)
assert_raise(ArgumentError) { klass.new(1, 2) }
@@ -126,6 +169,17 @@ module TestStruct
assert_equal 3, klass.new(1,2).total
end
+ def test_initialize_with_kw
+ klass = @Struct.new(:foo, :options) do
+ def initialize(foo, **options)
+ super(foo, options)
+ end
+ end
+ assert_equal({}, klass.new(42, **Hash.new).options)
+ x = assert_warn('') { klass.new(1, bar: 2) }
+ assert_equal 2, x.options[:bar]
+ end
+
def test_each
klass = @Struct.new(:a, :b)
o = klass.new(1, 2)
@@ -216,6 +270,13 @@ module TestStruct
assert_raise(ArgumentError) { o.select(1) }
end
+ def test_filter
+ klass = @Struct.new(:a, :b, :c, :d, :e, :f)
+ o = klass.new(1, 2, 3, 4, 5, 6)
+ assert_equal([1, 3, 5], o.filter {|v| v % 2 != 0 })
+ assert_raise(ArgumentError) { o.filter(1) }
+ end
+
def test_big_struct
klass1 = @Struct.new(*('a'..'z').map(&:to_sym))
o = klass1.new
@@ -272,6 +333,7 @@ module TestStruct
klass = @Struct.new(:a)
o = klass.new(1)
assert_kind_of(Integer, o.hash)
+ assert_kind_of(String, o.hash.to_s)
end
def test_eql
@@ -297,15 +359,30 @@ module TestStruct
end
def test_redefinition_warning
- @Struct.new("RedefinitionWarning")
+ @Struct.new(name = "RedefinitionWarning")
e = EnvUtil.verbose_warning do
@Struct.new("RedefinitionWarning")
end
assert_match(/redefining constant #@Struct::RedefinitionWarning/, e)
+
+ @Struct.class_eval do
+ remove_const name
+ end
+ end
+
+ def test_keyword_args_warning
+ assert_warn('') { assert_equal(1, @Struct.new(:a).new(a: 1).a) }
+ assert_warn('') { assert_equal(1, @Struct.new(:a, keyword_init: nil).new(a: 1).a) }
+ assert_warn('') { assert_equal({a: 1}, @Struct.new(:a).new({a: 1}).a) }
+ assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, :b).new(1, a: 1).b) }
+ assert_warn('') { assert_equal(1, @Struct.new(:a, keyword_init: true).new(a: 1).a) }
+ assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: nil).new({a: 1}).a) }
+ assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: false).new(a: 1).a) }
+ assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: false).new({a: 1}).a) }
end
def test_nonascii
- struct_test = @Struct.new("R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}")
+ struct_test = @Struct.new(name = "R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}")
assert_equal(@Struct.const_get("R\u{e9}sum\u{e9}"), struct_test, '[ruby-core:24849]')
a = struct_test.new(42)
assert_equal("#<struct #@Struct::R\u{e9}sum\u{e9} r\u{e9}sum\u{e9}=42>", a.inspect, '[ruby-core:24849]')
@@ -315,6 +392,10 @@ module TestStruct
assert_nothing_raised(Encoding::CompatibilityError) do
assert_match(/redefining constant #@Struct::R\u{e9}sum\u{e9}/, e)
end
+
+ @Struct.class_eval do
+ remove_const name
+ end
end
def test_junk
@@ -359,6 +440,13 @@ module TestStruct
assert_equal({a:1, b:2, c:3, d:4, e:5, f:6}, o.to_h)
end
+ def test_to_h_block
+ klass = @Struct.new(:a, :b, :c, :d, :e, :f)
+ o = klass.new(1, 2, 3, 4, 5, 6)
+ assert_equal({"a" => 1, "b" => 4, "c" => 9, "d" => 16, "e" => 25, "f" => 36},
+ o.to_h {|k, v| [k.to_s, v*v]})
+ end
+
def test_question_mark_in_member
klass = @Struct.new(:a, :b?)
x = Object.new
@@ -390,13 +478,84 @@ module TestStruct
assert_nil(o.dig(:b, 0))
end
- def test_new_dupilicate
+ def test_new_duplicate
bug12291 = '[ruby-core:74971] [Bug #12291]'
assert_raise_with_message(ArgumentError, /duplicate member/, bug12291) {
@Struct.new(:a, :a)
}
end
+ def test_deconstruct_keys
+ klass = @Struct.new(:a, :b)
+ o = klass.new(1, 2)
+ assert_equal({a: 1, b: 2}, o.deconstruct_keys(nil))
+ assert_equal({a: 1, b: 2}, o.deconstruct_keys([:b, :a]))
+ assert_equal({a: 1}, o.deconstruct_keys([:a]))
+ assert_not_send([o.deconstruct_keys([:a, :c]), :key?, :c])
+ assert_raise(TypeError) {
+ o.deconstruct_keys(0)
+ }
+ end
+
+ def test_public_send
+ klass = @Struct.new(:a)
+ x = klass.new(1)
+ assert_equal(1, x.public_send("a"))
+ assert_equal(42, x.public_send("a=", 42))
+ assert_equal(42, x.public_send("a"))
+ end
+
+ def test_arity
+ klass = @Struct.new(:a)
+ assert_equal 0, klass.instance_method(:a).arity
+ assert_equal 1, klass.instance_method(:a=).arity
+
+ klass.module_eval do
+ define_method(:b=, instance_method(:a=))
+ alias c= a=
+ end
+
+ assert_equal 1, klass.instance_method(:b=).arity
+ assert_equal 1, klass.instance_method(:c=).arity
+ end
+
+ def test_parameters
+ klass = @Struct.new(:a)
+ assert_equal [], klass.instance_method(:a).parameters
+ # NOTE: :_ may not be a spec.
+ assert_equal [[:req, :_]], klass.instance_method(:a=).parameters
+
+ klass.module_eval do
+ define_method(:b=, instance_method(:a=))
+ alias c= a=
+ end
+
+ assert_equal [[:req, :_]], klass.instance_method(:b=).parameters
+ assert_equal [[:req, :_]], klass.instance_method(:c=).parameters
+ 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
+ Struct.new("A")
+ Struct.send(:remove_const, :A)
+ end
+
+ 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 cf7580ab00..25bad2242a 100644
--- a/test/ruby/test_super.rb
+++ b/test/ruby/test_super.rb
@@ -8,6 +8,7 @@ class TestSuper < Test::Unit::TestCase
def array(*a) a end
def optional(a = 0) a end
def keyword(**a) a end
+ def forward(*a) a end
end
class Single1 < Base
def single(*) super end
@@ -63,6 +64,16 @@ class TestSuper < Test::Unit::TestCase
[x, y]
end
end
+ class Forward < Base
+ def forward(...)
+ w = super()
+ x = super
+ y = super(...)
+ a = 1
+ z = super(a, ...)
+ [w, x, y, z]
+ end
+ end
def test_single1
assert_equal(1, Single1.new.single(1))
@@ -102,11 +113,11 @@ class TestSuper < Test::Unit::TestCase
def test_optional2
assert_raise(ArgumentError) do
# call Base#optional with 2 arguments; the 2nd arg is supplied
- assert_equal(9, Optional2.new.optional(9))
+ Optional2.new.optional(9)
end
assert_raise(ArgumentError) do
# call Base#optional with 2 arguments
- assert_equal(9, Optional2.new.optional(9, 2))
+ Optional2.new.optional(9, 2)
end
end
def test_optional3
@@ -133,6 +144,11 @@ class TestSuper < Test::Unit::TestCase
def test_keyword2
assert_equal([{foo: "changed1"}, {foo: "changed2"}], Keyword2.new.keyword)
end
+ def test_forwardable(...)
+ assert_equal([[],[],[],[1]], Forward.new.forward())
+ assert_equal([[],[1,2],[1,2],[1,1,2]], Forward.new.forward(1,2))
+ assert_equal([[],[:test],[:test],[1,:test]], Forward.new.forward(:test, ...))
+ end
class A
def tt(aa)
@@ -307,6 +323,29 @@ class TestSuper < Test::Unit::TestCase
end
end
+ def test_super_in_instance_eval_in_module
+ super_class = EnvUtil.labeled_class("Super\u{30af 30e9 30b9}") {
+ def foo
+ return [:super, self]
+ end
+ }
+ mod = EnvUtil.labeled_module("Mod\u{30af 30e9 30b9}") {
+ def foo
+ x = Object.new
+ x.instance_eval do
+ super()
+ end
+ end
+ }
+ sub_class = EnvUtil.labeled_class("Sub\u{30af 30e9 30b9}", super_class) {
+ include mod
+ }
+ obj = sub_class.new
+ assert_raise_with_message(TypeError, /Sub\u{30af 30e9 30b9}/) do
+ obj.foo
+ end
+ end
+
def test_super_in_orphan_block
super_class = EnvUtil.labeled_class("Super\u{30af 30e9 30b9}") {
def foo
@@ -498,6 +537,55 @@ class TestSuper < Test::Unit::TestCase
assert_equal(%w[B A], result, bug9721)
end
+ # [Bug #18329]
+ def test_super_missing_prepended_module
+ a = Module.new do
+ def probe(*methods)
+ prepend(probing_module(methods))
+ end
+
+ def probing_module(methods)
+ Module.new do
+ methods.each do |method|
+ define_method(method) do |*args, **kwargs, &block|
+ super(*args, **kwargs, &block)
+ end
+ end
+ end
+ end
+ end
+
+ b = Class.new do
+ extend a
+
+ probe :danger!, :missing
+
+ def danger!; end
+ end
+
+ o = b.new
+ o.danger!
+ begin
+ original_gc_stress = GC.stress
+ GC.stress = true
+ 2.times { o.missing rescue NoMethodError }
+ ensure
+ GC.stress = original_gc_stress
+ end
+ end
+
+ def test_zsuper_kw_splat_not_mutable
+ extend(Module.new{def a(**k) k[:a] = 1 end})
+ extend(Module.new do
+ def a(**k)
+ before = k.dup
+ super
+ [before, k]
+ end
+ end)
+ assert_equal(*a)
+ end
+
def test_from_eval
bug10263 = '[ruby-core:65122] [Bug #10263a]'
a = Class.new do
@@ -545,6 +633,40 @@ class TestSuper < Test::Unit::TestCase
}
end
+ def test_super_with_included_prepended_module_method_caching_bug_20716
+ a = Module.new do
+ def test(*args)
+ super
+ end
+ end
+
+ b = Module.new do
+ def test(a)
+ a
+ end
+ end
+
+ c = Class.new
+
+ b.prepend(a)
+ c.include(b)
+
+ assert_equal(1, c.new.test(1))
+
+ b.class_eval do
+ begin
+ verbose_bak, $VERBOSE = $VERBOSE, nil
+ def test
+ :test
+ end
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ end
+
+ assert_equal(:test, c.new.test)
+ end
+
class TestFor_super_with_modified_rest_parameter_base
def foo *args
args
@@ -560,4 +682,96 @@ class TestSuper < Test::Unit::TestCase
def test_super_with_modified_rest_parameter
assert_equal [13], TestFor_super_with_modified_rest_parameter.new.foo
end
+
+ def test_super_with_define_method
+ superklass1 = Class.new do
+ def foo; :foo; end
+ def bar; :bar; end
+ def boo; :boo; end
+ end
+ superklass2 = Class.new(superklass1) do
+ alias baz boo
+ def boo; :boo2; end
+ end
+ subklass = Class.new(superklass2)
+ [:foo, :bar, :baz, :boo].each do |sym|
+ subklass.define_method(sym){ super() }
+ end
+ assert_equal :foo, subklass.new.foo
+ assert_equal :bar, subklass.new.bar
+ assert_equal :boo, subklass.new.baz
+ assert_equal :boo2, subklass.new.boo
+ end
+
+ def test_super_attr_writer # [Bug #16785]
+ writer_class = Class.new do
+ attr_writer :test
+ end
+ superwriter_class = Class.new(writer_class) do
+ def initialize
+ @test = 1 # index: 1
+ end
+
+ def test=(test)
+ super(test)
+ end
+ end
+ inherited_class = Class.new(superwriter_class) do
+ def initialize
+ @a = nil
+ @test = 2 # index: 2
+ end
+ end
+
+ superwriter = superwriter_class.new
+ superwriter.test = 3 # set ic->index of superwriter_class#test= to 1
+
+ inherited = inherited_class.new
+ inherited.test = 4 # it may set 4 to index=1 while it should be index=2
+
+ assert_equal 3, superwriter.instance_variable_get(:@test)
+ assert_equal 4, inherited.instance_variable_get(:@test)
+ end
+
+ def test_super_attr_reader
+ reader_class = Class.new do
+ attr_reader :test
+ end
+ superreader_class = Class.new(reader_class) do
+ def initialize
+ @test = 1 # index: 1
+ end
+
+ def test
+ super
+ end
+ end
+ inherited_class = Class.new(superreader_class) do
+ def initialize
+ @a = nil
+ @test = 2 # index: 2
+ end
+ end
+
+ superreader = superreader_class.new
+ assert_equal 1, superreader.test # set ic->index of superreader_class#test to 1
+
+ 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_symbol.rb b/test/ruby/test_symbol.rb
index 36cbf4a710..c50febf5d1 100644
--- a/test/ruby/test_symbol.rb
+++ b/test/ruby/test_symbol.rb
@@ -36,6 +36,19 @@ class TestSymbol < Test::Unit::TestCase
assert_eval_inspected(:"@@1", false)
assert_eval_inspected(:"@", false)
assert_eval_inspected(:"@@", false)
+ assert_eval_inspected(:"[]=")
+ assert_eval_inspected(:"[][]", false)
+ assert_eval_inspected(:"[][]=", false)
+ assert_eval_inspected(:"@=", false)
+ assert_eval_inspected(:"@@=", false)
+ assert_eval_inspected(:"@x=", false)
+ assert_eval_inspected(:"@@x=", false)
+ assert_eval_inspected(:"$$=", false)
+ assert_eval_inspected(:"$==", false)
+ assert_eval_inspected(:"$x=", false)
+ assert_eval_inspected(:"$$$=", false)
+ assert_eval_inspected(:"foo?=", false)
+ assert_eval_inspected(:"foo!=", false)
end
def assert_inspect_evaled(n)
@@ -77,12 +90,15 @@ class TestSymbol < Test::Unit::TestCase
end
def test_inspect_dollar
+ verbose_bak, $VERBOSE = $VERBOSE, nil
# 4) :$- always treats next character literally:
assert_raise(SyntaxError) {eval ':$-'}
assert_raise(SyntaxError) {eval ":$-\n"}
assert_raise(SyntaxError) {eval ":$- "}
assert_raise(SyntaxError) {eval ":$-#"}
assert_raise(SyntaxError) {eval ':$-('}
+ ensure
+ $VERBOSE = verbose_bak
end
def test_inspect_number
@@ -105,6 +121,20 @@ class TestSymbol < Test::Unit::TestCase
end
end
+ def test_inspect_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+
+ EnvUtil.under_gc_compact_stress do
+ assert_inspect_evaled(':testing')
+ end
+ end
+
+ def test_name
+ assert_equal("foo", :foo.name)
+ assert_same(:foo.name, :foo.name)
+ assert_predicate(:foo.name, :frozen?)
+ end
+
def test_to_proc
assert_equal %w(1 2 3), (1..3).map(&:to_s)
[
@@ -153,6 +183,14 @@ class TestSymbol < Test::Unit::TestCase
end;
end
+ def test_to_proc_lambda?
+ assert_predicate(:itself.to_proc, :lambda?)
+ end
+
+ def test_to_proc_arity
+ assert_equal(-2, :itself.to_proc.arity)
+ end
+
def test_to_proc_call_with_symbol_proc
first = 1
bug11594 = "[ruby-core:71088] [Bug #11594] corrupted the first local variable"
@@ -161,6 +199,53 @@ class TestSymbol < Test::Unit::TestCase
assert_equal(1, first, bug11594)
end
+ class TestToPRocArgWithRefinements; end
+ def _test_to_proc_arg_with_refinements_call(&block)
+ block.call TestToPRocArgWithRefinements.new
+ end
+ def _test_to_proc_with_refinements_call(&block)
+ block
+ end
+ using Module.new {
+ refine TestToPRocArgWithRefinements do
+ def hoge
+ :hoge
+ end
+ end
+ }
+ def test_to_proc_arg_with_refinements
+ assert_equal(:hoge, _test_to_proc_arg_with_refinements_call(&:hoge))
+ end
+
+ def test_to_proc_lambda_with_refinements
+ assert_predicate(_test_to_proc_with_refinements_call(&:hoge), :lambda?)
+ end
+
+ def test_to_proc_arity_with_refinements
+ assert_equal(-2, _test_to_proc_with_refinements_call(&:hoge).arity)
+ end
+
+ def self._test_to_proc_arg_with_refinements_call(&block)
+ block.call TestToPRocArgWithRefinements.new
+ end
+ _test_to_proc_arg_with_refinements_call(&:hoge)
+ using Module.new {
+ refine TestToPRocArgWithRefinements do
+ def hoge
+ :hogehoge
+ end
+ end
+ }
+ def test_to_proc_arg_with_refinements_override
+ assert_equal(:hogehoge, _test_to_proc_arg_with_refinements_call(&:hoge))
+ end
+
+ def test_to_proc_arg_with_refinements_undefined
+ assert_raise(NoMethodError) do
+ _test_to_proc_arg_with_refinements_call(&:foo)
+ end
+ end
+
private def return_from_proc
Proc.new { return 1 }.tap(&:call)
end
@@ -183,11 +268,11 @@ class TestSymbol < Test::Unit::TestCase
begin;
bug11845 = '[ruby-core:72381] [Bug #11845]'
assert_nil(:class.to_proc.source_location, bug11845)
- assert_equal([[:rest]], :class.to_proc.parameters, bug11845)
+ assert_equal([[:req], [:rest]], :class.to_proc.parameters, bug11845)
c = Class.new {define_method(:klass, :class.to_proc)}
m = c.instance_method(:klass)
assert_nil(m.source_location, bug11845)
- assert_equal([[:rest]], m.parameters, bug11845)
+ assert_equal([[:req], [:rest]], m.parameters, bug11845)
end;
end
@@ -468,12 +553,14 @@ class TestSymbol < Test::Unit::TestCase
assert_nothing_raised(NoMethodError, bug10259) {obj.send("unagi=".intern, 1)}
end
- def test_symbol_fstr_leak
+ def test_symbol_fstr_memory_leak
bug10686 = '[ruby-core:67268] [Bug #10686]'
- x = x = 0
- assert_no_memory_leak([], '200_000.times { |i| i.to_s.to_sym }; GC.start', "#{<<-"begin;"}\n#{<<-"end;"}", bug10686, limit: 1.71, rss: true, timeout: 20)
+ assert_no_memory_leak([], "#{<<~"begin;"}\n#{<<~'else;'}", "#{<<~'end;'}", bug10686, limit: 1.71, rss: true, timeout: 20)
begin;
- 200_000.times { |i| (i + 200_000).to_s.to_sym }
+ n = 100_000
+ n.times { |i| i.to_s.to_sym }
+ else;
+ (2 * n).times { |i| (i + n).to_s.to_sym }
end;
end
@@ -497,14 +584,6 @@ class TestSymbol < Test::Unit::TestCase
end;
end
- def test_not_freeze
- bug11721 = '[ruby-core:71611] [Bug #11721]'
- str = "\u{1f363}".taint
- assert_not_predicate(str, :frozen?)
- assert_equal str, str.to_sym.to_s
- assert_not_predicate(str, :frozen?, bug11721)
- end
-
def test_hash_nondeterministic
ruby = EnvUtil.rubybin
assert_not_equal :foo.hash, `#{ruby} -e 'puts :foo.hash'`.to_i,
@@ -527,4 +606,27 @@ class TestSymbol < Test::Unit::TestCase
puts :a == :a
RUBY
end
+
+ def test_start_with?
+ assert_equal(true, :hello.start_with?("hel"))
+ assert_equal(false, :hello.start_with?("el"))
+ assert_equal(true, :hello.start_with?("el", "he"))
+
+ bug5536 = '[ruby-core:40623]'
+ assert_raise(TypeError, bug5536) {:str.start_with? :not_convertible_to_string}
+
+ assert_equal(true, :hello.start_with?(/hel/))
+ assert_equal("hel", $&)
+ assert_equal(false, :hello.start_with?(/el/))
+ assert_nil($&)
+ end
+
+ def test_end_with?
+ assert_equal(true, :hello.end_with?("llo"))
+ assert_equal(false, :hello.end_with?("ll"))
+ assert_equal(true, :hello.end_with?("el", "lo"))
+
+ bug5536 = '[ruby-core:40623]'
+ assert_raise(TypeError, bug5536) {:str.end_with? :not_convertible_to_string}
+ end
end
diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb
index f8d28a4f8e..b355128a73 100644
--- a/test/ruby/test_syntax.rb
+++ b/test/ruby/test_syntax.rb
@@ -13,8 +13,7 @@ class TestSyntax < Test::Unit::TestCase
def assert_syntax_files(test)
srcdir = File.expand_path("../../..", __FILE__)
srcdir = File.join(srcdir, test)
- assert_separately(%W[--disable-gem - #{srcdir}],
- __FILE__, __LINE__, <<-'eom', timeout: Float::INFINITY)
+ assert_separately(%W[- #{srcdir}], __FILE__, __LINE__, <<-'eom', timeout: Float::INFINITY)
dir = ARGV.shift
for script in Dir["#{dir}/**/*.rb"].sort
assert_valid_syntax(IO::read(script), script)
@@ -46,7 +45,7 @@ class TestSyntax < Test::Unit::TestCase
assert_raise(ArgumentError, enc.name) {load(f.path)}
end
ensure
- f.close! if f
+ f&.close!
end
def test_script_lines
@@ -63,7 +62,144 @@ class TestSyntax < Test::Unit::TestCase
end
end
ensure
- f.close! if f
+ f&.close!
+ end
+
+ def test_script_lines_encoding
+ require 'tmpdir'
+ Dir.mktmpdir do |dir|
+ File.write(File.join(dir, "script_lines.rb"), "SCRIPT_LINES__ = {}\n")
+ assert_in_out_err(%w"-r./script_lines -w -Ke", "puts __ENCODING__.name",
+ %w"EUC-JP", /-K is specified/, chdir: dir)
+ end
+ end
+
+ def test_anonymous_block_forwarding
+ assert_syntax_error("def b; c(&); end", /no anonymous block parameter/)
+ assert_syntax_error("def b(&) ->(&) {c(&)} end", /anonymous block parameter is also used/)
+ assert_valid_syntax("def b(&) ->() {c(&)} end")
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ def b(&); c(&) end
+ def c(&); yield 1 end
+ a = nil
+ b{|c| a = c}
+ assert_equal(1, a)
+
+ def inner
+ yield
+ end
+
+ def block_only(&)
+ inner(&)
+ end
+ assert_equal(1, block_only{1})
+
+ def pos(arg1, &)
+ inner(&)
+ end
+ assert_equal(2, pos(nil){2})
+
+ def pos_kwrest(arg1, **kw, &)
+ inner(&)
+ end
+ assert_equal(3, pos_kwrest(nil){3})
+
+ def no_kw(arg1, **nil, &)
+ inner(&)
+ end
+ assert_equal(4, no_kw(nil){4})
+
+ def rest_kw(*a, kwarg: 1, &)
+ inner(&)
+ end
+ assert_equal(5, rest_kw{5})
+
+ def kw(kwarg:1, &)
+ inner(&)
+ end
+ assert_equal(6, kw{6})
+
+ def pos_kw_kwrest(arg1, kwarg:1, **kw, &)
+ inner(&)
+ end
+ assert_equal(7, pos_kw_kwrest(nil){7})
+
+ def pos_rkw(arg1, kwarg1:, &)
+ inner(&)
+ end
+ assert_equal(8, pos_rkw(nil, kwarg1: nil){8})
+
+ def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &)
+ inner(&)
+ end
+ assert_equal(9, all(nil, nil, nil, nil, okw1: nil, okw2: nil){9})
+
+ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &)
+ inner(&)
+ end
+ assert_equal(10, all_kwrest(nil, nil, nil, nil, okw1: nil, okw2: nil){10})
+
+ def evaled(&)
+ eval("inner(&)")
+ end
+ assert_equal(1, evaled{1})
+ end;
+ end
+
+ def test_anonymous_rest_forwarding
+ assert_syntax_error("def b; c(*); end", /no anonymous rest parameter/)
+ assert_syntax_error("def b; c(1, *); end", /no anonymous rest parameter/)
+ assert_syntax_error("def b(*) ->(*) {c(*)} end", /anonymous rest parameter is also used/)
+ assert_syntax_error("def b(a, *) ->(*) {c(1, *)} end", /anonymous rest parameter is also used/)
+ assert_syntax_error("def b(*) ->(a, *) {c(*)} end", /anonymous rest parameter is also used/)
+ assert_valid_syntax("def b(*) ->() {c(*)} end")
+ assert_valid_syntax("def b(a, *) ->() {c(1, *)} end")
+ assert_valid_syntax("def b(*) ->(a) {c(*)} end")
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ def b(*); c(*) end
+ def c(*a); a end
+ def d(*); b(*, *) end
+ def e(*); eval("b(*)") end
+ assert_equal([1, 2], b(1, 2))
+ assert_equal([1, 2, 1, 2], d(1, 2))
+ assert_equal([1, 2], e(1, 2))
+ end;
+ end
+
+ def test_anonymous_keyword_rest_forwarding
+ assert_syntax_error("def b; c(**); end", /no anonymous keyword rest parameter/)
+ assert_syntax_error("def b; c(k: 1, **); end", /no anonymous keyword rest parameter/)
+ assert_syntax_error("def b(**) ->(**) {c(**)} end", /anonymous keyword rest parameter is also used/)
+ assert_syntax_error("def b(k:, **) ->(**) {c(k: 1, **)} end", /anonymous keyword rest parameter is also used/)
+ assert_syntax_error("def b(**) ->(k:, **) {c(**)} end", /anonymous keyword rest parameter is also used/)
+ assert_valid_syntax("def b(**) ->() {c(**)} end")
+ assert_valid_syntax("def b(k:, **) ->() {c(k: 1, **)} end")
+ assert_valid_syntax("def b(**) ->(k:) {c(**)} end")
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ def b(**); c(**) end
+ def c(**kw); kw end
+ def d(**); b(k: 1, **) end
+ def e(**); b(**, k: 1) end
+ def f(a: nil, **); b(**) end
+ def g(**); eval("b(**)") end
+ assert_equal({a: 1, k: 3}, b(a: 1, k: 3))
+ assert_equal({a: 1, k: 3}, d(a: 1, k: 3))
+ assert_equal({a: 1, k: 1}, e(a: 1, k: 3))
+ assert_equal({k: 3}, f(a: 1, k: 3))
+ assert_equal({a: 1, k: 3}, g(a: 1, k: 3))
+ end;
+ end
+
+ def test_argument_forwarding_with_anon_rest_kwrest_and_block
+ assert_syntax_error("def f(*, **, &); g(...); end", /unexpected \.\.\./)
+ assert_syntax_error("def f(...); g(*); end", /no anonymous rest parameter/)
+ assert_syntax_error("def f(...); g(0, *); end", /no anonymous rest parameter/)
+ assert_syntax_error("def f(...); g(**); end", /no anonymous keyword rest parameter/)
+ assert_syntax_error("def f(...); g(x: 1, **); end", /no anonymous keyword rest parameter/)
+ assert_syntax_error("def f(...); g(&); end", /no anonymous block parameter/)
end
def test_newline_in_block_parameters
@@ -81,7 +217,7 @@ class TestSyntax < Test::Unit::TestCase
blocks = [['do end', 'do'], ['{}', 'brace']],
*|
[%w'. dot', %w':: colon'].product(methods, blocks) do |(c, n1), (m, n2), (b, n3)|
- m = m.tr_s('()', ' ').strip if n2 == 'do'
+ m = m.tr_s('()', ' ').strip if n3 == 'do'
name = "test_#{n3}_block_after_blockcall_#{n1}_#{n2}_arg"
code = "#{blockcall}#{c}#{m} #{b}"
define_method(name) {assert_valid_syntax(code, bug6115)}
@@ -93,6 +229,66 @@ class TestSyntax < Test::Unit::TestCase
assert_valid_syntax("tap (proc do end)", __FILE__, bug9726)
end
+ def test_hash_kwsplat_hash
+ kw = {}
+ h = {a: 1}
+ assert_equal({}, {**{}})
+ assert_equal({}, {**kw})
+ assert_equal(h, {**h})
+ assert_equal(false, {**{}}.frozen?)
+ assert_equal(false, {**kw}.equal?(kw))
+ assert_equal(false, {**h}.equal?(h))
+ end
+
+ def test_array_kwsplat_hash
+ kw = {}
+ h = {a: 1}
+ a = []
+ assert_equal([], [**{}])
+ assert_equal([], [**kw])
+ assert_equal([h], [**h])
+ assert_equal([{}], [{}])
+ assert_equal([kw], [kw])
+ assert_equal([h], [h])
+
+ assert_equal([1], [1, **{}])
+ assert_equal([1], [1, **kw])
+ assert_equal([1, h], [1, **h])
+ assert_equal([1, {}], [1, {}])
+ assert_equal([1, kw], [1, kw])
+ assert_equal([1, h], [1, h])
+
+ assert_equal([], [*a, **{}])
+ assert_equal([], [*a, **kw])
+ assert_equal([h], [*a, **h])
+ assert_equal([{}], [*a, {}])
+ assert_equal([kw], [*a, kw])
+ assert_equal([h], [*a, h])
+
+ assert_equal([1], [1, *a, **{}])
+ assert_equal([1], [1, *a, **kw])
+ assert_equal([1, h], [1, *a, **h])
+ assert_equal([1, {}], [1, *a, {}])
+ assert_equal([1, kw], [1, *a, kw])
+ assert_equal([1, h], [1, *a, h])
+
+ assert_equal([], [**kw, **kw])
+ assert_equal([], [**kw, **{}, **kw])
+ assert_equal([1], [1, **kw, **{}, **kw])
+
+ assert_equal([{}], [{}, **kw, **kw])
+ assert_equal([kw], [kw, **kw, **kw])
+ assert_equal([h], [h, **kw, **kw])
+ assert_equal([h, h], [h, **kw, **kw, **h])
+
+ assert_equal([h, {:a=>2}], [h, **{}, **h, a: 2])
+ assert_equal([h, h], [h, **{}, a: 2, **h])
+ assert_equal([h, h], [h, a: 2, **{}, **h])
+ assert_equal([h, h], [h, a: 2, **h, **{}])
+ assert_equal([h, {:a=>2}], [h, **h, a: 2, **{}])
+ assert_equal([h, {:a=>2}], [h, **h, **{}, a: 2])
+ end
+
def test_normal_argument
assert_valid_syntax('def foo(x) end')
assert_syntax_error('def foo(X) end', /constant/)
@@ -143,29 +339,52 @@ class TestSyntax < Test::Unit::TestCase
bug10315 = '[ruby-core:65368] [Bug #10315]'
o = KW2.new
- assert_equal([23, 2], o.kw(**{k1: 22}, **{k1: 23}), bug10315)
+ begin
+ verbose_bak, $VERBOSE = $VERBOSE, nil
+ assert_equal([23, 2], eval("o.kw(**{k1: 22}, **{k1: 23})"), bug10315)
+ ensure
+ $VERBOSE = verbose_bak
+ end
h = {k3: 31}
assert_raise(ArgumentError) {o.kw(**h)}
h = {"k1"=>11, k2: 12}
- assert_raise(TypeError) {o.kw(**h)}
+ assert_raise(ArgumentError) {o.kw(**h)}
end
def test_keyword_duplicated
bug10315 = '[ruby-core:65625] [Bug #10315]'
a = []
def a.add(x) push(x); x; end
- def a.f(k:) k; end
+ b = a.clone
+ def a.f(k:, **) k; end
+ def b.f(k:) k; end
a.clear
r = nil
- assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), k: a.add(2))")}
+ assert_warn(/duplicated/) {r = eval("b.f(k: b.add(1), k: b.add(2))")}
assert_equal(2, r)
- assert_equal([1, 2], a, bug10315)
+ assert_equal([1, 2], b, bug10315)
+ b.clear
+ r = nil
+ assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), j: a.add(2), k: a.add(3), k: a.add(4))")}
+ assert_equal(4, r)
+ assert_equal([1, 2, 3, 4], a)
a.clear
r = nil
- assert_warn(/duplicated/) {r = eval("a.f({k: a.add(1), k: a.add(2)})")}
+ assert_warn(/duplicated/) {r = eval("b.f(**{k: b.add(1), k: b.add(2)})")}
assert_equal(2, r)
- assert_equal([1, 2], a, bug10315)
+ assert_equal([1, 2], b, bug10315)
+ b.clear
+ r = nil
+ assert_warn(/duplicated/) {r = eval("a.f(**{k: a.add(1), j: a.add(2), k: a.add(3), k: a.add(4)})")}
+ assert_equal(4, r)
+ assert_equal([1, 2, 3, 4], a)
+ a.clear
+ r = nil
+ _z = {}
+ assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), **_z, k: a.add(2))")}
+ assert_equal(2, r)
+ assert_equal([1, 2], a)
end
def test_keyword_empty_splat
@@ -187,33 +406,25 @@ class TestSyntax < Test::Unit::TestCase
end
def test_keyword_self_reference
- bug9593 = '[ruby-core:61299] [Bug #9593]'
- o = Object.new
- assert_warn(/circular argument reference - var/) do
- o.instance_eval("def foo(var: defined?(var)) var end")
- end
- assert_equal(42, o.foo(var: 42))
- assert_equal("local-variable", o.foo, bug9593)
+ assert_valid_syntax("def foo(var: defined?(var)) var end")
+ assert_valid_syntax("def foo(var: var) var end")
+ assert_valid_syntax("def foo(var: bar(var)) var end")
+ assert_valid_syntax("def foo(var: bar {var}) var end")
+ assert_valid_syntax("def foo(var: (1 in ^var)); end")
o = Object.new
- assert_warn(/circular argument reference - var/) do
- o.instance_eval("def foo(var: var) var end")
- end
- assert_nil(o.foo, bug9593)
-
- o = Object.new
- assert_warn(/circular argument reference - var/) do
- o.instance_eval("def foo(var: bar(var)) var end")
+ assert_warn("") do
+ o.instance_eval("def foo(var: bar {|var| var}) var end")
end
o = Object.new
- assert_warn(/circular argument reference - var/) do
- o.instance_eval("def foo(var: bar {var}) var end")
+ assert_warn("") do
+ o.instance_eval("def foo(var: bar {| | var}) var end")
end
o = Object.new
assert_warn("") do
- o.instance_eval("def foo(var: bar {|var| var}) var end")
+ o.instance_eval("def foo(var: bar {|| var}) var end")
end
o = Object.new
@@ -225,61 +436,63 @@ class TestSyntax < Test::Unit::TestCase
assert_warn("") do
o.instance_eval("proc {|var: 1| var}")
end
+
+ o = Object.new
+ assert_nil(o.instance_eval("def foo(bar: bar) = bar; foo"))
end
def test_keyword_invalid_name
bug11663 = '[ruby-core:71356] [Bug #11663]'
- o = o = Object.new
- assert_syntax_error('def o.foo(arg1?:) end', /arg1\?/, bug11663)
- assert_syntax_error('def o.foo(arg1?:, arg2:) end', /arg1\?/, bug11663)
+ assert_syntax_error('def foo(arg1?:) end', /arg1\?/, bug11663)
+ assert_syntax_error('def foo(arg1?:, arg2:) end', /arg1\?/, bug11663)
assert_syntax_error('proc {|arg1?:|}', /arg1\?/, bug11663)
assert_syntax_error('proc {|arg1?:, arg2:|}', /arg1\?/, bug11663)
bug10545 = '[ruby-dev:48742] [Bug #10545]'
- assert_syntax_error('def o.foo(FOO: a) end', /constant/, bug10545)
- assert_syntax_error('def o.foo(@foo: a) end', /instance variable/)
- assert_syntax_error('def o.foo(@@foo: a) end', /class variable/)
+ assert_syntax_error('def foo(FOO: a) end', /constant/, bug10545)
+ assert_syntax_error('def foo(@foo: a) end', /instance variable/)
+ assert_syntax_error('def foo(@@foo: a) end', /class variable/)
end
- def test_optional_self_reference
- bug9593 = '[ruby-core:61299] [Bug #9593]'
- o = Object.new
- assert_warn(/circular argument reference - var/) do
- o.instance_eval("def foo(var = defined?(var)) var end")
- end
- assert_equal(42, o.foo(42))
- assert_equal("local-variable", o.foo, bug9593)
+ def test_keywords_specified_and_not_accepted
+ assert_syntax_error('def foo(a:, **nil) end', /unexpected/)
+ assert_syntax_error('def foo(a:, **nil, &b) end', /unexpected/)
+ assert_syntax_error('def foo(**a, **nil) end', /unexpected/)
+ assert_syntax_error('def foo(**a, **nil, &b) end', /unexpected/)
+ assert_syntax_error('def foo(**nil, **a) end', /unexpected/)
+ assert_syntax_error('def foo(**nil, **a, &b) end', /unexpected/)
- o = Object.new
- assert_warn(/circular argument reference - var/) do
- o.instance_eval("def foo(var = var) var end")
- end
- assert_nil(o.foo, bug9593)
-
- o = Object.new
- assert_warn(/circular argument reference - var/) do
- o.instance_eval("def foo(var = bar(var)) var end")
- end
+ assert_syntax_error('proc do |a:, **nil| end', /unexpected/)
+ assert_syntax_error('proc do |a:, **nil, &b| end', /unexpected/)
+ assert_syntax_error('proc do |**a, **nil| end', /unexpected/)
+ assert_syntax_error('proc do |**a, **nil, &b| end', /unexpected/)
+ assert_syntax_error('proc do |**nil, **a| end', /unexpected/)
+ assert_syntax_error('proc do |**nil, **a, &b| end', /unexpected/)
+ end
- o = Object.new
- assert_warn(/circular argument reference - var/) do
- o.instance_eval("def foo(var = bar {var}) var end")
- end
+ def test_optional_self_reference
+ assert_valid_syntax("def foo(var = defined?(var)) var end")
+ assert_valid_syntax("def foo(var = var) var end")
+ assert_valid_syntax("def foo(var = bar(var)) var end")
+ assert_valid_syntax("def foo(var = bar {var}) var end")
+ assert_valid_syntax("def foo(var = (def bar;end; var)) var end")
+ assert_valid_syntax("def foo(var = (def self.bar;end; var)) var end")
+ assert_valid_syntax("def foo(var = (1 in ^var)); end")
o = Object.new
- assert_warn(/circular argument reference - var/) do
- o.instance_eval("def foo(var = (def bar;end; var)) var end")
+ assert_warn("") do
+ o.instance_eval("def foo(var = bar {|var| var}) var end")
end
o = Object.new
- assert_warn(/circular argument reference - var/) do
- o.instance_eval("def foo(var = (def self.bar;end; var)) var end")
+ assert_warn("") do
+ o.instance_eval("def foo(var = bar {| | var}) var end")
end
o = Object.new
assert_warn("") do
- o.instance_eval("def foo(var = bar {|var| var}) var end")
+ o.instance_eval("def foo(var = bar {|| var}) var end")
end
o = Object.new
@@ -291,6 +504,9 @@ class TestSyntax < Test::Unit::TestCase
assert_warn("") do
o.instance_eval("proc {|var = 1| var}")
end
+
+ o = Object.new
+ assert_nil(o.instance_eval("def foo(bar: bar) = bar; foo"))
end
def test_warn_grouped_expression
@@ -308,10 +524,6 @@ class TestSyntax < Test::Unit::TestCase
end
def test_warn_balanced
- warning = <<WARN
-test:1: warning: `%s' after local variable or literal is interpreted as binary operator
-test:1: warning: even though it seems like %s
-WARN
[
[:**, "argument prefix"],
[:*, "argument prefix"],
@@ -325,7 +537,9 @@ WARN
all_assertions do |a|
["puts 1 #{op}0", "puts :a #{op}0", "m = 1; puts m #{op}0"].each do |src|
a.for(src) do
- assert_warning(warning % [op, syn], src) do
+ warning = /'#{Regexp.escape(op)}' after local variable or literal is interpreted as binary operator.+?even though it seems like #{syn}/m
+
+ assert_warning(warning, src) do
assert_valid_syntax(src, "test", verbose: true)
end
end
@@ -406,21 +620,30 @@ WARN
def test_duplicated_arg
assert_syntax_error("def foo(a, a) end", /duplicated argument name/)
assert_valid_syntax("def foo(_, _) end")
+ (obj = Object.new).instance_eval("def foo(_, x, _) x end")
+ assert_equal(2, obj.foo(1, 2, 3))
end
def test_duplicated_rest
assert_syntax_error("def foo(a, *a) end", /duplicated argument name/)
assert_valid_syntax("def foo(_, *_) end")
+ (obj = Object.new).instance_eval("def foo(_, x, *_) x end")
+ assert_equal(2, obj.foo(1, 2, 3))
end
def test_duplicated_opt
assert_syntax_error("def foo(a, a=1) end", /duplicated argument name/)
assert_valid_syntax("def foo(_, _=1) end")
+ (obj = Object.new).instance_eval("def foo(_, x, _=42) x end")
+ assert_equal(2, obj.foo(1, 2))
end
def test_duplicated_opt_rest
assert_syntax_error("def foo(a=1, *a) end", /duplicated argument name/)
assert_valid_syntax("def foo(_=1, *_) end")
+ (obj = Object.new).instance_eval("def foo(_, x=42, *_) x end")
+ assert_equal(42, obj.foo(1))
+ assert_equal(2, obj.foo(1, 2))
end
def test_duplicated_rest_opt
@@ -429,46 +652,87 @@ WARN
def test_duplicated_rest_post
assert_syntax_error("def foo(*a, a) end", /duplicated argument name/)
+ assert_valid_syntax("def foo(*_, _) end")
+ (obj = Object.new).instance_eval("def foo(*_, x, _) x end")
+ assert_equal(2, obj.foo(1, 2, 3))
+ assert_equal(2, obj.foo(2, 3))
+ (obj = Object.new).instance_eval("def foo(*_, _, x) x end")
+ assert_equal(3, obj.foo(1, 2, 3))
+ assert_equal(3, obj.foo(2, 3))
end
def test_duplicated_opt_post
assert_syntax_error("def foo(a=1, a) end", /duplicated argument name/)
assert_valid_syntax("def foo(_=1, _) end")
+ (obj = Object.new).instance_eval("def foo(_=1, x, _) x end")
+ assert_equal(2, obj.foo(1, 2, 3))
+ assert_equal(2, obj.foo(2, 3))
+ (obj = Object.new).instance_eval("def foo(_=1, _, x) x end")
+ assert_equal(3, obj.foo(1, 2, 3))
+ assert_equal(3, obj.foo(2, 3))
end
def test_duplicated_kw
assert_syntax_error("def foo(a, a: 1) end", /duplicated argument name/)
assert_valid_syntax("def foo(_, _: 1) end")
+ (obj = Object.new).instance_eval("def foo(_, x, _: 1) x end")
+ assert_equal(3, obj.foo(2, 3))
+ assert_equal(3, obj.foo(2, 3, _: 42))
+ (obj = Object.new).instance_eval("def foo(x, _, _: 1) x end")
+ assert_equal(2, obj.foo(2, 3))
+ assert_equal(2, obj.foo(2, 3, _: 42))
end
def test_duplicated_rest_kw
assert_syntax_error("def foo(*a, a: 1) end", /duplicated argument name/)
assert_nothing_raised {def foo(*_, _: 1) end}
+ (obj = Object.new).instance_eval("def foo(*_, x: 42, _: 1) x end")
+ assert_equal(42, obj.foo(42))
+ assert_equal(42, obj.foo(2, _: 0))
+ assert_equal(2, obj.foo(x: 2, _: 0))
+ ensure
+ self.class.remove_method(:foo)
end
def test_duplicated_opt_kw
assert_syntax_error("def foo(a=1, a: 1) end", /duplicated argument name/)
assert_valid_syntax("def foo(_=1, _: 1) end")
+ (obj = Object.new).instance_eval("def foo(_=42, x, _: 1) x end")
+ assert_equal(0, obj.foo(0))
+ assert_equal(0, obj.foo(0, _: 3))
end
def test_duplicated_kw_kwrest
assert_syntax_error("def foo(a: 1, **a) end", /duplicated argument name/)
assert_valid_syntax("def foo(_: 1, **_) end")
+ (obj = Object.new).instance_eval("def foo(_: 1, x: 42, **_) x end")
+ assert_equal(42, obj.foo())
+ assert_equal(42, obj.foo(a: 0))
+ assert_equal(42, obj.foo(_: 0, a: 0))
+ assert_equal(1, obj.foo(_: 0, x: 1, a: 0))
end
def test_duplicated_rest_kwrest
assert_syntax_error("def foo(*a, **a) end", /duplicated argument name/)
assert_valid_syntax("def foo(*_, **_) end")
+ (obj = Object.new).instance_eval("def foo(*_, x, **_) x end")
+ assert_equal(1, obj.foo(1))
+ assert_equal(1, obj.foo(1, a: 0))
+ assert_equal(2, obj.foo(1, 2, a: 0))
end
def test_duplicated_opt_kwrest
assert_syntax_error("def foo(a=1, **a) end", /duplicated argument name/)
assert_valid_syntax("def foo(_=1, **_) end")
+ (obj = Object.new).instance_eval("def foo(_=42, x, **_) x end")
+ assert_equal(1, obj.foo(1))
+ assert_equal(1, obj.foo(1, a: 0))
+ assert_equal(1, obj.foo(0, 1, a: 0))
end
def test_duplicated_when
- w = 'warning: duplicated when clause is ignored'
- assert_warning(/3: #{w}.+4: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m){
+ w = ->(line) { "warning: 'when' clause on line #{line} duplicates 'when' clause on line 3 and is ignored" }
+ assert_warning(/#{w[3]}.+#{w[4]}.+#{w[4]}.+#{w[5]}.+#{w[5]}/m) {
eval %q{
case 1
when 1, 1
@@ -477,7 +741,7 @@ WARN
end
}
}
- assert_warning(/#{w}/){#/3: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m){
+ assert_warning(/#{w[3]}.+#{w[4]}.+#{w[5]}.+#{w[5]}/m) {
a = a = 1
eval %q{
case 1
@@ -487,6 +751,35 @@ WARN
end
}
}
+ assert_warning(/#{w[3]}/) {
+ eval %q{
+ case 1
+ when __LINE__, __LINE__
+ when 3, 3
+ when 3, 3
+ end
+ }
+ }
+ assert_warning(/#{w[3]}/) {
+ eval %q{
+ case 1
+ when __FILE__, __FILE__
+ when "filename", "filename"
+ when "filename", "filename"
+ end
+ }, binding, "filename"
+ }
+ end
+
+ def test_duplicated_when_check_option
+ w = /'when' clause on line 4 duplicates 'when' clause on line 3 and is ignored/
+ assert_in_out_err(%[-wc], "#{<<~"begin;"}\n#{<<~'end;'}", ["Syntax OK"], w)
+ begin;
+ case 1
+ when 1
+ when 1
+ end
+ end;
end
def test_invalid_break
@@ -524,6 +817,11 @@ WARN
def test_do_block_after_lambda
bug11380 = '[ruby-core:70067] [Bug #11380]'
assert_valid_syntax('p -> { :hello }, a: 1 do end', bug11380)
+
+ assert_valid_syntax('->(opt = (foo.[] bar)) {}')
+ assert_valid_syntax('->(opt = (foo.[]= bar)) {}')
+ assert_valid_syntax('->(opt = (foo.[] bar)) do end')
+ assert_valid_syntax('->(opt = (foo.[]= bar)) do end')
end
def test_reserved_method_no_args
@@ -534,7 +832,7 @@ WARN
def test_unassignable
gvar = global_variables
%w[self nil true false __FILE__ __LINE__ __ENCODING__].each do |kwd|
- assert_raise(SyntaxError) {eval("#{kwd} = nil")}
+ assert_syntax_error("#{kwd} = nil", /Can't .* #{kwd}$/)
assert_equal(gvar, global_variables)
end
end
@@ -607,6 +905,16 @@ e"
assert_dedented_heredoc(expect, result)
end
+ def test_dedented_heredoc_with_leading_blank_line
+ # the blank line has six leading spaces
+ result = " \n" \
+ " b\n"
+ expect = " \n" \
+ "b\n"
+ assert_dedented_heredoc(expect, result)
+ end
+
+
def test_dedented_heredoc_with_blank_more_indented_line_escaped
result = " a\n" \
"\\ \\ \\ \\ \\ \\ \n" \
@@ -694,6 +1002,8 @@ e"
\
TEXT
end;
+
+ assert_equal(" TEXT\n", eval("<<~eos\n" " \\\n" "TEXT\n" "eos\n"))
end
def test_lineno_after_heredoc
@@ -711,33 +1021,52 @@ eom
assert_syntax_error('<<~ "#{}"', /unexpected <</)
end
+ def test_dedented_heredoc_concatenation
+ assert_equal(" \n0\n1", eval("<<~0 '1'\n \n0\#{}\n0"))
+ end
+
def test_heredoc_mixed_encoding
- assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source')
+ e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source')
#encoding: cp932
<<-TEXT
\xe9\x9d\u1234
TEXT
HEREDOC
- assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source')
+ assert_not_match(/end-of-input/, e.message)
+
+ e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source')
#encoding: cp932
<<-TEXT
\xe9\x9d
\u1234
TEXT
HEREDOC
- assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source')
+ assert_not_match(/end-of-input/, e.message)
+
+ e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source')
#encoding: cp932
<<-TEXT
\u1234\xe9\x9d
TEXT
HEREDOC
- assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source')
+ assert_not_match(/end-of-input/, e.message)
+
+ e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source')
#encoding: cp932
<<-TEXT
\u1234
\xe9\x9d
TEXT
HEREDOC
+ assert_not_match(/end-of-input/, e.message)
+ end
+
+ def test_invalid_regexp
+ bug20295 = '[ruby-core:116913] [Bug #20295]'
+
+ assert_syntax_error("/[/=~s", /premature end of char-class/, bug20295)
+ assert_syntax_error("/(?<>)/=~s", /group name is empty/, bug20295)
+ assert_syntax_error("/(?<a>[)/=~s", /premature end of char-class/, bug20295)
end
def test_lineno_operation_brace_block
@@ -752,7 +1081,7 @@ eom
["p ", ""], # no-pop
["", "p Foo::Bar"], # pop
].each do |p1, p2|
- src = <<-EOM.gsub(/^\s*\n/, '')
+ src = <<~EOM
class Foo
#{"Bar = " + preset if preset}
end
@@ -784,7 +1113,7 @@ eom
["p ", ""], # no-pop
["", "p ::Bar"], # pop
].each do |p1, p2|
- src = <<-EOM.gsub(/^\s*\n/, '')
+ src = <<~EOM
#{"Bar = " + preset if preset}
class Foo
#{p1}::Bar #{op}= 42
@@ -829,9 +1158,20 @@ eom
assert_syntax_error("puts <<""EOS\n""ng\n""EOS\r""NO\n", /can't find string "EOS" anywhere before EOF/)
end
- def test_heredoc_newline
- assert_warn(/ends with a newline/) do
- eval("<<\"EOS\n\"\nEOS\n")
+ def test_heredoc_no_terminator
+ assert_syntax_error("puts <<""A\n", /can't find string "A" anywhere before EOF/)
+ assert_syntax_error("puts <<""A + <<""B\n", /can't find string "A" anywhere before EOF/)
+ assert_syntax_error("puts <<""A + <<""B\n", /can't find string "B" anywhere before EOF/)
+ end
+
+ def test_unterminated_heredoc
+ assert_syntax_error("<<\"EOS\n\nEOS\n", /unterminated/)
+ assert_syntax_error("<<\"EOS\n\"\nEOS\n", /unterminated/)
+ end
+
+ def test_unterminated_heredoc_cr
+ %W[\r\n \n].each do |nl|
+ assert_syntax_error("<<\"\r\"#{nl}\r#{nl}", /unterminated/, nil, "CR with #{nl.inspect}")
end
end
@@ -841,9 +1181,14 @@ eom
def test_warning_for_cr
feature8699 = '[ruby-core:56240] [Feature #8699]'
- assert_warning(/encountered \\r/, feature8699) do
- eval("\r""__id__\r")
+ s = assert_warning(/encountered \\r/, feature8699) do
+ eval("'\r'\r")
+ end
+ assert_equal("\r", s)
+ s = assert_warning('') do
+ eval("'\r'\r\n")
end
+ assert_equal("\r", s)
end
def test_unexpected_fraction
@@ -865,8 +1210,28 @@ eom
bug10957 = '[ruby-core:68477] [Bug #10957]'
assert_ruby_status(['-c', '-e', 'p ()..0'], "", bug10957)
assert_ruby_status(['-c', '-e', 'p ()...0'], "", bug10957)
- assert_syntax_error('0..%w.', /unterminated string/, bug10957)
- assert_syntax_error('0...%w.', /unterminated string/, bug10957)
+ assert_syntax_error('0..%q.', /unterminated string/, bug10957)
+ assert_syntax_error('0...%q.', /unterminated string/, bug10957)
+ end
+
+ def test_range_at_eol
+ assert_warn(/\.\.\. at EOL/) {eval("1...\n2")}
+ assert_warn('') {eval("(1...)")}
+ assert_warn('') {eval("(1...\n2)")}
+ assert_warn('') {eval("{a: 1...\n2}")}
+
+ assert_warn(/\.\.\. at EOL/) do
+ assert_valid_syntax('foo.[]= ...', verbose: true)
+ end
+ assert_warn(/\.\.\. at EOL/) do
+ assert_valid_syntax('foo.[] ...', verbose: true)
+ end
+ assert_warn(/\.\.\. at EOL/) do
+ assert_syntax_error('foo.[]= bar, ...', /unexpected/, verbose: true)
+ end
+ assert_warn(/\.\.\. at EOL/) do
+ assert_syntax_error('foo.[] bar, ...', /unexpected/, verbose: true)
+ end
end
def test_too_big_nth_ref
@@ -882,9 +1247,81 @@ eom
assert_syntax_error(":#\n foo", /unexpected ':'/)
end
+ def test_invalid_literal_message
+ assert_syntax_error("def :foo", /unexpected symbol literal/)
+ assert_syntax_error("def 'foo'", /unexpected string literal/)
+ end
+
def test_fluent_dot
assert_valid_syntax("a\n.foo")
assert_valid_syntax("a\n&.foo")
+ assert_valid_syntax("a #\n#\n.foo\n")
+ 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/)
+ end
+
+ def test_safe_call_in_for_variable
+ assert_valid_syntax("for x&.bar in []; end")
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ foo = nil
+ for foo&.bar in [1]; end
+ assert_nil(foo)
+
+ foo = Struct.new(:bar).new
+ for foo&.bar in [1]; end
+ assert_equal(1, foo.bar)
+ end;
end
def test_no_warning_logop_literal
@@ -900,22 +1337,25 @@ eom
end
def test_warning_literal_in_condition
- assert_warn(/literal in condition/) do
+ assert_warn(/string literal in condition/) do
eval('1 if ""')
end
- assert_warn(/literal in condition/) do
- eval('1 if //')
+ assert_warning(/string literal in condition/) do
+ eval('1 if __FILE__')
end
- assert_warn(/literal in condition/) do
- eval('1 if true..false')
+ assert_warn(/regex literal in condition/) do
+ eval('1 if //')
end
assert_warning(/literal in condition/) do
eval('1 if 1')
end
assert_warning(/literal in condition/) do
+ eval('1 if __LINE__')
+ end
+ assert_warning(/symbol literal in condition/) do
eval('1 if :foo')
end
- assert_warning(/literal in condition/) do
+ assert_warning(/symbol literal in condition/) do
eval('1 if :"#{"foo".upcase}"')
end
@@ -939,6 +1379,27 @@ eom
end
end
+ def test_warning_literal_in_flip_flop
+ assert_warn(/literal in flip-flop/) do
+ eval('1 if ""..false')
+ end
+ assert_warning(/literal in flip-flop/) do
+ eval('1 if :foo..false')
+ end
+ assert_warning(/literal in flip-flop/) do
+ eval('1 if :"#{"foo".upcase}"..false')
+ end
+ assert_warn(/literal in flip-flop/) do
+ eval('1 if ""...false')
+ end
+ assert_warning(/literal in flip-flop/) do
+ eval('1 if :foo...false')
+ end
+ assert_warning(/literal in flip-flop/) do
+ eval('1 if :"#{"foo".upcase}"...false')
+ end
+ end
+
def test_alias_symbol
bug8851 = '[ruby-dev:47681] [Bug #8851]'
formats = ['%s', ":'%s'", ':"%s"', '%%s(%s)']
@@ -964,7 +1425,7 @@ eom
end
def test_parenthesised_statement_argument
- assert_syntax_error("foo(bar rescue nil)", /unexpected modifier_rescue/)
+ assert_syntax_error("foo(bar rescue nil)", /unexpected 'rescue' modifier/)
assert_valid_syntax("foo (bar rescue nil)")
end
@@ -986,7 +1447,7 @@ eom
def test_block_after_cmdarg_in_paren
bug11873 = '[ruby-core:72482] [Bug #11873]'
- def bug11873.p(*);end;
+ def bug11873.p(*, &);end;
assert_raise(LocalJumpError, bug11873) do
bug11873.instance_eval do
@@ -1014,6 +1475,25 @@ eom
assert_valid_syntax 'p :foo, {proc do end => proc do end, b: proc do end}', bug13073
end
+ def test_invalid_encoding_symbol
+ assert_syntax_error('{"\xC3": 1}', "invalid symbol")
+ end
+
+ def test_invalid_symbol_in_hash_memory_leak
+ assert_no_memory_leak([], "#{<<-'begin;'}", "#{<<-'end;'}", rss: true)
+ str = '{"\xC3": 1}'.force_encoding("UTF-8")
+ code = proc do
+ eval(str)
+ raise "unreachable"
+ rescue SyntaxError
+ end
+
+ 1_000.times(&code)
+ begin;
+ 1_000_000.times(&code)
+ end;
+ end
+
def test_do_after_local_variable
obj = Object.new
def obj.m; yield; end
@@ -1062,9 +1542,10 @@ eom
"#{return}"
raise((return; "should not raise"))
begin raise; ensure return; end; self
- begin raise; ensure return; end and self
nil&defined?0--begin e=no_method_error(); return; 0;end
return puts('ignored') #=> ignored
+ BEGIN {return}
+ END {return if false}
end;
.split(/\n/).map {|s|[(line+=1), *s.split(/#=> /, 2)]}
failed = proc do |n, s|
@@ -1094,6 +1575,76 @@ eom
end
end
+ def test_eval_return_toplevel
+ feature4840 = '[ruby-core:36785] [Feature #4840]'
+ line = __LINE__+2
+ code = "#{<<~"begin;"}#{<<~'end;'}"
+ begin;
+ eval "return"; raise
+ begin eval "return"; rescue SystemExit; exit false; end
+ begin eval "return"; ensure puts "ensured"; end #=> ensured
+ begin ensure eval "return"; end
+ begin raise; ensure; eval "return"; end
+ begin raise; rescue; eval "return"; end
+ eval "return false"; raise
+ eval "return 1"; raise
+ "#{eval "return"}"
+ raise((eval "return"; "should not raise"))
+ begin raise; ensure eval "return"; end; self
+ begin raise; ensure eval "return"; end and self
+ eval "return puts('ignored')" #=> ignored
+ BEGIN {eval "return"}
+ end;
+ .split(/\n/).map {|s|[(line+=1), *s.split(/#=> /, 2)]}
+ failed = proc do |n, s|
+ RubyVM::InstructionSequence.compile(s, __FILE__, nil, n).disasm
+ end
+ Tempfile.create(%w"test_return_ .rb") do |lib|
+ lib.close
+ args = %W[-W0 -r#{lib.path}]
+ all_assertions_foreach(feature4840, *[:main, :lib].product([:class, :top], code)) do |main, klass, (n, s, *ex)|
+ if klass == :class
+ s = "class X; #{s}; end"
+ if main == :main
+ assert_in_out_err(%[-W0], s, ex, /return/, proc {failed[n, s]}, success: false)
+ else
+ File.write(lib, s)
+ assert_in_out_err(args, "", ex, /return/, proc {failed[n, s]}, success: false)
+ end
+ else
+ if main == :main
+ assert_in_out_err(%[-W0], s, ex, [], proc {failed[n, s]}, success: true)
+ else
+ File.write(lib, s)
+ assert_in_out_err(args, "", ex, [], proc {failed[n, s]}, success: true)
+ end
+ end
+ end
+ end
+ end
+
+ def test_return_toplevel_with_argument
+ assert_warn(/argument of top-level return is ignored/) {eval("return 1")}
+ end
+
+ def test_return_in_proc_in_class
+ assert_in_out_err(['-e', 'class TestSyntax; proc{ return }.call; end'], "", [], /^-e:1:.*unexpected return \(LocalJumpError\)/)
+ end
+
+ def test_return_in_END
+ assert_normal_exit('END {return}')
+ end
+
+ def test_return_in_BEGIN_in_eval
+ # `BEGIN` in `eval` is allowed, even inside a method, and `return`
+ # from that block exits from that method without `LocalJumpError`.
+ obj = Object.new
+ def obj.ok
+ eval("BEGIN {return :ok}")
+ end
+ assert_equal :ok, assert_nothing_raised(LocalJumpError) {obj.ok}
+ end
+
def test_syntax_error_in_rescue
bug12613 = '[ruby-core:76531] [Bug #12613]'
assert_syntax_error("#{<<-"begin;"}\n#{<<-"end;"}", /Invalid retry/, bug12613)
@@ -1111,8 +1662,14 @@ eom
end;
end
+ def test_syntax_error_at_newline
+ expected = /(\n|\| ) \^/
+ assert_syntax_error("%[abcdef", expected)
+ assert_syntax_error("%[abcdef\n", expected)
+ end
+
def test_invalid_jump
- assert_in_out_err(%w[-e redo], "", [], /^-e:1: /)
+ assert_in_out_err(%w[-e redo], "", [], /^-e:1: |~ Invalid redo/)
end
def test_keyword_not_parens
@@ -1186,6 +1743,24 @@ eom
assert_equal(:begin, result)
end
+ def test_rescue_do_end_ensure_in_lambda
+ result = []
+ eval("#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ -> do
+ result << :begin
+ raise "An exception occurred!"
+ rescue
+ result << :rescue
+ else
+ result << :else
+ ensure
+ result << :ensure
+ end.call
+ end;
+ assert_equal([:begin, :rescue, :ensure], result)
+ end
+
def test_return_in_loop
obj = Object.new
def obj.test
@@ -1195,6 +1770,598 @@ eom
assert_nil obj.test
end
+ def test_assignment_return_in_loop
+ obj = Object.new
+ def obj.test
+ x = nil
+ _y = (return until x unless x)
+ end
+ assert_nil obj.test, "[Bug #16695]"
+ end
+
+ def test_method_call_location
+ line = __LINE__+5
+ e = assert_raise(NoMethodError) do
+ 1.upto(0) do
+ end
+ .
+ foo(
+ 1,
+ 2,
+ )
+ end
+ assert_equal(line, e.backtrace_locations[0].lineno)
+
+ line = __LINE__+5
+ e = assert_raise(NoMethodError) do
+ 1.upto 0 do
+ end
+ .
+ foo(
+ 1,
+ 2,
+ )
+ end
+ assert_equal(line, e.backtrace_locations[0].lineno)
+ end
+
+ def test_methoddef_endless
+ assert_valid_syntax('private def foo = 42')
+ assert_valid_syntax('private def foo() = 42')
+ assert_valid_syntax('private def inc(x) = x + 1')
+ assert_valid_syntax('private def obj.foo = 42')
+ assert_valid_syntax('private def obj.foo() = 42')
+ assert_valid_syntax('private def obj.inc(x) = x + 1')
+ k = Class.new do
+ class_eval('def rescued(x) = raise("to be caught") rescue "instance #{x}"')
+ class_eval('def self.rescued(x) = raise("to be caught") rescue "class #{x}"')
+ end
+ assert_equal("class ok", k.rescued("ok"))
+ assert_equal("instance ok", k.new.rescued("ok"))
+
+ error = /setter method cannot be defined in an endless method definition/
+ assert_syntax_error('def foo=() = 42', error)
+ assert_syntax_error('def obj.foo=() = 42', error)
+ assert_syntax_error('def foo=() = 42 rescue nil', error)
+ assert_syntax_error('def obj.foo=() = 42 rescue nil', error)
+ end
+
+ def test_methoddef_endless_command
+ assert_valid_syntax('def foo = puts "Hello"')
+ assert_valid_syntax('def foo() = puts "Hello"')
+ assert_valid_syntax('def foo(x) = puts x')
+ assert_valid_syntax('def obj.foo = puts "Hello"')
+ assert_valid_syntax('def obj.foo() = puts "Hello"')
+ assert_valid_syntax('def obj.foo(x) = puts x')
+ k = Class.new do
+ class_eval('def rescued(x) = raise "to be caught" rescue "instance #{x}"')
+ class_eval('def self.rescued(x) = raise "to be caught" rescue "class #{x}"')
+ end
+ assert_equal("class ok", k.rescued("ok"))
+ assert_equal("instance ok", k.new.rescued("ok"))
+
+ 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
+ assert_valid_syntax('while def foo; tap do end; end; break; end')
+ assert_valid_syntax('while def foo a = tap do end; end; break; end')
+ end
+
+ def test_classdef_in_cond
+ assert_valid_syntax('while class Foo; tap do end; end; break; end')
+ assert_valid_syntax('while class Foo a = tap do end; end; break; end')
+ end
+
+ def test_command_with_cmd_brace_block
+ assert_valid_syntax('obj.foo (1) {}')
+ assert_valid_syntax('obj::foo (1) {}')
+ assert_valid_syntax('bar {}')
+ assert_valid_syntax('Bar {}')
+ assert_valid_syntax('bar() {}')
+ assert_valid_syntax('Bar() {}')
+ assert_valid_syntax('Foo::bar {}')
+ assert_valid_syntax('Foo::Bar {}')
+ assert_valid_syntax('Foo::bar() {}')
+ assert_valid_syntax('Foo::Bar() {}')
+ end
+
+ def test_command_newline_in_tlparen_args
+ assert_valid_syntax("p (1\n2\n),(3),(4)")
+ assert_valid_syntax("p (\n),(),()")
+ assert_valid_syntax("a.b (1\n2\n),(3),(4)")
+ assert_valid_syntax("a.b (\n),(),()")
+ end
+
+ def test_command_semicolon_in_tlparen_at_the_first_arg
+ bug19281 = '[ruby-core:111499] [Bug #19281]'
+ assert_valid_syntax('p (1;2),(3),(4)', bug19281)
+ assert_valid_syntax('p (;),(),()', bug19281)
+ assert_valid_syntax('a.b (1;2),(3),(4)', bug19281)
+ assert_valid_syntax('a.b (;),(),()', bug19281)
+ end
+
+ def test_command_do_block_call_with_empty_args_brace_block
+ assert_valid_syntax('cmd 1, 2 do end.m() { blk_body }')
+ end
+
+ def test_numbered_parameter
+ assert_valid_syntax('proc {_1}')
+ assert_equal(3, eval('[1,2].then {_1+_2}'))
+ assert_equal("12", eval('[1,2].then {"#{_1}#{_2}"}'))
+ assert_equal([1, 2], eval('[1,2].then {_1}'))
+ assert_equal(3, eval('->{_1+_2}.call(1,2)'))
+ assert_equal(4, eval('->(a=->{_1}){a}.call.call(4)'))
+ assert_equal(5, eval('-> a: ->{_1} {a}.call.call(5)'))
+ assert_syntax_error('proc {|| _1}', /ordinary parameter is defined/)
+ assert_syntax_error('proc {|;a| _1}', /ordinary parameter is defined/)
+ assert_syntax_error("proc {|\n| _1}", /ordinary parameter is defined/)
+ assert_syntax_error('proc {|x| _1}', /ordinary parameter is defined/)
+ assert_syntax_error('proc {_1; proc {_2}}', /numbered parameter is already used/)
+ assert_syntax_error('proc {proc {_1}; _2}', /numbered parameter is already used/)
+ assert_syntax_error('->(){_1}', /ordinary parameter is defined/)
+ assert_syntax_error('->(x){_1}', /ordinary parameter is defined/)
+ assert_syntax_error('->x{_1}', /ordinary parameter is defined/)
+ assert_syntax_error('->x:_2{}', /ordinary parameter is defined/)
+ assert_syntax_error('->x=_1{}', /ordinary parameter is defined/)
+ assert_syntax_error('-> {_1; -> {_2}}', /numbered parameter is already used/)
+ assert_syntax_error('-> {-> {_1}; _2}', /numbered parameter is already used/)
+ assert_syntax_error('proc {_1; _1 = nil}', /Can't assign to numbered parameter _1/)
+ assert_syntax_error('proc {_1 = nil}', /_1 is reserved for numbered parameter/)
+ assert_syntax_error('_2=1', /_2 is reserved for numbered parameter/)
+ assert_syntax_error('proc {|_3|}', /_3 is reserved for numbered parameter/)
+ assert_syntax_error('def x(_4) end', /_4 is reserved for numbered parameter/)
+ assert_syntax_error('def _5; end', /_5 is reserved for numbered parameter/)
+ assert_syntax_error('def self._6; end', /_6 is reserved for numbered parameter/)
+ assert_raise_with_message(NameError, /undefined local variable or method '_1'/) {
+ eval('_1')
+ }
+ ['class C', 'class << C', 'module M', 'def m', 'def o.m'].each do |c|
+ assert_valid_syntax("->{#{c};->{_1};end;_1}\n")
+ assert_valid_syntax("->{_1;#{c};->{_1};end}\n")
+ end
+
+ 1.times {
+ [
+ _1,
+ assert_equal([:a], eval("[:a].map{_1}")),
+ assert_raise(NameError) {eval("_1")},
+ ]
+ }
+
+ assert_valid_syntax("proc {def foo(_);end;_1}")
+ assert_valid_syntax("p { [_1 **2] }")
+ assert_valid_syntax("proc {_1;def foo();end;_1}")
+ end
+
+ def test_it
+ assert_valid_syntax('proc {it}')
+ assert_syntax_error('[1,2].then {it+_2}', /'it' is already used/)
+ assert_syntax_error('[1,2].then {_2+it}', /numbered parameter is already used/)
+ assert_equal([1, 2], eval('[1,2].then {it}'))
+ assert_syntax_error('[1,2].then {"#{it}#{_2}"}', /'it' is already used/)
+ assert_syntax_error('[1,2].then {"#{_2}#{it}"}', /numbered parameter is already used/)
+ assert_syntax_error('->{it+_2}.call(1,2)', /'it' is already used/)
+ assert_syntax_error('->{_2+it}.call(1,2)', /numbered parameter is already used/)
+ assert_equal(4, eval('->(a=->{it}){a}.call.call(4)'))
+ assert_equal(5, eval('-> a: ->{it} {a}.call.call(5)'))
+ assert_syntax_error('proc {|| it}', /ordinary parameter is defined/)
+ assert_syntax_error('proc {|;a| it}', /ordinary parameter is defined/)
+ assert_syntax_error("proc {|\n| it}", /ordinary parameter is defined/)
+ assert_syntax_error('proc {|x| it}', /ordinary parameter is defined/)
+ assert_equal([1, 2], eval('1.then {[it, 2.then {_1}]}'))
+ assert_equal([2, 1], eval('1.then {[2.then {_1}, it]}'))
+ assert_syntax_error('->(){it}', /ordinary parameter is defined/)
+ assert_syntax_error('->(x){it}', /ordinary parameter is defined/)
+ assert_syntax_error('->x{it}', /ordinary parameter is defined/)
+ assert_syntax_error('->x:_1{}', /ordinary parameter is defined/)
+ assert_syntax_error('->x=it{}', /ordinary parameter is defined/)
+ assert_valid_syntax('-> {it; -> {_2}}')
+ assert_valid_syntax('-> {-> {it}; _2}')
+ assert_equal([1, nil], eval('proc {that=it; it=nil; [that, it]}.call(1)'))
+ assert_equal(1, eval('proc {it = 1}.call'))
+ assert_warning(/1: warning: assigned but unused variable - it/) {
+ assert_equal(2, eval('a=Object.new; def a.foo; it = 2; end; a.foo'))
+ }
+ assert_equal(3, eval('proc {|it| it}.call(3)'))
+ assert_equal(4, eval('a=Object.new; def a.foo(it); it; end; a.foo(4)'))
+ assert_equal(5, eval('a=Object.new; def a.it; 5; end; a.it'))
+ assert_equal(6, eval('a=Class.new; a.class_eval{ def it; 6; end }; a.new.it'))
+ assert_equal([7, 8], eval('a=[]; [7].each { a << it; [8].each { a << it } }; a'))
+ assert_raise_with_message(NameError, /undefined local variable or method 'it'/) do
+ eval('it')
+ end
+ ['class C', 'class << C', 'module M', 'def m', 'def o.m'].each do |c|
+ assert_valid_syntax("->{#{c};->{it};end;it}\n")
+ assert_valid_syntax("->{it;#{c};->{it};end}\n")
+ end
+ 1.times do
+ [
+ assert_equal(0, it),
+ assert_equal([:a], eval('[:a].map{it}')),
+ assert_raise(NameError) {eval('it')},
+ ]
+ 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
+ mesg = /void value expression/
+ assert_syntax_error("tap {a = (true ? next : break)}", mesg)
+ assert_valid_syntax("tap {a = (true ? true : break)}")
+ assert_valid_syntax("tap {a = (break if false)}")
+ assert_valid_syntax("tap {a = (break unless true)}")
+ end
+
+ def test_value_expr_in_singleton
+ mesg = /void value expression/
+ assert_syntax_error("class << (return); end", mesg)
+ end
+
+ def test_tautological_condition
+ assert_valid_syntax("def f() return if false and invalid; nil end")
+ assert_valid_syntax("def f() return unless true or invalid; nil end")
+ end
+
+ def test_argument_forwarding
+ assert_valid_syntax('def foo(...) bar(...) end')
+ assert_valid_syntax('def foo(...) end')
+ assert_valid_syntax('def foo(a, ...) bar(...) end')
+ assert_valid_syntax("def foo ...\n bar(...)\nend")
+ assert_valid_syntax("def foo a, ...\n bar(...)\nend")
+ assert_valid_syntax("def foo b = 1, ...\n bar(...)\nend")
+ assert_valid_syntax("def foo ...; bar(...); end")
+ assert_valid_syntax("def foo a, ...; bar(...); end")
+ assert_valid_syntax("def foo b = 1, ...; bar(...); end")
+ assert_valid_syntax("(def foo ...\n bar(...)\nend)")
+ assert_valid_syntax("(def foo ...; bar(...); end)")
+ assert_valid_syntax("def (1...).foo ...; bar(...); end")
+ assert_valid_syntax("def (tap{1...}).foo ...; bar(...); end")
+ assert_valid_syntax('def ==(...) end')
+ assert_valid_syntax('def [](...) end')
+ assert_valid_syntax('def nil(...) end')
+ assert_valid_syntax('def true(...) end')
+ assert_valid_syntax('def false(...) end')
+ assert_valid_syntax('->a=1...{}')
+ unexpected = /unexpected \.{3}/
+ assert_syntax_error('iter do |...| end', /unexpected/)
+ assert_syntax_error('iter {|...|}', /unexpected/)
+ assert_syntax_error('->... {}', unexpected)
+ assert_syntax_error('->(...) {}', unexpected)
+ assert_syntax_error('->a,... {}', unexpected)
+ assert_syntax_error('->(a,...) {}', unexpected)
+ assert_syntax_error('->a=1,... {}', unexpected)
+ assert_syntax_error('->(a=1,...) {}', unexpected)
+ assert_syntax_error('def foo(x, y, z) bar(...); end', /unexpected/)
+ assert_syntax_error('def foo(x, y, z) super(...); end', /unexpected/)
+ assert_syntax_error('def foo(...) yield(...); end', /unexpected/)
+ assert_syntax_error('def foo(...) return(...); end', /unexpected/)
+ assert_syntax_error('def foo(...) a = (...); end', /unexpected/)
+ assert_syntax_error('def foo(...) [...]; end', /unexpected/)
+ assert_syntax_error('def foo(...) foo[...]; end', /unexpected/)
+ assert_syntax_error('def foo(...) foo[...] = x; end', /unexpected/)
+ assert_syntax_error('def foo(...) foo(...) { }; end', /both block arg and actual block given/)
+ assert_syntax_error('def foo(...) defined?(...); end', /unexpected/)
+ assert_syntax_error('def foo(*rest, ...) end', '... after rest argument')
+ assert_syntax_error('def foo(*, ...) end', '... after rest argument')
+
+ obj1 = Object.new
+ def obj1.bar(*args, **kws, &block)
+ if block
+ block.call(args, kws)
+ else
+ [args, kws]
+ end
+ end
+ obj4 = obj1.clone
+ obj5 = obj1.clone
+ obj6 = obj1.clone
+ obj1.instance_eval('def foo(...) 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)
+ if block
+ block.call(args, kws)
+ else
+ [args, kws]
+ end
+ end
+ }
+ obj2 = klass.new
+ obj2.instance_eval('def foo(...) super(...) end', __FILE__, __LINE__)
+
+ obj3 = Object.new
+ def obj3.bar(*args, &block)
+ if kws = Hash.try_convert(args.last)
+ args.pop
+ else
+ kws = {}
+ end
+ if block
+ block.call(args, kws)
+ else
+ [args, kws]
+ end
+ end
+ obj3.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__)
+
+ [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})
+ }
+ assert_warning('') {
+ assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5))
+ }
+ array = obj == obj3 ? [] : [{}]
+ assert_warning('') {
+ assert_equal([array, {}], obj.foo({}) {|*x| x})
+ }
+ assert_warning('') {
+ assert_equal([array, {}], obj.foo({}))
+ }
+ assert_equal(-1, obj.method(:foo).arity)
+ parameters = obj.method(:foo).parameters
+ assert_equal(:rest, parameters.dig(0, 0))
+ assert_equal(:keyrest, parameters.dig(1, 0))
+ assert_equal(:block, parameters.dig(2, 0))
+ end
+ end
+
+ def test_argument_forwarding_with_leading_arguments
+ obj = Object.new
+ def obj.bar(*args, **kws, &block)
+ if block
+ block.call(args, kws)
+ else
+ [args, kws]
+ end
+ end
+ obj.instance_eval('def foo(_a, ...) bar(...) end', __FILE__, __LINE__)
+ assert_equal [[], {}], obj.foo(1)
+ assert_equal [[2], {}], obj.foo(1, 2)
+ assert_equal [[2, 3], {}], obj.foo(1, 2, 3)
+ assert_equal [[], {a: 1}], obj.foo(1, a: 1)
+ assert_equal [[2], {a: 1}], obj.foo(1, 2, a: 1)
+ assert_equal [[2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1)
+ assert_equal [[2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval('def foo(...) bar(1, ...) end', __FILE__, __LINE__)
+ assert_equal [[1], {}], obj.foo
+ assert_equal [[1, 1], {}], obj.foo(1)
+ assert_equal [[1, 1, 2], {}], obj.foo(1, 2)
+ assert_equal [[1, 1, 2, 3], {}], obj.foo(1, 2, 3)
+ assert_equal [[1], {a: 1}], obj.foo(a: 1)
+ assert_equal [[1, 1], {a: 1}], obj.foo(1, a: 1)
+ assert_equal [[1, 1, 2], {a: 1}], obj.foo(1, 2, a: 1)
+ assert_equal [[1, 1, 2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1)
+ assert_equal [[1, 1, 2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval('def foo(a, ...) bar(a, ...) end', __FILE__, __LINE__)
+ assert_equal [[4], {}], obj.foo(4)
+ assert_equal [[4, 2], {}], obj.foo(4, 2)
+ assert_equal [[4, 2, 3], {}], obj.foo(4, 2, 3)
+ assert_equal [[4], {a: 1}], obj.foo(4, a: 1)
+ assert_equal [[4, 2], {a: 1}], obj.foo(4, 2, a: 1)
+ assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1)
+ assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval('def foo(_a, ...) bar(1, ...) end', __FILE__, __LINE__)
+ assert_equal [[1], {}], obj.foo(4)
+ assert_equal [[1, 2], {}], obj.foo(4, 2)
+ assert_equal [[1, 2, 3], {}], obj.foo(4, 2, 3)
+ assert_equal [[1], {a: 1}], obj.foo(4, a: 1)
+ assert_equal [[1, 2], {a: 1}], obj.foo(4, 2, a: 1)
+ assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1)
+ assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval('def foo(_a, _b, ...) bar(...) end', __FILE__, __LINE__)
+ assert_equal [[], {}], obj.foo(4, 5)
+ assert_equal [[2], {}], obj.foo(4, 5, 2)
+ assert_equal [[2, 3], {}], obj.foo(4, 5, 2, 3)
+ assert_equal [[], {a: 1}], obj.foo(4, 5, a: 1)
+ assert_equal [[2], {a: 1}], obj.foo(4, 5, 2, a: 1)
+ assert_equal [[2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1)
+ assert_equal [[2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval('def foo(_a, _b, ...) bar(1, ...) end', __FILE__, __LINE__)
+ assert_equal [[1], {}], obj.foo(4, 5)
+ assert_equal [[1, 2], {}], obj.foo(4, 5, 2)
+ assert_equal [[1, 2, 3], {}], obj.foo(4, 5, 2, 3)
+ assert_equal [[1], {a: 1}], obj.foo(4, 5, a: 1)
+ assert_equal [[1, 2], {a: 1}], obj.foo(4, 5, 2, a: 1)
+ assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1)
+ assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval('def foo(_a, ...) bar(1, 2, ...) end', __FILE__, __LINE__)
+ assert_equal [[1, 2], {}], obj.foo(5)
+ assert_equal [[1, 2, 5], {}], obj.foo(4, 5)
+ assert_equal [[1, 2, 5, 2], {}], obj.foo(4, 5, 2)
+ assert_equal [[1, 2, 5, 2, 3], {}], obj.foo(4, 5, 2, 3)
+ assert_equal [[1, 2, 5], {a: 1}], obj.foo(4, 5, a: 1)
+ assert_equal [[1, 2, 5, 2], {a: 1}], obj.foo(4, 5, 2, a: 1)
+ assert_equal [[1, 2, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1)
+ assert_equal [[1, 2, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval('def foo(a, b, ...) bar(b, a, ...) end', __FILE__, __LINE__)
+ assert_equal [[5, 4], {}], obj.foo(4, 5)
+ assert_equal [[5, 4, 2], {}], obj.foo(4, 5, 2)
+ assert_equal [[5, 4, 2, 3], {}], obj.foo(4, 5, 2, 3)
+ assert_equal [[5, 4], {a: 1}], obj.foo(4, 5, a: 1)
+ assert_equal [[5, 4, 2], {a: 1}], obj.foo(4, 5, 2, a: 1)
+ assert_equal [[5, 4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1)
+ assert_equal [[5, 4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval('def foo(a, _b, ...) bar(a, ...) end', __FILE__, __LINE__)
+ assert_equal [[4], {}], obj.foo(4, 5)
+ assert_equal [[4, 2], {}], obj.foo(4, 5, 2)
+ assert_equal [[4, 2, 3], {}], obj.foo(4, 5, 2, 3)
+ assert_equal [[4], {a: 1}], obj.foo(4, 5, a: 1)
+ assert_equal [[4, 2], {a: 1}], obj.foo(4, 5, 2, a: 1)
+ assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1)
+ assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval('def foo(a, ...) bar(a, 1, ...) end', __FILE__, __LINE__)
+ assert_equal [[4, 1], {}], obj.foo(4)
+ assert_equal [[4, 1, 5], {}], obj.foo(4, 5)
+ assert_equal [[4, 1, 5, 2], {}], obj.foo(4, 5, 2)
+ assert_equal [[4, 1, 5, 2, 3], {}], obj.foo(4, 5, 2, 3)
+ assert_equal [[4, 1, 5], {a: 1}], obj.foo(4, 5, a: 1)
+ assert_equal [[4, 1, 5, 2], {a: 1}], obj.foo(4, 5, 2, a: 1)
+ assert_equal [[4, 1, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1)
+ assert_equal [[4, 1, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval("def foo a, ...\n bar(a, ...)\n"" end", __FILE__, __LINE__)
+ assert_equal [[4], {}], obj.foo(4)
+ assert_equal [[4, 2], {}], obj.foo(4, 2)
+ assert_equal [[4, 2, 3], {}], obj.foo(4, 2, 3)
+ assert_equal [[4], {a: 1}], obj.foo(4, a: 1)
+ assert_equal [[4, 2], {a: 1}], obj.foo(4, 2, a: 1)
+ assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1)
+ assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval("def foo a, ...; bar(a, ...); end", __FILE__, __LINE__)
+ assert_equal [[4], {}], obj.foo(4)
+ assert_equal [[4, 2], {}], obj.foo(4, 2)
+ assert_equal [[4, 2, 3], {}], obj.foo(4, 2, 3)
+ assert_equal [[4], {a: 1}], obj.foo(4, a: 1)
+ assert_equal [[4, 2], {a: 1}], obj.foo(4, 2, a: 1)
+ assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1)
+ assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ exp = eval("-> (a: nil) {a...1}")
+ assert_equal 0...1, exp.call(a: 0)
+ end
+
+ def test_argument_forwarding_with_super
+ assert_valid_syntax('def foo(...) super {}; end')
+ assert_valid_syntax('def foo(...) super() {}; end')
+ assert_syntax_error('def foo(...) super(...) {}; end', /both block arg and actual block/)
+ assert_syntax_error('def foo(...) super(1, ...) {}; end', /both block arg and actual block/)
+ end
+
+ def test_argument_forwarding_with_super_memory_leak
+ assert_no_memory_leak([], "#{<<-'begin;'}", "#{<<-'end;'}", rss: true)
+ code = proc do
+ eval("def foo(...) super(...) {}; end")
+ raise "unreachable"
+ rescue SyntaxError
+ end
+
+ 1_000.times(&code)
+ begin;
+ 100_000.times(&code)
+ end;
+ end
+
+ def test_class_module_Object_ancestors
+ assert_ruby_status([], <<-RUBY)
+ m = Module.new
+ m::Bug18832 = 1
+ include m
+ class Bug18832; end
+ RUBY
+ assert_ruby_status([], <<-RUBY)
+ m = Module.new
+ m::Bug18832 = 1
+ include m
+ module Bug18832; end
+ RUBY
+ end
+
+ def test_cdhash
+ assert_separately([], <<-RUBY)
+ n = case 1 when 2r then false else true end
+ assert_equal(n, true, '[ruby-core:103759] [Bug #17854]')
+ RUBY
+ assert_separately([], <<-RUBY)
+ n = case 3/2r when 1.5r then true else false end
+ assert_equal(n, true, '[ruby-core:103759] [Bug #17854]')
+ RUBY
+ assert_separately([], <<-RUBY)
+ n = case 1i when 1i then true else false end
+ assert_equal(n, true, '[ruby-core:103759] [Bug #17854]')
+ RUBY
+ end
+
+ def test_defined_in_short_circuit_if_condition
+ bug = '[ruby-core:20501]'
+ conds = [
+ "false && defined?(Some::CONSTANT)",
+ "true || defined?(Some::CONSTANT)",
+ "(false && defined?(Some::CONSTANT))", # parens exercise different code path
+ "(true || defined?(Some::CONSTANT))",
+ "@val && false && defined?(Some::CONSTANT)",
+ "@val || true || defined?(Some::CONSTANT)"
+ ]
+
+ conds.each do |cond|
+ code = %Q{
+ def my_method
+ var = "there"
+ if #{cond}
+ var = "here"
+ end
+ raise var
+ end
+ begin
+ my_method
+ rescue
+ print 'ok'
+ else
+ print 'ng'
+ end
+ }
+ # Invoke in a subprocess because the bug caused a segfault using the parse.y compiler.
+ # Don't use assert_separately because the bug was best reproducible in a clean slate,
+ # without test env loaded.
+ out, _err, status = EnvUtil.invoke_ruby(["--disable-gems"], code, true, false)
+ assert_predicate(status, :success?, bug)
+ assert_equal 'ok', out
+ end
+ end
+
private
def not_label(x) @result = x; @not_label ||= nil end
@@ -1229,7 +2396,7 @@ eom
end
end
- def caller_lineno(*)
+ def caller_lineno(*, &)
caller_locations(1, 1)[0].lineno
end
end
diff --git a/test/ruby/test_system.rb b/test/ruby/test_system.rb
index 60037ab044..3fcdaa6aad 100644
--- a/test/ruby/test_system.rb
+++ b/test/ruby/test_system.rb
@@ -146,6 +146,19 @@ class TestSystem < Test::Unit::TestCase
end
end
+ def test_system_closed
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ ios = []
+ ObjectSpace.each_object(IO) {|io| ios << io}
+ `echo`
+ ObjectSpace.each_object(IO) do |io|
+ next if ios.include?(io)
+ assert_nothing_raised {io.close}
+ end
+ end;
+ end
+
def test_empty_evstr
assert_equal("", eval('"#{}"', nil, __FILE__, __LINE__), "[ruby-dev:25113]")
end
@@ -160,4 +173,45 @@ class TestSystem < Test::Unit::TestCase
assert_equal(true, system(tmpfilename), '[ruby-core:32745]')
}
end if File.executable?("/bin/sh")
+
+ def test_system_exception
+ ruby = EnvUtil.rubybin
+ assert_nothing_raised do
+ system('feature_14235', exception: false)
+ end
+ assert_nothing_raised do
+ system(ruby, "-e", "abort", exception: false)
+ end
+ assert_nothing_raised do
+ system("'#{ruby}' -e abort", exception: false)
+ end
+ assert_raise(Errno::ENOENT) do
+ system('feature_14235', exception: true)
+ end
+ assert_raise_with_message(RuntimeError, /\ACommand failed with exit /) do
+ system(ruby, "-e", "abort", exception: true)
+ end
+ assert_raise_with_message(RuntimeError, /\ACommand failed with exit /) do
+ system("'#{ruby}' -e abort", exception: true)
+ end
+ end
+
+ def test_system_exception_nonascii
+ Dir.mktmpdir("ruby_script_tmp") do |tmpdir|
+ name = "\u{30c6 30b9 30c8}"
+ tmpfilename = "#{tmpdir}/#{name}.cmd"
+ message = /#{name}\.cmd/
+ assert_raise_with_message(Errno::ENOENT, message) do
+ system(tmpfilename, exception: true)
+ end
+ open(tmpfilename, "w") {|f|
+ f.print "@" if /mingw|mswin/ =~ RUBY_PLATFORM
+ f.puts "exit 127"
+ f.chmod(0755)
+ }
+ assert_raise_with_message(RuntimeError, message) do
+ system(tmpfilename, exception: true)
+ end
+ end
+ end
end
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb
index 135d17237e..2a61fc3450 100644
--- a/test/ruby/test_thread.rb
+++ b/test/ruby/test_thread.rb
@@ -1,6 +1,8 @@
# -*- coding: us-ascii -*-
# frozen_string_literal: false
require 'test/unit'
+require "rbconfig/sizeof"
+require "timeout"
class TestThread < Test::Unit::TestCase
class Thread < ::Thread
@@ -27,9 +29,19 @@ class TestThread < Test::Unit::TestCase
end
def test_inspect
- th = Module.new {break module_eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")}.start{}
- assert_match(/::C\u{30b9 30ec 30c3 30c9}:/, th.inspect)
+ m = Thread::Mutex.new
+ m.lock
+ line = __LINE__+1
+ th = Module.new {break module_eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")}.start do
+ m.synchronize {}
+ end
+ Thread.pass until th.stop?
+ s = th.inspect
+ assert_include(s, "::C\u{30b9 30ec 30c3 30c9}:")
+ assert_include(s, " #{__FILE__}:#{line} ")
+ assert_equal(s, th.to_s)
ensure
+ m.unlock
th.join
end
@@ -166,6 +178,8 @@ class TestThread < Test::Unit::TestCase
t1.kill
t2.kill
assert_operator(c1, :>, c2, "[ruby-dev:33124]") # not guaranteed
+ t1.join
+ t2.join
end
def test_new
@@ -184,8 +198,8 @@ class TestThread < Test::Unit::TestCase
end
ensure
- t1.kill if t1
- t2.kill if t2
+ t1&.kill&.join
+ t2&.kill&.join
end
def test_new_symbol_proc
@@ -201,7 +215,7 @@ class TestThread < Test::Unit::TestCase
assert_nil(t.join(0.05))
ensure
- t.kill if t
+ t&.kill&.join
end
def test_join2
@@ -222,9 +236,51 @@ class TestThread < Test::Unit::TestCase
assert_equal(t1, t3.value)
ensure
- t1.kill if t1
- t2.kill if t2
- t3.kill if t3
+ t1&.kill&.join
+ t2&.kill&.join
+ t3&.kill&.join
+ end
+
+ def test_join_argument_conversion
+ t = Thread.new {}
+
+ # Make sure that the thread terminates
+ Thread.pass while t.status
+
+ assert_raise(TypeError) {t.join(:foo)}
+
+ limit = Struct.new(:to_f, :count).new(0.05)
+ assert_same(t, t.join(limit))
+ end
+
+ { 'FIXNUM_MAX' => RbConfig::LIMITS['FIXNUM_MAX'],
+ 'UINT64_MAX' => RbConfig::LIMITS['UINT64_MAX'],
+ 'INFINITY' => Float::INFINITY
+ }.each do |name, limit|
+ define_method("test_join_limit_#{name}") do
+ t = Thread.new {}
+ assert_same t, t.join(limit), "limit=#{limit.inspect}"
+ end
+ end
+
+ { 'minus_1' => -1,
+ 'minus_0_1' => -0.1,
+ 'FIXNUM_MIN' => RbConfig::LIMITS['FIXNUM_MIN'],
+ 'INT64_MIN' => RbConfig::LIMITS['INT64_MIN'],
+ 'minus_INFINITY' => -Float::INFINITY
+ }.each do |name, limit|
+ define_method("test_join_limit_negative_#{name}") do
+ t = Thread.new { sleep }
+ begin
+ assert_nothing_raised(Timeout::Error) do
+ Timeout.timeout(30) do
+ assert_nil t.join(limit), "limit=#{limit.inspect}"
+ end
+ end
+ ensure
+ t.kill
+ end
+ end
end
def test_kill_main_thread
@@ -276,9 +332,8 @@ class TestThread < Test::Unit::TestCase
Thread.pass while t.alive?
assert_equal(2, s)
assert_raise(ThreadError) { t.wakeup }
-
ensure
- t.kill if t
+ t&.kill&.join
end
def test_stop
@@ -343,7 +398,7 @@ class TestThread < Test::Unit::TestCase
end
INPUT
- assert_in_out_err(%w(--disable-gems -d), <<-INPUT, %w(false 2), %r".+")
+ assert_in_out_err(%w(-d), <<-INPUT, %w(false 2), %r".+")
p Thread.abort_on_exception
begin
t = Thread.new { raise }
@@ -399,6 +454,7 @@ class TestThread < Test::Unit::TestCase
}
assert_equal(false, q1.pop)
Thread.pass while th.alive?
+ assert_raise(RuntimeError) { th.join }
}
assert_warn(/report 2/, "exception should be reported when true") {
@@ -408,6 +464,7 @@ class TestThread < Test::Unit::TestCase
}
assert_equal(true, q1.pop)
Thread.pass while th.alive?
+ assert_raise(RuntimeError) { th.join }
}
assert_warn("", "the global flag should not affect already started threads") {
@@ -420,6 +477,7 @@ class TestThread < Test::Unit::TestCase
q2.push(Thread.report_on_exception = true)
assert_equal(false, q1.pop)
Thread.pass while th.alive?
+ assert_raise(RuntimeError) { th.join }
}
assert_warn(/report 4/, "should defaults to the global flag at the start") {
@@ -430,6 +488,7 @@ class TestThread < Test::Unit::TestCase
}
assert_equal(true, q1.pop)
Thread.pass while th.alive?
+ assert_raise(RuntimeError) { th.join }
}
assert_warn(/report 5/, "should first report and then raise with report_on_exception + abort_on_exception") {
@@ -443,10 +502,24 @@ class TestThread < Test::Unit::TestCase
q2.push(true)
Thread.pass while th.alive?
}
+ assert_raise(RuntimeError) { th.join }
}
end;
end
+ def test_ignore_deadlock
+ if /mswin|mingw/ =~ RUBY_PLATFORM
+ omit "can't trap a signal from another process on Windows"
+ end
+ assert_in_out_err([], <<-INPUT, %w(false :sig), [], :signal=>:INT, timeout: 1, timeout_error: nil)
+ p Thread.ignore_deadlock
+ q = Thread::Queue.new
+ trap(:INT){q.push :sig}
+ Thread.ignore_deadlock = true
+ p q.pop
+ INPUT
+ end
+
def test_status_and_stop_p
a = ::Thread.new {
Thread.current.report_on_exception = false
@@ -470,11 +543,10 @@ class TestThread < Test::Unit::TestCase
es1 = e.status
es2 = e.stop?
assert_equal(["run", false], [es1, es2])
-
+ assert_raise(RuntimeError) { a.join }
ensure
- a.kill if a
- b.kill if b
- c.kill if c
+ b&.kill&.join
+ c&.join
end
def test_switch_while_busy_loop
@@ -492,24 +564,7 @@ class TestThread < Test::Unit::TestCase
end
assert(!flag, bug1402)
ensure
- waiter.kill.join
- end
-
- def test_safe_level
- ok = false
- t = Thread.new do
- EnvUtil.suppress_warning do
- $SAFE = 1
- end
- ok = true
- sleep
- end
- Thread.pass until ok
- assert_equal(0, Thread.current.safe_level)
- assert_equal(1, t.safe_level)
-
- ensure
- t.kill if t
+ waiter&.kill&.join
end
def test_thread_local
@@ -529,7 +584,7 @@ class TestThread < Test::Unit::TestCase
assert_equal([:foo, :bar, :baz].sort, t.keys.sort)
ensure
- t.kill if t
+ t&.kill&.join
end
def test_thread_local_fetch
@@ -561,7 +616,7 @@ class TestThread < Test::Unit::TestCase
assert_equal(:qux, e.key)
assert_equal(t, e.receiver)
ensure
- t.kill if t
+ t&.kill&.join
end
def test_thread_local_security
@@ -591,7 +646,8 @@ class TestThread < Test::Unit::TestCase
end
Thread.pass until t.stop?
assert_predicate(t, :alive?)
- t.kill
+ ensure
+ t&.kill&.join
end
def test_mutex_deadlock
@@ -684,7 +740,7 @@ class TestThread < Test::Unit::TestCase
end
def test_no_valid_cfp
- skip 'with win32ole, cannot run this testcase because win32ole redefines Thread#initialize' if defined?(WIN32OLE)
+ omit 'with win32ole, cannot run this testcase because win32ole redefines Thread#initialize' if defined?(WIN32OLE)
bug5083 = '[ruby-dev:44208]'
assert_equal([], Thread.new(&Module.method(:nesting)).value, bug5083)
assert_instance_of(Thread, Thread.new(:to_s, &Class.new.method(:undef_method)).join, bug5083)
@@ -692,8 +748,8 @@ class TestThread < Test::Unit::TestCase
def make_handle_interrupt_test_thread1 flag
r = []
- ready_q = Queue.new
- done_q = Queue.new
+ ready_q = Thread::Queue.new
+ done_q = Thread::Queue.new
th = Thread.new{
begin
Thread.handle_interrupt(RuntimeError => flag){
@@ -769,14 +825,15 @@ class TestThread < Test::Unit::TestCase
end
def test_handle_interrupt_blocking
- r=:ng
- e=Class.new(Exception)
+ r = nil
+ q = Thread::Queue.new
+ e = Class.new(Exception)
th_s = Thread.current
- th = Thread.start{
+ th = Thread.start {
assert_raise(RuntimeError) {
Thread.handle_interrupt(Object => :on_blocking){
begin
- Thread.pass until r == :wait
+ q.pop
Thread.current.raise RuntimeError, "will raise in sleep"
r = :ok
sleep
@@ -786,25 +843,26 @@ class TestThread < Test::Unit::TestCase
}
}
}
- assert_raise(e) {r = :wait; sleep 0.2}
- th.join
- assert_equal(:ok,r)
+ assert_raise(e) {q << true; th.join}
+ assert_equal(:ok, r)
end
def test_handle_interrupt_and_io
assert_in_out_err([], <<-INPUT, %w(ok), [])
th_waiting = true
+ q = Thread::Queue.new
t = Thread.new {
Thread.current.report_on_exception = false
Thread.handle_interrupt(RuntimeError => :on_blocking) {
+ q << true
nil while th_waiting
# async interrupt should be raised _before_ writing puts arguments
puts "ng"
}
}
- Thread.pass while t.stop?
+ q.pop
t.raise RuntimeError
th_waiting = false
t.join rescue nil
@@ -843,15 +901,15 @@ class TestThread < Test::Unit::TestCase
begin
begin
Thread.pass until done
- rescue => e
+ rescue
q.push :ng1
end
begin
Thread.handle_interrupt(Object => :immediate){} if Thread.pending_interrupt?
- rescue RuntimeError => e
+ rescue RuntimeError
q.push :ok
end
- rescue => e
+ rescue
q.push :ng2
ensure
q.push :ng3
@@ -917,17 +975,25 @@ _eom
end
def test_thread_timer_and_interrupt
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
bug5757 = '[ruby-dev:44985]'
pid = nil
- cmd = 'Signal.trap(:INT, "DEFAULT"); r,=IO.pipe; Thread.start {Thread.pass until Thread.main.stop?; puts; STDOUT.flush}; r.read'
+ cmd = 'Signal.trap(:INT, "DEFAULT"); pipe=IO.pipe; Thread.start {Thread.pass until Thread.main.stop?; puts; STDOUT.flush}; pipe[0].read'
opt = {}
opt[:new_pgroup] = true if /mswin|mingw/ =~ RUBY_PLATFORM
- s, t, _err = EnvUtil.invoke_ruby(['-e', cmd], "", true, true, opt) do |in_p, out_p, err_p, cpid|
+ s, t, _err = EnvUtil.invoke_ruby(['-e', cmd], "", true, true, **opt) do |in_p, out_p, err_p, cpid|
+ assert IO.select([out_p], nil, nil, 10), 'subprocess not ready'
out_p.gets
pid = cpid
t0 = Time.now.to_f
Process.kill(:SIGINT, pid)
- Process.wait(pid)
+ begin
+ Timeout.timeout(10) { Process.wait(pid) }
+ rescue Timeout::Error
+ EnvUtil.terminate(pid)
+ raise
+ end
t1 = Time.now.to_f
[$?, t1 - t0, err_p.read]
end
@@ -1013,7 +1079,7 @@ q.pop
puts mth.status
Process.kill(:INT, $$)
}
- sleep 0.1
+ sleep
INPUT
end
@@ -1072,7 +1138,7 @@ q.pop
Thread.pass until mutex.locked?
assert_equal(mutex.owned?, false)
ensure
- th.kill if th
+ th&.kill&.join
end
end
@@ -1099,7 +1165,9 @@ q.pop
env = {}
env['RUBY_THREAD_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size
env['RUBY_THREAD_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size
- out, = EnvUtil.invoke_ruby([env, '-e', script], '', true, true)
+ out, err, status = EnvUtil.invoke_ruby([env, '-e', script], '', true, true)
+ assert_not_predicate(status, :signaled?, err)
+
use_length ? out.length : out
end
@@ -1116,6 +1184,7 @@ q.pop
"0 thread_machine_stack_size")
assert_operator(h_default[:thread_machine_stack_size], :<=, h_large[:thread_machine_stack_size],
"large thread_machine_stack_size")
+ assert_equal("ok", invoke_rec('print :ok', 1024 * 1024 * 100, nil, false))
end
def test_vm_machine_stack_size
@@ -1144,12 +1213,10 @@ q.pop
bug8433 = '[ruby-core:55102] [Bug #8433]'
mutex = Thread::Mutex.new
- flag = false
mutex.lock
th = Thread.new do
mutex.synchronize do
- flag = true
sleep
end
end
@@ -1188,8 +1255,23 @@ q.pop
assert_predicate(status, :success?, bug9751)
end if Process.respond_to?(:fork)
+ def test_fork_value
+ bug18902 = "[Bug #18902]"
+ th = Thread.start { sleep 2 }
+ begin
+ pid = fork do
+ th.value
+ end
+ _, status = Process.wait2(pid)
+ assert_predicate(status, :success?, bug18902)
+ ensure
+ th.kill
+ th.join
+ end
+ end if Process.respond_to?(:fork)
+
def test_fork_while_locked
- m = Mutex.new
+ m = Thread::Mutex.new
thrs = []
3.times do |i|
thrs << Thread.new { m.synchronize { Process.waitpid2(fork{})[1] } }
@@ -1199,6 +1281,42 @@ q.pop
end
end if Process.respond_to?(:fork)
+ def test_fork_while_parent_locked
+ omit 'needs fork' unless Process.respond_to?(:fork)
+ m = Thread::Mutex.new
+ nr = 1
+ thrs = []
+ m.synchronize do
+ thrs = nr.times.map { Thread.new { m.synchronize {} } }
+ thrs.each { Thread.pass }
+ pid = fork do
+ m.locked? or exit!(2)
+ thrs = nr.times.map { Thread.new { m.synchronize {} } }
+ m.unlock
+ thrs.each { |t| t.join(1) == t or exit!(1) }
+ exit!(0)
+ end
+ _, st = Process.waitpid2(pid)
+ assert_predicate st, :success?, '[ruby-core:90312] [Bug #15383]'
+ end
+ thrs.each { |t| assert_same t, t.join(1) }
+ end
+
+ def test_fork_while_mutex_locked_by_forker
+ omit 'needs fork' unless Process.respond_to?(:fork)
+ m = Thread::Mutex.new
+ m.synchronize do
+ pid = fork do
+ exit!(2) unless m.locked?
+ m.unlock rescue exit!(3)
+ m.synchronize {} rescue exit!(4)
+ exit!(0)
+ end
+ _, st = Timeout.timeout(30) { Process.waitpid2(pid) }
+ assert_predicate st, :success?, '[ruby-core:90595] [Bug #15430]'
+ end
+ end
+
def test_subclass_no_initialize
t = Module.new do
break eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")
@@ -1246,29 +1364,148 @@ q.pop
t.join
end
+ def test_yield_across_thread_through_enum
+ bug18649 = '[ruby-core:107980] [Bug #18649]'
+ @log = []
+
+ def self.p(arg)
+ @log << arg
+ end
+
+ def self.synchronize
+ yield
+ end
+
+ def self.execute(task)
+ success = true
+ value = reason = nil
+ end_sync = false
+
+ synchronize do
+ begin
+ p :before
+ value = task.call
+ p :never_reached
+ success = true
+ rescue StandardError => ex
+ ex = ex.class
+ p [:rescue, ex]
+ reason = ex
+ success = false
+ end
+
+ end_sync = true
+ p :end_sync
+ end
+
+ p :should_not_reach_here! unless end_sync
+ [success, value, reason]
+ end
+
+ def self.foo
+ Thread.new do
+ result = execute(-> { yield 42 })
+ p [:result, result]
+ end.join
+ end
+
+ value = to_enum(:foo).first
+ expected = [:before,
+ [:rescue, LocalJumpError],
+ :end_sync,
+ [:result, [false, nil, LocalJumpError]]]
+
+ assert_equal(expected, @log, bug18649)
+ assert_equal(42, value, bug18649)
+ end
+
def test_thread_setname_in_initialize
bug12290 = '[ruby-core:74963] [Bug #12290]'
c = Class.new(Thread) {def initialize() self.name = "foo"; super; end}
assert_equal("foo", c.new {Thread.current.name}.value, bug12290)
end
+ def test_thread_native_thread_id
+ omit "don't support native_thread_id" unless Thread.method_defined?(:native_thread_id)
+ assert_instance_of Integer, Thread.main.native_thread_id
+
+ th1 = Thread.start{sleep}
+
+ # newly created thread which doesn't run yet returns nil or integer
+ assert_include [NilClass, Integer], th1.native_thread_id.class
+
+ Thread.pass until th1.stop?
+
+ # After a thread starts (and execute `sleep`), it returns native_thread_id
+ native_tid = th1.native_thread_id
+ assert_instance_of Integer, native_tid if native_tid # it can be nil
+
+ th1.wakeup
+ Thread.pass while th1.alive?
+
+ # dead thread returns nil
+ assert_nil th1.native_thread_id
+ end
+
+ def test_thread_native_thread_id_across_fork_on_linux
+ begin
+ require '-test-/thread/id'
+ rescue LoadError
+ omit "this test is only for Linux"
+ else
+ extend Bug::ThreadID
+ end
+
+ parent_thread_id = Thread.main.native_thread_id
+ real_parent_thread_id = gettid
+
+ assert_equal real_parent_thread_id, parent_thread_id
+
+ child_lines = nil
+ IO.popen('-') do |pipe|
+ if pipe
+ # parent
+ child_lines = pipe.read.lines
+ else
+ # child
+ puts Thread.main.native_thread_id
+ puts gettid
+ end
+ end
+ child_thread_id = child_lines[0].chomp.to_i
+ real_child_thread_id = child_lines[1].chomp.to_i
+
+ assert_equal real_child_thread_id, child_thread_id
+ refute_equal parent_thread_id, child_thread_id
+ end
+
def test_thread_interrupt_for_killed_thread
- assert_normal_exit(<<-_end, '[Bug #8996]', timeout: 5, timeout_error: nil)
+ pend "hang-up" if /mswin|mingw/ =~ RUBY_PLATFORM
+
+ opts = { timeout: 5, timeout_error: nil }
+
+ assert_normal_exit(<<-_end, '[Bug #8996]', **opts)
Thread.report_on_exception = false
trap(:TERM){exit}
while true
t = Thread.new{sleep 0}
t.raise Interrupt
+ Thread.pass # allow t to finish
end
_end
end
def test_signal_at_join
if /mswin|mingw/ =~ RUBY_PLATFORM
- skip "can't trap a signal from another process on Windows"
+ omit "can't trap a signal from another process on Windows"
# opt = {new_pgroup: true}
end
- assert_separately([], "#{<<~"{#"}\n#{<<~'};'}")
+
+ if /freebsd/ =~ RUBY_PLATFORM
+ omit "[Bug #18613]"
+ end
+
+ assert_separately([], "#{<<~"{#"}\n#{<<~'};'}", timeout: 120)
{#
n = 1000
sig = :INT
@@ -1314,4 +1551,105 @@ q.pop
end
};
end
+
+ def test_pending_interrupt?
+ t = Thread.handle_interrupt(Exception => :never) { Thread.new { Thread.stop } }
+ t.raise(StandardError)
+ assert_equal(true, t.pending_interrupt?)
+ assert_equal(true, t.pending_interrupt?(Exception))
+ assert_equal(false, t.pending_interrupt?(ArgumentError))
+ end
+
+ def test_deadlock_backtrace
+ bug21127 = '[ruby-core:120930] [Bug #21127]'
+
+ expected_stderr = [
+ /-:12:in 'Thread#join': No live threads left. Deadlock\? \(fatal\)\n/,
+ /2 threads, 2 sleeps current:\w+ main thread:\w+\n/,
+ /\* #<Thread:\w+ sleep_forever>\n/,
+ :*,
+ /^\s*-:6:in 'Object#frame_for_deadlock_test_2'/,
+ :*,
+ /\* #<Thread:\w+ -:10 sleep_forever>\n/,
+ :*,
+ /^\s*-:2:in 'Object#frame_for_deadlock_test_1'/,
+ :*,
+ ]
+
+ assert_in_out_err([], <<-INPUT, [], expected_stderr, bug21127)
+ def frame_for_deadlock_test_1
+ yield
+ end
+
+ def frame_for_deadlock_test_2
+ yield
+ end
+
+ q = Thread::Queue.new
+ t = Thread.new { frame_for_deadlock_test_1 { 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
new file mode 100644
index 0000000000..81201f134f
--- /dev/null
+++ b/test/ruby/test_thread_cv.rb
@@ -0,0 +1,246 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'tmpdir'
+
+class TestThreadConditionVariable < Test::Unit::TestCase
+ ConditionVariable = Thread::ConditionVariable
+ Mutex = Thread::Mutex
+
+ def test_condvar_signal_and_wait
+ mutex = Thread::Mutex.new
+ condvar = Thread::ConditionVariable.new
+ result = []
+ woken = nil
+ mutex.synchronize do
+ t = Thread.new do
+ mutex.synchronize do
+ result << 1
+ condvar.signal
+ end
+ end
+
+ result << 0
+ woken = condvar.wait(mutex)
+ result << 2
+ t.join
+ end
+ assert_equal([0, 1, 2], result)
+ assert(woken)
+ end
+
+ def test_condvar_wait_exception_handling
+ # Calling wait in the only thread running should raise a ThreadError of
+ # 'stopping only thread'
+ mutex = Thread::Mutex.new
+ condvar = Thread::ConditionVariable.new
+
+ locked = false
+ thread = Thread.new do
+ Thread.current.abort_on_exception = false
+ mutex.synchronize do
+ assert_raise(Interrupt) {
+ condvar.wait(mutex)
+ }
+ locked = mutex.locked?
+ end
+ end
+
+ until thread.stop?
+ sleep(0.1)
+ end
+
+ thread.raise Interrupt, "interrupt a dead condition variable"
+ thread.join
+ assert(locked)
+ end
+
+ def test_condvar_wait_and_broadcast
+ nr_threads = 3
+ threads = Array.new
+ mutex = Thread::Mutex.new
+ condvar = Thread::ConditionVariable.new
+ result = []
+
+ nr_threads.times do |i|
+ threads[i] = Thread.new do
+ mutex.synchronize do
+ result << "C1"
+ condvar.wait mutex
+ result << "C2"
+ end
+ end
+ end
+ sleep 0.1
+ mutex.synchronize do
+ result << "P1"
+ condvar.broadcast
+ result << "P2"
+ end
+ Timeout.timeout(60) do
+ nr_threads.times do |i|
+ threads[i].join
+ end
+ end
+
+ assert_equal ["C1", "C1", "C1", "P1", "P2", "C2", "C2", "C2"], result
+ ensure
+ threads.each(&:kill)
+ threads.each(&:join)
+ end
+
+ def test_condvar_wait_deadlock
+ assert_in_out_err([], <<-INPUT, /\Afatal\nNo live threads left\. Deadlock/, [])
+ mutex = Thread::Mutex.new
+ cv = Thread::ConditionVariable.new
+
+ klass = nil
+ mesg = nil
+ begin
+ mutex.lock
+ cv.wait mutex
+ mutex.unlock
+ rescue Exception => e
+ klass = e.class
+ mesg = e.message
+ end
+ puts klass
+ print mesg
+INPUT
+ end
+
+ def test_condvar_wait_deadlock_2
+ nr_threads = 3
+ threads = Array.new
+ mutex = Thread::Mutex.new
+ condvar = Thread::ConditionVariable.new
+
+ nr_threads.times do |i|
+ if (i != 0)
+ mutex.unlock
+ end
+ threads[i] = Thread.new do
+ mutex.synchronize do
+ condvar.wait mutex
+ end
+ end
+ mutex.lock
+ end
+
+ assert_raise(Timeout::Error) do
+ Timeout.timeout(0.1) { condvar.wait mutex }
+ end
+ mutex.unlock
+ threads.each(&:kill)
+ threads.each(&:join)
+ end
+
+ def test_condvar_timed_wait
+ mutex = Thread::Mutex.new
+ condvar = Thread::ConditionVariable.new
+ timeout = 0.3
+ locked = false
+ woken = true
+
+ t0 = Time.now
+ mutex.synchronize do
+ begin
+ woken = condvar.wait(mutex, timeout)
+ ensure
+ locked = mutex.locked?
+ end
+ end
+ t1 = Time.now
+ t = t1-t0
+
+ assert_operator(timeout*0.9, :<, t)
+ assert(locked)
+ assert_nil(woken)
+ end
+
+ def test_condvar_nolock
+ mutex = Thread::Mutex.new
+ condvar = Thread::ConditionVariable.new
+
+ assert_raise(ThreadError) {condvar.wait(mutex)}
+ end
+
+ def test_condvar_nolock_2
+ mutex = Thread::Mutex.new
+ condvar = Thread::ConditionVariable.new
+
+ Thread.new do
+ assert_raise(ThreadError) {condvar.wait(mutex)}
+ end.join
+ end
+
+ def test_condvar_nolock_3
+ mutex = Thread::Mutex.new
+ condvar = Thread::ConditionVariable.new
+
+ Thread.new do
+ assert_raise(ThreadError) {condvar.wait(mutex, 0.1)}
+ end.join
+ end
+
+ def test_condvar_empty_signal
+ mutex = Thread::Mutex.new
+ condvar = Thread::ConditionVariable.new
+
+ assert_nothing_raised(Exception) { mutex.synchronize {condvar.signal} }
+ end
+
+ def test_condvar_empty_broadcast
+ mutex = Thread::Mutex.new
+ condvar = Thread::ConditionVariable.new
+
+ assert_nothing_raised(Exception) { mutex.synchronize {condvar.broadcast} }
+ end
+
+ def test_dup
+ bug9440 = '[ruby-core:59961] [Bug #9440]'
+ condvar = Thread::ConditionVariable.new
+ assert_raise(NoMethodError, bug9440) do
+ condvar.dup
+ end
+ end
+
+ (DumpableCV = ConditionVariable.dup).class_eval {remove_method :marshal_dump}
+
+ def test_dump
+ bug9674 = '[ruby-core:61677] [Bug #9674]'
+ condvar = Thread::ConditionVariable.new
+ assert_raise_with_message(TypeError, /#{ConditionVariable}/, bug9674) do
+ Marshal.dump(condvar)
+ end
+
+ condvar = DumpableCV.new
+ assert_raise(TypeError, bug9674) do
+ Marshal.dump(condvar)
+ end
+ end
+
+ def test_condvar_fork
+ mutex = Thread::Mutex.new
+ condvar = Thread::ConditionVariable.new
+ thrs = (1..10).map do
+ Thread.new { mutex.synchronize { condvar.wait(mutex) } }
+ end
+ thrs.each { 3.times { Thread.pass } }
+ pid = fork do
+ th = Thread.new do
+ mutex.synchronize { condvar.wait(mutex) }
+ :ok
+ end
+ until th.join(0.01)
+ mutex.synchronize { condvar.broadcast }
+ end
+ exit!(th.value == :ok ? 0 : 1)
+ end
+ _, s = Process.waitpid2(pid)
+ assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]'
+ until thrs.empty?
+ mutex.synchronize { condvar.broadcast }
+ thrs.delete_if { |t| t.join(0.01) }
+ end
+ end if Process.respond_to?(:fork)
+end
diff --git a/test/ruby/test_thread_queue.rb b/test/ruby/test_thread_queue.rb
new file mode 100644
index 0000000000..9a41be8b1a
--- /dev/null
+++ b/test/ruby/test_thread_queue.rb
@@ -0,0 +1,730 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'tmpdir'
+require 'timeout'
+
+class TestThreadQueue < Test::Unit::TestCase
+ Queue = Thread::Queue
+ SizedQueue = Thread::SizedQueue
+
+ def test_queue_initialized
+ assert_raise_with_message(TypeError, /\bQueue.* not initialized/) {
+ Queue.allocate.push(nil)
+ }
+ end
+
+ def test_sized_queue_initialized
+ assert_raise_with_message(TypeError, /\bSizedQueue.* not initialized/) {
+ SizedQueue.allocate.push(nil)
+ }
+ end
+
+ def test_freeze
+ assert_raise(TypeError) {
+ Queue.new.freeze
+ }
+ assert_raise(TypeError) {
+ SizedQueue.new(5).freeze
+ }
+ end
+
+ def test_queue
+ grind(5, 1000, 15, Queue)
+ end
+
+ def test_sized_queue
+ grind(5, 1000, 15, SizedQueue, 1000)
+ end
+
+ def grind(num_threads, num_objects, num_iterations, klass, *args)
+ from_workers = klass.new(*args)
+ to_workers = klass.new(*args)
+
+ workers = (1..num_threads).map {
+ Thread.new {
+ while object = to_workers.pop
+ from_workers.push object
+ end
+ }
+ }
+
+ Thread.new {
+ num_iterations.times {
+ num_objects.times { to_workers.push 99 }
+ num_objects.times { from_workers.pop }
+ }
+ }.join
+
+ # close the queue the old way to test for backwards-compatibility
+ num_threads.times { to_workers.push nil }
+ workers.each { |t| t.join }
+
+ assert_equal 0, from_workers.size
+ assert_equal 0, to_workers.size
+ end
+
+ def test_queue_initialize
+ e = Class.new do
+ include Enumerable
+ def initialize(list) @list = list end
+ def each(&block) @list.each(&block) end
+ end
+
+ all_assertions_foreach(nil,
+ [Array, "Array"],
+ [e, "Enumerable"],
+ [Struct.new(:to_a), "Array-like"],
+ ) do |a, type|
+ q = Thread::Queue.new(a.new([1,2,3]))
+ assert_equal(3, q.size, type)
+ assert_not_predicate(q, :empty?, type)
+ assert_equal(1, q.pop, type)
+ assert_equal(2, q.pop, type)
+ assert_equal(3, q.pop, type)
+ assert_predicate(q, :empty?, type)
+ end
+ end
+
+ def test_sized_queue_initialize
+ q = Thread::SizedQueue.new(1)
+ assert_equal 1, q.max
+ assert_raise(ArgumentError) { Thread::SizedQueue.new(0) }
+ assert_raise(ArgumentError) { Thread::SizedQueue.new(-1) }
+ end
+
+ def test_sized_queue_assign_max
+ q = Thread::SizedQueue.new(2)
+ assert_equal(2, q.max)
+ q.max = 1
+ assert_equal(1, q.max)
+ assert_raise(ArgumentError) { q.max = 0 }
+ assert_equal(1, q.max)
+ assert_raise(ArgumentError) { q.max = -1 }
+ assert_equal(1, q.max)
+
+ before = q.max
+ q.max.times { q << 1 }
+ t1 = Thread.new { q << 1 }
+ sleep 0.01 until t1.stop?
+ q.max = q.max + 1
+ assert_equal before + 1, q.max
+ ensure
+ t1.join if t1
+ end
+
+ def test_queue_pop_interrupt
+ q = Thread::Queue.new
+ t1 = Thread.new { q.pop }
+ sleep 0.01 until t1.stop?
+ t1.kill.join
+ assert_equal(0, q.num_waiting)
+ end
+
+ def test_queue_pop_timeout
+ q = Thread::Queue.new
+ q << 1
+ assert_equal 1, q.pop(timeout: 1)
+
+ t1 = Thread.new { q.pop(timeout: 1) }
+ assert_equal t1, t1.join(2)
+ assert_nil t1.value
+
+ t2 = Thread.new { q.pop(timeout: 0.1) }
+ assert_equal t2, t2.join(1)
+ assert_nil t2.value
+ ensure
+ t1&.kill&.join
+ t2&.kill&.join
+ end
+
+ def test_queue_pop_non_block
+ q = Thread::Queue.new
+ assert_raise_with_message(ThreadError, /empty/) do
+ q.pop(true)
+ end
+ end
+
+ def test_sized_queue_pop_interrupt
+ q = Thread::SizedQueue.new(1)
+ t1 = Thread.new { q.pop }
+ sleep 0.01 until t1.stop?
+ t1.kill.join
+ assert_equal(0, q.num_waiting)
+ end
+
+ def test_sized_queue_pop_timeout
+ q = Thread::SizedQueue.new(1)
+
+ q << 1
+ assert_equal 1, q.pop(timeout: 1)
+
+ t1 = Thread.new { q.pop(timeout: 1) }
+ assert_equal t1, t1.join(2)
+ assert_nil t1.value
+
+ t2 = Thread.new { q.pop(timeout: 0.1) }
+ assert_equal t2, t2.join(1)
+ assert_nil t2.value
+ ensure
+ t1&.kill&.join
+ t2&.kill&.join
+ end
+
+ def test_sized_queue_pop_non_block
+ q = Thread::SizedQueue.new(1)
+ assert_raise_with_message(ThreadError, /empty/) do
+ q.pop(true)
+ end
+ end
+
+ def test_sized_queue_push_timeout
+ q = Thread::SizedQueue.new(1)
+
+ q << 1
+ assert_equal 1, q.size
+
+ t1 = Thread.new { q.push(2, timeout: 1) }
+ assert_equal t1, t1.join(2)
+ assert_nil t1.value
+
+ t2 = Thread.new { q.push(2, timeout: 0.1) }
+ assert_equal t2, t2.join(1)
+ assert_nil t2.value
+ ensure
+ t1&.kill&.join
+ t2&.kill&.join
+ end
+
+ def test_sized_queue_push_interrupt
+ q = Thread::SizedQueue.new(1)
+ q.push(1)
+ assert_raise_with_message(ThreadError, /full/) do
+ q.push(2, true)
+ end
+ end
+
+ def test_sized_queue_push_non_block
+ q = Thread::SizedQueue.new(1)
+ q.push(1)
+ t1 = Thread.new { q.push(2) }
+ sleep 0.01 until t1.stop?
+ t1.kill.join
+ assert_equal(0, q.num_waiting)
+ end
+
+ def test_thr_kill
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
+ bug5343 = '[ruby-core:39634]'
+ Dir.mktmpdir {|d|
+ timeout = 60
+ total_count = 250
+ begin
+ assert_normal_exit(<<-"_eom", bug5343, timeout: timeout, chdir: d)
+ r, w = IO.pipe
+ #{total_count}.times do |i|
+ File.open("test_thr_kill_count", "w") {|f| f.puts i }
+ queue = Thread::Queue.new
+ th = Thread.start {
+ queue.push(nil)
+ r.read 1
+ }
+ queue.pop
+ th.kill
+ th.join
+ 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.#{la}"
+ end
+ }
+ end
+
+ def test_queue_push_return_value
+ q = Thread::Queue.new
+ retval = q.push(1)
+ assert_same q, retval
+ end
+
+ def test_queue_clear_return_value
+ q = Thread::Queue.new
+ retval = q.clear
+ assert_same q, retval
+ end
+
+ def test_sized_queue_clear
+ # Fill queue, then test that Thread::SizedQueue#clear wakes up all waiting threads
+ sq = Thread::SizedQueue.new(2)
+ 2.times { sq << 1 }
+
+ t1 = Thread.new do
+ sq << 1
+ end
+
+ t2 = Thread.new do
+ sq << 1
+ end
+
+ t3 = Thread.new do
+ Thread.pass
+ sq.clear
+ end
+
+ [t3, t2, t1].each(&:join)
+ assert_equal sq.length, 2
+ end
+
+ def test_sized_queue_push_return_value
+ q = Thread::SizedQueue.new(1)
+ retval = q.push(1)
+ assert_same q, retval
+ end
+
+ def test_sized_queue_clear_return_value
+ q = Thread::SizedQueue.new(1)
+ retval = q.clear
+ assert_same q, retval
+ end
+
+ def test_sized_queue_throttle
+ q = Thread::SizedQueue.new(1)
+ i = 0
+ consumer = Thread.new do
+ while q.pop
+ i += 1
+ Thread.pass
+ end
+ end
+ nprod = 4
+ npush = 100
+
+ producer = nprod.times.map do
+ Thread.new do
+ npush.times { q.push(true) }
+ end
+ end
+ producer.each(&:join)
+ q.push(nil)
+ consumer.join
+ assert_equal(nprod * npush, i)
+ end
+
+ def test_queue_thread_raise
+ q = Thread::Queue.new
+ th1 = Thread.new do
+ begin
+ q.pop
+ rescue RuntimeError
+ sleep
+ end
+ end
+ th2 = Thread.new do
+ sleep 0.1
+ q.pop
+ end
+ sleep 0.1
+ th1.raise
+ sleep 0.1
+ q << :s
+ assert_nothing_raised(Timeout::Error) do
+ Timeout.timeout(1) { th2.join }
+ end
+ ensure
+ [th1, th2].each do |th|
+ if th and th.alive?
+ th.wakeup
+ th.join
+ end
+ end
+ end
+
+ def test_dup
+ bug9440 = '[ruby-core:59961] [Bug #9440]'
+ q = Thread::Queue.new
+ assert_raise(NoMethodError, bug9440) do
+ q.dup
+ end
+ end
+
+ (DumpableQueue = Queue.dup).class_eval {remove_method :marshal_dump}
+
+ def test_dump
+ bug9674 = '[ruby-core:61677] [Bug #9674]'
+ q = Thread::Queue.new
+ assert_raise_with_message(TypeError, /#{Queue}/, bug9674) do
+ Marshal.dump(q)
+ end
+
+ sq = Thread::SizedQueue.new(1)
+ assert_raise_with_message(TypeError, /#{SizedQueue}/, bug9674) do
+ Marshal.dump(sq)
+ end
+
+ q = DumpableQueue.new
+ assert_raise(TypeError, bug9674) do
+ Marshal.dump(q)
+ end
+ end
+
+ def test_close
+ [->{Thread::Queue.new}, ->{Thread::SizedQueue.new 3}].each do |qcreate|
+ q = qcreate.call
+ assert_equal false, q.closed?
+ q << :something
+ assert_equal q, q.close
+ assert_predicate q, :closed?
+ assert_raise_with_message(ClosedQueueError, /closed/){q << :nothing}
+ assert_equal q.pop, :something
+ assert_nil q.pop
+ assert_nil q.pop
+ # non-blocking
+ assert_raise_with_message(ThreadError, /queue empty/){q.pop(non_block=true)}
+ end
+ end
+
+ # test that waiting producers are woken up on close
+ def close_wakeup( num_items, num_threads, &qcreate )
+ raise "This test won't work with num_items(#{num_items}) >= num_threads(#{num_threads})" if num_items >= num_threads
+
+ # create the Queue
+ q = yield
+ threads = num_threads.times.map{Thread.new{q.pop}}
+ num_items.times{|i| q << i}
+
+ # wait until queue empty
+ (Thread.pass; sleep 0.01) until q.size == 0
+
+ # close the queue so remaining threads will wake up
+ q.close
+
+ # wait for them to go away
+ Thread.pass until threads.all?{|thr| thr.status == false}
+
+ # check that they've gone away. Convert nil to -1 so we can sort and do the comparison
+ expected_values = [-1] * (num_threads - num_items) + num_items.times.to_a
+ assert_equal expected_values, threads.map{|thr| thr.value || -1 }.sort
+ end
+
+ def test_queue_close_wakeup
+ close_wakeup(15, 18){Thread::Queue.new}
+ end
+
+ def test_size_queue_close_wakeup
+ close_wakeup(5, 8){Thread::SizedQueue.new 9}
+ end
+
+ def test_sized_queue_one_closed_interrupt
+ q = Thread::SizedQueue.new 1
+ q << :one
+ t1 = Thread.new {
+ Thread.current.report_on_exception = false
+ q << :two
+ }
+ sleep 0.01 until t1.stop?
+ q.close
+ assert_raise(ClosedQueueError) {t1.join}
+
+ assert_equal 1, q.size
+ assert_equal :one, q.pop
+ assert_empty q
+ end
+
+ # make sure that shutdown state is handled properly by empty? for the non-blocking case
+ def test_empty_non_blocking
+ q = Thread::SizedQueue.new 3
+ 3.times{|i| q << i}
+
+ # these all block cos the queue is full
+ prod_threads = 4.times.map {|i|
+ Thread.new {
+ Thread.current.report_on_exception = false
+ q << 3+i
+ }
+ }
+ sleep 0.01 until prod_threads.all?{|thr| thr.stop?}
+
+ items = []
+ # sometimes empty? is false but pop will raise ThreadError('empty'),
+ # meaning a value is not immediately available but will be soon.
+ while prod_threads.any?(&:alive?) or !q.empty?
+ items << q.pop(true) rescue nil
+ end
+ assert_join_threads(prod_threads)
+ items.compact!
+
+ assert_equal 7.times.to_a, items.sort
+ assert q.empty?
+ end
+
+ def test_sized_queue_closed_push_non_blocking
+ q = Thread::SizedQueue.new 7
+ q.close
+ assert_raise_with_message(ClosedQueueError, /queue closed/){q.push(non_block=true)}
+ end
+
+ def test_blocked_pushers
+ q = Thread::SizedQueue.new 3
+ prod_threads = 6.times.map do |i|
+ thr = Thread.new{
+ Thread.current.report_on_exception = false
+ q << i
+ }
+ thr[:pc] = i
+ thr
+ end
+
+ # wait until some producer threads have finished, and the other 3 are blocked
+ sleep 0.01 while prod_threads.reject{|t| t.status}.count < 3
+ # this would ensure that all producer threads call push before close
+ # sleep 0.01 while prod_threads.select{|t| t.status == 'sleep'}.count < 3
+ q.close
+
+ # more than prod_threads
+ cons_threads = 10.times.map do |i|
+ thr = Thread.new{q.pop}; thr[:pc] = i; thr
+ end
+
+ # values that came from the queue
+ popped_values = cons_threads.map &:value
+
+ # wait untl all threads have finished
+ sleep 0.01 until prod_threads.find_all{|t| t.status}.count == 0
+
+ # pick only the producer threads that got in before close
+ successful_prod_threads = prod_threads.reject{|thr| thr.status == nil}
+ assert_nothing_raised{ successful_prod_threads.map(&:value) }
+
+ # the producer threads that tried to push after q.close should all fail
+ unsuccessful_prod_threads = prod_threads - successful_prod_threads
+ unsuccessful_prod_threads.each do |thr|
+ assert_raise(ClosedQueueError){ thr.value }
+ end
+
+ assert_equal cons_threads.size, popped_values.size
+ assert_equal 0, q.size
+
+ # check that consumer threads with values match producers that called push before close
+ assert_equal successful_prod_threads.map{|thr| thr[:pc]}, popped_values.compact.sort
+ assert_nil q.pop
+ end
+
+ def test_deny_pushers
+ [->{Thread::Queue.new}, ->{Thread::SizedQueue.new 3}].each do |qcreate|
+ q = qcreate[]
+ synq = Thread::Queue.new
+ prod_threads = 20.times.map do |i|
+ Thread.new {
+ synq.pop
+ assert_raise(ClosedQueueError) {
+ q << i
+ }
+ }
+ end
+ q.close
+ synq.close # start producer threads
+
+ prod_threads.each(&:join)
+ end
+ end
+
+ # size should account for waiting pushers during shutdown
+ def sized_queue_size_close
+ q = Thread::SizedQueue.new 4
+ 4.times{|i| q << i}
+ Thread.new{ q << 5 }
+ Thread.new{ q << 6 }
+ assert_equal 4, q.size
+ assert_equal 4, q.items
+ q.close
+ assert_equal 6, q.size
+ assert_equal 4, q.items
+ end
+
+ def test_blocked_pushers_empty
+ q = Thread::SizedQueue.new 3
+ prod_threads = 6.times.map do |i|
+ Thread.new{
+ Thread.current.report_on_exception = false
+ q << i
+ }
+ end
+
+ # this ensures that all producer threads call push before close
+ sleep 0.01 while prod_threads.select{|t| t.status == 'sleep'}.count < 3
+ q.close
+
+ ary = []
+ until q.empty?
+ ary << q.pop
+ end
+ assert_equal 0, q.size
+
+ assert_equal 3, ary.size
+ ary.each{|e| assert_include [0,1,2,3,4,5], e}
+ assert_nil q.pop
+
+ prod_threads.each{|t|
+ begin
+ t.join
+ rescue
+ end
+ }
+ end
+
+ # test thread wakeup on one-element SizedQueue with close
+ def test_one_element_sized_queue
+ q = Thread::SizedQueue.new 1
+ t = Thread.new{ q.pop }
+ q.close
+ assert_nil t.value
+ end
+
+ def test_close_twice
+ [->{Thread::Queue.new}, ->{Thread::SizedQueue.new 3}].each do |qcreate|
+ q = qcreate[]
+ q.close
+ assert_nothing_raised(ClosedQueueError){q.close}
+ end
+ end
+
+ def test_queue_close_multi_multi
+ q = Thread::SizedQueue.new rand(800..1200)
+
+ count_items = rand(3000..5000)
+ count_producers = rand(10..20)
+
+ # ensure threads do not start running too soon and complete before we check status
+ mutex = Mutex.new
+ mutex.lock
+
+ producers = count_producers.times.map do
+ Thread.new do
+ mutex.lock
+ mutex.unlock
+ count_items.times{|i| q << [i,"#{i} for #{Thread.current.inspect}"]}
+ end
+ end
+
+ consumers = rand(7..12).times.map do
+ Thread.new do
+ count = 0
+ while e = q.pop
+ i, st = e
+ count += 1 if i.is_a?(Integer) && st.is_a?(String)
+ end
+ count
+ end
+ end
+
+ # No dead or finished threads, give up to 10 seconds to start running
+ t = Time.now
+ Thread.pass until Time.now - t > 10 || (consumers + producers).all?{|thr| thr.status.to_s =~ /\A(?:run|sleep)\z/}
+
+ assert (consumers + producers).all?{|thr| thr.status.to_s =~ /\A(?:run|sleep)\z/}, 'no threads running'
+
+ mutex.unlock
+
+ # just exercising the concurrency of the support methods.
+ counter = Thread.new do
+ until q.closed? && q.empty?
+ raise if q.size > q.max
+ # otherwise this exercise causes too much contention on the lock
+ sleep 0.01
+ end
+ end
+
+ producers.each &:join
+ q.close
+
+ # results not randomly distributed. Not sure why.
+ # consumers.map{|thr| thr.value}.each do |x|
+ # assert_not_equal 0, x
+ # end
+
+ all_items_count = consumers.map{|thr| thr.value}.inject(:+)
+ assert_equal count_items * count_producers, all_items_count
+
+ # don't leak this thread
+ assert_nothing_raised{counter.join}
+ end
+
+ def test_queue_with_trap
+ if ENV['APPVEYOR'] == 'True' && RUBY_PLATFORM.match?(/mswin/)
+ omit 'This test fails too often on AppVeyor vs140'
+ end
+ if RUBY_PLATFORM.match?(/mingw/)
+ omit 'This test fails too often on MinGW'
+ end
+
+ assert_in_out_err([], <<-INPUT, %w(INT INT exit), [])
+ q = Thread::Queue.new
+ trap(:INT){
+ q.push 'INT'
+ }
+ Thread.new{
+ loop{
+ Process.kill :INT, $$
+ sleep 0.1
+ }
+ }
+ puts q.pop
+ puts q.pop
+ puts 'exit'
+ INPUT
+ end
+
+ def test_fork_while_queue_waiting
+ q = Thread::Queue.new
+ sq = Thread::SizedQueue.new(1)
+ thq = Thread.new { q.pop }
+ thsq = Thread.new { sq.pop }
+ Thread.pass until thq.stop? && thsq.stop?
+
+ pid = fork do
+ exit!(1) if q.num_waiting != 0
+ exit!(2) if sq.num_waiting != 0
+ exit!(6) unless q.empty?
+ exit!(7) unless sq.empty?
+ q.push :child_q
+ sq.push :child_sq
+ exit!(3) if q.pop != :child_q
+ exit!(4) if sq.pop != :child_sq
+ exit!(0)
+ end
+ _, s = Process.waitpid2(pid)
+ assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]'
+
+ q.push :thq
+ sq.push :thsq
+ assert_equal :thq, thq.value
+ assert_equal :thsq, thsq.value
+
+ sq.push(1)
+ th = Thread.new { q.pop; sq.pop }
+ thsq = Thread.new { sq.push(2) }
+ Thread.pass until th.stop? && thsq.stop?
+ pid = fork do
+ exit!(1) if q.num_waiting != 0
+ exit!(2) if sq.num_waiting != 0
+ exit!(3) unless q.empty?
+ exit!(4) if sq.empty?
+ exit!(5) if sq.pop != 1
+ exit!(0)
+ end
+ _, s = Process.waitpid2(pid)
+ assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]'
+
+ assert_predicate thsq, :stop?
+ assert_equal 1, sq.pop
+ assert_same sq, thsq.value
+ q.push('restart th')
+ assert_equal 2, th.value
+ end if Process.respond_to?(:fork)
+end
diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb
index 5ddcc9dcfe..333edb8021 100644
--- a/test/ruby/test_time.rb
+++ b/test/ruby/test_time.rb
@@ -7,7 +7,6 @@ require 'delegate'
class TestTime < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
- $VERBOSE = nil
end
def teardown
@@ -38,8 +37,11 @@ class TestTime < Test::Unit::TestCase
end
def test_new
+ assert_equal(Time.new(2000,1,1,0,0,0), Time.new(2000))
+ assert_equal(Time.new(2000,2,1,0,0,0), Time.new("2000", "Feb"))
assert_equal(Time.utc(2000,2,10), Time.new(2000,2,10, 11,0,0, 3600*11))
assert_equal(Time.utc(2000,2,10), Time.new(2000,2,9, 13,0,0, -3600*11))
+ assert_equal(Time.utc(2000,2,29,23,0,0), Time.new(2000, 3, 1, 0, 0, 0, 3600))
assert_equal(Time.utc(2000,2,10), Time.new(2000,2,10, 11,0,0, "+11:00"))
assert_equal(Rational(1,2), Time.new(2000,2,10, 11,0,5.5, "+11:00").subsec)
bug4090 = '[ruby-dev:42631]'
@@ -47,6 +49,121 @@ class TestTime < Test::Unit::TestCase
t = Time.new(*tm, "-12:00")
assert_equal([2001,2,28,23,59,30,-43200], [t.year, t.month, t.mday, t.hour, t.min, t.sec, t.gmt_offset], bug4090)
assert_raise(ArgumentError) { Time.new(2000,1,1, 0,0,0, "+01:60") }
+ msg = /invalid value for Integer/
+ assert_raise_with_message(ArgumentError, msg) { Time.new(2021, 1, 1, "+09:99") }
+ assert_raise_with_message(ArgumentError, msg) { Time.new(2021, 1, "+09:99") }
+ assert_raise_with_message(ArgumentError, msg) { Time.new(2021, "+09:99") }
+
+ assert_equal([0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], Time.new(2000, 1, 1, 0, 0, 0, "-00:00").to_a)
+ assert_equal([0, 0, 0, 2, 1, 2000, 0, 2, false, "UTC"], Time.new(2000, 1, 1, 24, 0, 0, "-00:00").to_a)
+ end
+
+ def test_new_from_string
+ assert_raise(ArgumentError) { Time.new(2021, 1, 1, "+09:99") }
+
+ t = Time.utc(2020, 12, 24, 15, 56, 17)
+ assert_equal(t, Time.new("2020-12-24T15:56:17Z"))
+ assert_equal(t, Time.new("2020-12-25 00:56:17 +09:00"))
+ assert_equal(t, Time.new("2020-12-25 00:57:47 +09:01:30"))
+ assert_equal(t, Time.new("2020-12-25 00:56:17 +0900"))
+ assert_equal(t, Time.new("2020-12-25 00:57:47 +090130"))
+ assert_equal(t, Time.new("2020-12-25T00:56:17+09:00"))
+ assert_raise_with_message(ArgumentError, /missing sec part/) {
+ Time.new("2020-12-25 00:56 +09:00")
+ }
+ assert_raise_with_message(ArgumentError, /missing min part/) {
+ Time.new("2020-12-25 00 +09:00")
+ }
+
+ assert_equal(Time.new(2021), Time.new("2021"))
+ assert_equal(Time.new(2021, 12, 25, in: "+09:00"), Time.new("2021-12-25+09:00"))
+ assert_equal(Time.new(2021, 12, 25, in: "+09:00"), Time.new("2021-12-25+09:00", in: "-01:00"))
+
+ assert_equal(0.123456r, Time.new("2021-12-25 00:00:00.123456 +09:00").subsec)
+ assert_equal(0.123456789r, Time.new("2021-12-25 00:00:00.123456789876 +09:00").subsec)
+ assert_equal(0.123r, Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 3).subsec)
+ assert_equal(0.123456789876r, Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: nil).subsec)
+ assert_raise_with_message(ArgumentError, "subsecond expected after dot: 00:56:17. ") {
+ Time.new("2020-12-25 00:56:17. +0900")
+ }
+ assert_raise_with_message(ArgumentError, /year must be 4 or more/) {
+ Time.new("021-12-25 00:00:00.123456 +09:00")
+ }
+ assert_raise_with_message(ArgumentError, /fraction min is.*56\./) {
+ Time.new("2020-12-25 00:56. +0900")
+ }
+ assert_raise_with_message(ArgumentError, /fraction hour is.*00\./) {
+ Time.new("2020-12-25 00. +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits sec.*:017\b/) {
+ Time.new("2020-12-25 00:56:017 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits sec.*:9\b/) {
+ Time.new("2020-12-25 00:56:9 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /sec out of range/) {
+ Time.new("2020-12-25 00:56:64 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits min.*:056\b/) {
+ Time.new("2020-12-25 00:056:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits min.*:5\b/) {
+ Time.new("2020-12-25 00:5:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /min out of range/) {
+ Time.new("2020-12-25 00:64:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits hour.*\b000\b/) {
+ Time.new("2020-12-25 000:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits hour.*\b0\b/) {
+ Time.new("2020-12-25 0:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /hour out of range/) {
+ Time.new("2020-12-25 33:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits mday.*\b025\b/) {
+ Time.new("2020-12-025 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits mday.*\b5\b/) {
+ Time.new("2020-12-5 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /mday out of range/) {
+ Time.new("2020-12-33 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits mon.*\b012\b/) {
+ Time.new("2020-012-25 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits mon.*\b1\b/) {
+ Time.new("2020-1-25 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /mon out of range/) {
+ Time.new("2020-17-25 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /no time information/) {
+ Time.new("2020-12")
+ }
+ assert_raise_with_message(ArgumentError, /no time information/) {
+ Time.new("2020-12-02")
+ }
+ assert_raise_with_message(ArgumentError, /can't parse/) {
+ Time.new(" 2020-12-02 00:00:00")
+ }
+ assert_raise_with_message(ArgumentError, /can't parse/) {
+ Time.new("2020-12-02 00:00:00 ")
+ }
+ assert_raise_with_message(ArgumentError, /utc_offset/) {
+ Time.new("2020-12-25 00:00:00 +0960")
+ }
+ assert_raise_with_message(ArgumentError, /utc_offset/) {
+ Time.new("2020-12-25 00:00:00 +09:60")
+ }
+ assert_raise_with_message(ArgumentError, /utc_offset/) {
+ Time.new("2020-12-25 00:00:00 +090060")
+ }
+ assert_raise_with_message(ArgumentError, /utc_offset/) {
+ Time.new("2020-12-25 00:00:00 +09:00:60")
+ }
end
def test_time_add()
@@ -108,6 +225,10 @@ class TestTime < Test::Unit::TestCase
assert_equal(78796800, Time.utc(1972, 7, 1, 0, 0, 0).tv_sec)
assert_equal(78796801, Time.utc(1972, 7, 1, 0, 0, 1).tv_sec)
assert_equal(946684800, Time.utc(2000, 1, 1, 0, 0, 0).tv_sec)
+
+ # Giveup to try 2nd test because some state is changed.
+ omit if Test::Unit::Runner.current_repeat_count > 0
+
assert_equal(0x7fffffff, Time.utc(2038, 1, 19, 3, 14, 7).tv_sec)
assert_equal(0x80000000, Time.utc(2038, 1, 19, 3, 14, 8).tv_sec)
else
@@ -173,7 +294,7 @@ class TestTime < Test::Unit::TestCase
assert_equal(10000, Time.at(0.00001).nsec)
assert_equal(3000, Time.at(0.000003).nsec)
assert_equal(200, Time.at(0.0000002r).nsec)
- assert_equal(199, Time.at(0.0000002).nsec)
+ assert_in_delta(200, Time.at(0.0000002).nsec, 1, "should be within FP error")
assert_equal(10, Time.at(0.00000001).nsec)
assert_equal(1, Time.at(0.000000001).nsec)
@@ -236,6 +357,10 @@ class TestTime < Test::Unit::TestCase
assert_equal(1, Time.at(0, 0.001).nsec)
end
+ def test_at_splat
+ assert_equal(Time.at(1, 2), Time.at(*[1, 2]))
+ end
+
def test_at_with_unit
assert_equal(123456789, Time.at(0, 123456789, :nanosecond).nsec)
assert_equal(123456789, Time.at(0, 123456789, :nsec).nsec)
@@ -326,7 +451,7 @@ class TestTime < Test::Unit::TestCase
end
def test_marshal_zone_gc
- assert_separately(%w(--disable-gems), <<-'end;', timeout: 30)
+ assert_separately([], <<-'end;', timeout: 30)
ENV["TZ"] = "JST-9"
s = Marshal.dump(Time.now)
t = Marshal.load(s)
@@ -375,6 +500,21 @@ class TestTime < Test::Unit::TestCase
end
end
+ def test_marshal_broken_month
+ data = "\x04\x08u:\tTime\r\x20\x7c\x1e\xc0\x00\x00\x00\x00"
+ assert_equal(Time.utc(2022, 4, 1), Marshal.load(data))
+ end
+
+ def test_marshal_distant_past
+ assert_marshal_roundtrip(Time.utc(1890, 1, 1))
+ assert_marshal_roundtrip(Time.utc(-4.5e9, 1, 1))
+ end
+
+ def test_marshal_distant_future
+ assert_marshal_roundtrip(Time.utc(30000, 1, 1))
+ assert_marshal_roundtrip(Time.utc(5.67e9, 4, 8))
+ end
+
def test_at3
t2000 = get_t2000
assert_equal(t2000, Time.at(t2000))
@@ -407,8 +547,10 @@ class TestTime < Test::Unit::TestCase
def o.to_int; 0; end
def o.to_r; nil; end
assert_raise(TypeError) { Time.gm(2000, 1, 1, 0, 0, o, :foo, :foo) }
+ class << o; remove_method(:to_r); end
def o.to_r; ""; end
assert_raise(TypeError) { Time.gm(2000, 1, 1, 0, 0, o, :foo, :foo) }
+ class << o; remove_method(:to_r); end
def o.to_r; Rational(11); end
assert_equal(11, Time.gm(2000, 1, 1, 0, 0, o).sec)
o = Object.new
@@ -421,6 +563,10 @@ class TestTime < Test::Unit::TestCase
assert_equal(-4427700000, Time.utc(-4427700000,12,1).year)
assert_equal(-2**30+10, Time.utc(-2**30+10,1,1).year)
+
+ assert_raise(ArgumentError) { Time.gm(2000, 1, -1) }
+ assert_raise(ArgumentError) { Time.gm(2000, 1, 2**30 + 1) }
+ assert_raise(ArgumentError) { Time.gm(2000, 1, -2**30 + 1) }
end
def test_time_interval
@@ -543,6 +689,34 @@ class TestTime < Test::Unit::TestCase
assert_equal(Time.at(946684800).getlocal.to_s, Time.at(946684800).to_s)
end
+ def test_inspect
+ t2000 = get_t2000
+ assert_equal("2000-01-01 00:00:00 UTC", t2000.inspect)
+ assert_equal(Encoding::US_ASCII, t2000.inspect.encoding)
+ assert_kind_of(String, Time.at(946684800).getlocal.inspect)
+ assert_equal(Time.at(946684800).getlocal.inspect, Time.at(946684800).inspect)
+
+ t2000 = get_t2000 + 1/10r
+ assert_equal("2000-01-01 00:00:00.1 UTC", t2000.inspect)
+ t2000 = get_t2000 + 1/1000000000r
+ assert_equal("2000-01-01 00:00:00.000000001 UTC", t2000.inspect)
+ t2000 = get_t2000 + 1/10000000000r
+ assert_equal("2000-01-01 00:00:00 1/10000000000 UTC", t2000.inspect)
+ t2000 = get_t2000 + 0.1
+ assert_equal("2000-01-01 00:00:00 3602879701896397/36028797018963968 UTC", t2000.inspect)
+
+ t2000 = get_t2000
+ t2000 = t2000.localtime(9*3600)
+ assert_equal("2000-01-01 09:00:00 +0900", t2000.inspect)
+
+ t2000 = get_t2000.localtime(9*3600) + 1/10r
+ assert_equal("2000-01-01 09:00:00.1 +0900", t2000.inspect)
+
+ t2000 = get_t2000
+ assert_equal("2000-01-01 09:12:00 +0912", t2000.localtime(9*3600+12*60).inspect)
+ assert_equal("2000-01-01 09:12:34 +091234", t2000.localtime(9*3600+12*60+34).inspect)
+ end
+
def assert_zone_encoding(time)
zone = time.zone
assert_predicate(zone, :valid_encoding?)
@@ -556,15 +730,18 @@ class TestTime < Test::Unit::TestCase
def test_zone
assert_zone_encoding Time.now
+ t = Time.now.utc
+ assert_equal("UTC", t.zone)
+ assert_nil(t.getlocal(0).zone)
+ assert_nil(t.getlocal("+02:00").zone)
end
- def test_plus_minus_succ
+ def test_plus_minus
t2000 = get_t2000
# assert_raise(RangeError) { t2000 + 10000000000 }
# assert_raise(RangeError) t2000 - 3094168449 }
# assert_raise(RangeError) { t2000 + 1200798848 }
assert_raise(TypeError) { t2000 + Time.now }
- assert_equal(t2000 + 1, t2000.succ)
end
def test_plus_type
@@ -667,7 +844,9 @@ class TestTime < Test::Unit::TestCase
assert_equal("12:00:00 AM", t2000.strftime("%r"))
assert_equal("Sat 2000-01-01T00:00:00", t2000.strftime("%3a %FT%T"))
- assert_equal("", t2000.strftime(""))
+ assert_warning(/strftime called with empty format string/) do
+ assert_equal("", t2000.strftime(""))
+ end
assert_equal("foo\0bar\x0000\x0000\x0000", t2000.strftime("foo\0bar\0%H\0%M\0%S"))
assert_equal("foo" * 1000, t2000.strftime("foo" * 1000))
@@ -847,6 +1026,13 @@ class TestTime < Test::Unit::TestCase
assert_equal(8192, Time.now.strftime('%8192z').size)
end
+ def test_strftime_wide_precision
+ t2000 = get_t2000
+ s = t2000.strftime("%28c")
+ assert_equal(28, s.size)
+ assert_equal(t2000.strftime("%c"), s.strip)
+ end
+
def test_strfimte_zoneoffset
t2000 = get_t2000
t = t2000.getlocal("+09:00:00")
@@ -860,6 +1046,17 @@ class TestTime < Test::Unit::TestCase
assert_equal("+09:00", t.strftime("%:z"))
assert_equal("+09:00:01", t.strftime("%::z"))
assert_equal("+09:00:01", t.strftime("%:::z"))
+
+ assert_equal("+0000", t2000.strftime("%z"))
+ assert_equal("-0000", t2000.strftime("%-z"))
+ assert_equal("-00:00", t2000.strftime("%-:z"))
+ assert_equal("-00:00:00", t2000.strftime("%-::z"))
+
+ t = t2000.getlocal("+00:00")
+ assert_equal("+0000", t.strftime("%z"))
+ assert_equal("+0000", t.strftime("%-z"))
+ assert_equal("+00:00", t.strftime("%-:z"))
+ assert_equal("+00:00:00", t.strftime("%-::z"))
end
def test_strftime_padding
@@ -966,6 +1163,63 @@ class TestTime < Test::Unit::TestCase
}
end
+ def test_floor
+ t = Time.utc(1999,12,31, 23,59,59)
+ t2 = (t+0.4).floor
+ assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a)
+ assert_equal(0, t2.subsec)
+ t2 = (t+0.49).floor
+ assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a)
+ assert_equal(0, t2.subsec)
+ t2 = (t+0.5).floor
+ assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a)
+ assert_equal(0, t2.subsec)
+ t2 = (t+1.4).floor
+ assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a)
+ assert_equal(0, t2.subsec)
+ t2 = (t+1.49).floor
+ assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a)
+ assert_equal(0, t2.subsec)
+ t2 = (t+1.5).floor
+ assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a)
+ assert_equal(0, t2.subsec)
+
+ t2 = (t+0.123456789).floor(4)
+ assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a)
+ assert_equal(Rational(1234,10000), t2.subsec)
+ end
+
+ def test_ceil
+ t = Time.utc(1999,12,31, 23,59,59)
+ t2 = (t+0.4).ceil
+ assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a)
+ assert_equal(0, t2.subsec)
+ t2 = (t+0.49).ceil
+ assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a)
+ assert_equal(0, t2.subsec)
+ t2 = (t+0.5).ceil
+ assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a)
+ assert_equal(0, t2.subsec)
+ t2 = (t+1.4).ceil
+ assert_equal([1,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a)
+ assert_equal(0, t2.subsec)
+ t2 = (t+1.49).ceil
+ assert_equal([1,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a)
+ assert_equal(0, t2.subsec)
+ t2 = (t+1.5).ceil
+ assert_equal([1,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a)
+ assert_equal(0, t2.subsec)
+
+ t2 = (t+0.123456789).ceil(4)
+ assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a)
+ assert_equal(Rational(1235,10000), t2.subsec)
+
+ time = Time.utc(2016, 4, 23, 0, 0, 0.123456789r)
+ assert_equal(time, time.ceil(9))
+ assert_equal(time, time.ceil(10))
+ assert_equal(time, time.ceil(11))
+ end
+
def test_getlocal_dont_share_eigenclass
bug5012 = "[ruby-dev:44071]"
@@ -1039,6 +1293,9 @@ class TestTime < Test::Unit::TestCase
end
def test_2038
+ # Giveup to try 2nd test because some state is changed.
+ omit if Test::Unit::Runner.current_repeat_count > 0
+
if no_leap_seconds?
assert_equal(0x80000000, Time.utc(2038, 1, 19, 3, 14, 8).tv_sec)
end
@@ -1097,6 +1354,30 @@ class TestTime < Test::Unit::TestCase
}
end
+ def test_getlocal_utc
+ t = Time.gm(2000)
+ assert_equal [00, 00, 00, 1, 1, 2000], (t1 = t.getlocal("UTC")).to_a[0, 6]
+ assert_predicate t1, :utc?
+ assert_equal [00, 00, 00, 1, 1, 2000], (t1 = t.getlocal("-0000")).to_a[0, 6]
+ assert_predicate t1, :utc?
+ assert_equal [00, 00, 00, 1, 1, 2000], (t1 = t.getlocal("+0000")).to_a[0, 6]
+ assert_not_predicate t1, :utc?
+ end
+
+ def test_getlocal_utc_offset
+ t = Time.gm(2000)
+ assert_equal [00, 30, 21, 31, 12, 1999], t.getlocal("-02:30").to_a[0, 6]
+ assert_equal [00, 00, 9, 1, 1, 2000], t.getlocal("+09:00").to_a[0, 6]
+ assert_equal [20, 29, 21, 31, 12, 1999], t.getlocal("-02:30:40").to_a[0, 6]
+ assert_equal [35, 10, 9, 1, 1, 2000], t.getlocal("+09:10:35").to_a[0, 6]
+ assert_equal [00, 30, 21, 31, 12, 1999], t.getlocal("-0230").to_a[0, 6]
+ assert_equal [00, 00, 9, 1, 1, 2000], t.getlocal("+0900").to_a[0, 6]
+ assert_equal [20, 29, 21, 31, 12, 1999], t.getlocal("-023040").to_a[0, 6]
+ assert_equal [35, 10, 9, 1, 1, 2000], t.getlocal("+091035").to_a[0, 6]
+ assert_raise(ArgumentError) {t.getlocal("-02:3040")}
+ assert_raise(ArgumentError) {t.getlocal("+0910:35")}
+ end
+
def test_getlocal_nil
now = Time.now
now2 = nil
@@ -1126,17 +1407,6 @@ class TestTime < Test::Unit::TestCase
assert_equal("366", t.strftime("%j"))
end
- def test_strftime_no_hidden_garbage
- fmt = %w(Y m d).map { |x| "%#{x}" }.join('-') # defeats optimization
- t = Time.at(0).getutc
- ObjectSpace.count_objects(res = {}) # creates strings on first call
- before = ObjectSpace.count_objects(res)[:T_STRING]
- val = t.strftime(fmt)
- after = ObjectSpace.count_objects(res)[:T_STRING]
- assert_equal before + 1, after, 'only new string is the created one'
- assert_equal '1970-01-01', val
- end
-
def test_num_exact_error
bad = EnvUtil.labeled_class("BadValue").new
x = EnvUtil.labeled_class("Inexact") do
@@ -1149,18 +1419,100 @@ class TestTime < Test::Unit::TestCase
def test_memsize
# Time objects are common in some code, try to keep them small
- skip "Time object size test" if /^(?:i.?86|x86_64)-linux/ !~ RUBY_PLATFORM
+ omit "Time object size test" if /^(?:i.?86|x86_64)-linux/ !~ RUBY_PLATFORM
+ omit "GC is in debug" if GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] > 0
+ omit "memsize is not accurate due to using malloc_usable_size" if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ omit "Only run this test on 64-bit" if RbConfig::SIZEOF["void*"] != 8
+
require 'objspace'
t = Time.at(0)
- size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
- case size
- when 20 then expect = 50
- when 40 then expect = 86
- else
- flunk "Unsupported RVALUE_SIZE=#{size}, update test_memsize"
- end
- assert_equal expect, ObjectSpace.memsize_of(t)
+ sizeof_timew =
+ if RbConfig::SIZEOF.key?("uint64_t") && RbConfig::SIZEOF["long"] * 2 <= RbConfig::SIZEOF["uint64_t"]
+ RbConfig::SIZEOF["uint64_t"]
+ else
+ RbConfig::SIZEOF["void*"] # Same size as VALUE
+ end
+ sizeof_vtm = RbConfig::SIZEOF["void*"] * 4 + 8
+ expect = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + sizeof_timew + sizeof_vtm
+ assert_operator ObjectSpace.memsize_of(t), :<=, expect
rescue LoadError => e
- skip "failed to load objspace: #{e.message}"
+ omit "failed to load objspace: #{e.message}"
+ end
+
+ def test_deconstruct_keys
+ t = in_timezone('JST-9') { Time.local(2022, 10, 16, 14, 1, 30, 500) }
+ assert_equal(
+ {year: 2022, month: 10, day: 16, wday: 0, yday: 289,
+ hour: 14, min: 1, sec: 30, subsec: 1/2000r, dst: false, zone: 'JST'},
+ t.deconstruct_keys(nil)
+ )
+
+ assert_equal(
+ {year: 2022, month: 10, sec: 30},
+ t.deconstruct_keys(%i[year month sec nonexistent])
+ )
+ end
+
+ def test_parse_zero_bigint
+ assert_equal 0, Time.new("2020-10-28T16:48:07.000Z").nsec, '[Bug #19390]'
+ end
+
+ def test_xmlschema_encode
+ [:xmlschema, :iso8601].each do |method|
+ bug6100 = '[ruby-core:42997]'
+
+ t = Time.utc(2001, 4, 17, 19, 23, 17, 300000)
+ assert_equal("2001-04-17T19:23:17Z", t.__send__(method))
+ assert_equal("2001-04-17T19:23:17.3Z", t.__send__(method, 1))
+ assert_equal("2001-04-17T19:23:17.300000Z", t.__send__(method, 6))
+ assert_equal("2001-04-17T19:23:17.3000000Z", t.__send__(method, 7))
+ assert_equal("2001-04-17T19:23:17.3Z", t.__send__(method, 1.9), bug6100)
+
+ t = Time.utc(2001, 4, 17, 19, 23, 17, 123456)
+ assert_equal("2001-04-17T19:23:17.1234560Z", t.__send__(method, 7))
+ assert_equal("2001-04-17T19:23:17.123456Z", t.__send__(method, 6))
+ assert_equal("2001-04-17T19:23:17.12345Z", t.__send__(method, 5))
+ assert_equal("2001-04-17T19:23:17.1Z", t.__send__(method, 1))
+ assert_equal("2001-04-17T19:23:17.1Z", t.__send__(method, 1.9), bug6100)
+
+ t = Time.at(2.quo(3)).getlocal("+09:00")
+ assert_equal("1970-01-01T09:00:00.666+09:00", t.__send__(method, 3))
+ assert_equal("1970-01-01T09:00:00.6666666666+09:00", t.__send__(method, 10))
+ assert_equal("1970-01-01T09:00:00.66666666666666666666+09:00", t.__send__(method, 20))
+ assert_equal("1970-01-01T09:00:00.6+09:00", t.__send__(method, 1.1), bug6100)
+ assert_equal("1970-01-01T09:00:00.666+09:00", t.__send__(method, 3.2), bug6100)
+
+ t = Time.at(123456789.quo(9999999999)).getlocal("+09:00")
+ assert_equal("1970-01-01T09:00:00.012+09:00", t.__send__(method, 3))
+ assert_equal("1970-01-01T09:00:00.012345678+09:00", t.__send__(method, 9))
+ assert_equal("1970-01-01T09:00:00.0123456789+09:00", t.__send__(method, 10))
+ assert_equal("1970-01-01T09:00:00.0123456789012345678+09:00", t.__send__(method, 19))
+ assert_equal("1970-01-01T09:00:00.01234567890123456789+09:00", t.__send__(method, 20))
+ assert_equal("1970-01-01T09:00:00.012+09:00", t.__send__(method, 3.8), bug6100)
+
+ t = Time.utc(1)
+ assert_equal("0001-01-01T00:00:00Z", t.__send__(method))
+
+ begin
+ Time.at(-1)
+ rescue ArgumentError
+ # ignore
+ else
+ t = Time.utc(1960, 12, 31, 23, 0, 0, 123456)
+ assert_equal("1960-12-31T23:00:00.123456Z", t.__send__(method, 6))
+ end
+
+ t = get_t2000.getlocal("-09:30") # Pacific/Marquesas
+ assert_equal("1999-12-31T14:30:00-09:30", t.__send__(method))
+
+ assert_equal("10000-01-01T00:00:00Z", Time.utc(10000).__send__(method))
+ assert_equal("9999-01-01T00:00:00Z", Time.utc(9999).__send__(method))
+ assert_equal("0001-01-01T00:00:00Z", Time.utc(1).__send__(method)) # 1 AD
+ assert_equal("0000-01-01T00:00:00Z", Time.utc(0).__send__(method)) # 1 BC
+ assert_equal("-0001-01-01T00:00:00Z", Time.utc(-1).__send__(method)) # 2 BC
+ assert_equal("-0004-01-01T00:00:00Z", Time.utc(-4).__send__(method)) # 5 BC
+ assert_equal("-9999-01-01T00:00:00Z", Time.utc(-9999).__send__(method))
+ assert_equal("-10000-01-01T00:00:00Z", Time.utc(-10000).__send__(method))
+ end
end
end
diff --git a/test/ruby/test_time_tz.rb b/test/ruby/test_time_tz.rb
index bfe9b4eef3..473c3cabcb 100644
--- a/test/ruby/test_time_tz.rb
+++ b/test/ruby/test_time_tz.rb
@@ -6,9 +6,9 @@ class TestTimeTZ < Test::Unit::TestCase
has_lisbon_tz = true
force_tz_test = ENV["RUBY_FORCE_TIME_TZ_TEST"] == "yes"
case RUBY_PLATFORM
- when /linux/
+ when /darwin|linux/
force_tz_test = true
- when /darwin|freebsd/
+ when /freebsd|openbsd/
has_lisbon_tz = false
force_tz_test = true
end
@@ -94,6 +94,9 @@ class TestTimeTZ < Test::Unit::TestCase
CORRECT_KIRITIMATI_SKIP_1994 = with_tz("Pacific/Kiritimati") {
Time.local(1994, 12, 31, 0, 0, 0).year == 1995
}
+ CORRECT_SINGAPORE_1982 = with_tz("Asia/Singapore") {
+ "2022g" if Time.local(1981, 12, 31, 23, 59, 59).utc_offset == 8*3600
+ }
def time_to_s(t)
t.to_s
@@ -111,7 +114,7 @@ class TestTimeTZ < Test::Unit::TestCase
t = with_tz("America/Los_Angeles") {
Time.local(2000, 1, 1)
}
- skip "force_tz_test is false on this environment" unless t
+ omit "force_tz_test is false on this environment" unless t
z1 = t.zone
z2 = with_tz(tz="Asia/Singapore") {
t.localtime.zone
@@ -139,9 +142,12 @@ class TestTimeTZ < Test::Unit::TestCase
def test_asia_singapore
with_tz(tz="Asia/Singapore") {
- assert_time_constructor(tz, "1981-12-31 23:59:59 +0730", :local, [1981,12,31,23,59,59])
- assert_time_constructor(tz, "1982-01-01 00:30:00 +0800", :local, [1982,1,1,0,0,0])
- assert_time_constructor(tz, "1982-01-01 00:59:59 +0800", :local, [1982,1,1,0,29,59])
+ assert_time_constructor(tz, "1981-12-31 23:29:59 +0730", :local, [1981,12,31,23,29,59])
+ if CORRECT_SINGAPORE_1982
+ assert_time_constructor(tz, "1982-01-01 00:00:00 +0800", :local, [1981,12,31,23,30,00])
+ assert_time_constructor(tz, "1982-01-01 00:00:00 +0800", :local, [1982,1,1,0,0,0])
+ assert_time_constructor(tz, "1982-01-01 00:29:59 +0800", :local, [1982,1,1,0,29,59])
+ end
assert_time_constructor(tz, "1982-01-01 00:30:00 +0800", :local, [1982,1,1,0,30,0])
}
end
@@ -195,7 +201,7 @@ class TestTimeTZ < Test::Unit::TestCase
def test_europe_lisbon
with_tz("Europe/Lisbon") {
- assert_equal("LMT", Time.new(-0x1_0000_0000_0000_0000).zone)
+ assert_include(%w"LMT CET", Time.new(-0x1_0000_0000_0000_0000).zone)
}
end if has_lisbon_tz
@@ -225,6 +231,29 @@ class TestTimeTZ < Test::Unit::TestCase
}
end if has_right_tz
+ def test_right_utc_switching
+ with_tz("UTC") { # ensure no leap second timezone
+ assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i)
+ with_tz(tz="right/UTC") {
+ assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59])
+ assert_time_constructor(tz, "2008-12-31 23:59:60 UTC", :utc, [2008,12,31,23,59,60])
+ assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0])
+ assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0])
+ assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i)
+ }
+ }
+ with_tz("right/UTC") {
+ assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i)
+ with_tz(tz="UTC") {
+ assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59])
+ assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,23,59,60])
+ assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0])
+ assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0])
+ assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i)
+ }
+ }
+ end if has_right_tz
+
def test_right_america_los_angeles
with_tz(tz="right/America/Los_Angeles") {
assert_time_constructor(tz, "2008-12-31 15:59:59 -0800", :local, [2008,12,31,15,59,59])
@@ -233,6 +262,68 @@ class TestTimeTZ < Test::Unit::TestCase
}
end if has_right_tz
+ def test_utc_names
+ assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "UTC"), :utc?)
+ assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "utc"), :utc?)
+ assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "Z"), :utc?)
+ assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "-00:00"), :utc?)
+ assert_not_predicate(Time.new(2019, 1, 1, 0, 0, 0, "+00:00"), :utc?)
+ end
+
+ def test_military_names
+ assert_equal( +1*3600, Time.new(2019, 1, 1, 0, 0, 0, "A").gmtoff)
+ assert_equal( +2*3600, Time.new(2019, 1, 1, 0, 0, 0, "B").gmtoff)
+ assert_equal( +3*3600, Time.new(2019, 1, 1, 0, 0, 0, "C").gmtoff)
+ assert_equal( +4*3600, Time.new(2019, 1, 1, 0, 0, 0, "D").gmtoff)
+ assert_equal( +5*3600, Time.new(2019, 1, 1, 0, 0, 0, "E").gmtoff)
+ assert_equal( +6*3600, Time.new(2019, 1, 1, 0, 0, 0, "F").gmtoff)
+ assert_equal( +7*3600, Time.new(2019, 1, 1, 0, 0, 0, "G").gmtoff)
+ assert_equal( +8*3600, Time.new(2019, 1, 1, 0, 0, 0, "H").gmtoff)
+ assert_equal( +9*3600, Time.new(2019, 1, 1, 0, 0, 0, "I").gmtoff)
+ assert_equal(+10*3600, Time.new(2019, 1, 1, 0, 0, 0, "K").gmtoff)
+ assert_equal(+11*3600, Time.new(2019, 1, 1, 0, 0, 0, "L").gmtoff)
+ assert_equal(+12*3600, Time.new(2019, 1, 1, 0, 0, 0, "M").gmtoff)
+ assert_equal( -1*3600, Time.new(2019, 1, 1, 0, 0, 0, "N").gmtoff)
+ assert_equal( -2*3600, Time.new(2019, 1, 1, 0, 0, 0, "O").gmtoff)
+ assert_equal( -3*3600, Time.new(2019, 1, 1, 0, 0, 0, "P").gmtoff)
+ assert_equal( -4*3600, Time.new(2019, 1, 1, 0, 0, 0, "Q").gmtoff)
+ assert_equal( -5*3600, Time.new(2019, 1, 1, 0, 0, 0, "R").gmtoff)
+ assert_equal( -6*3600, Time.new(2019, 1, 1, 0, 0, 0, "S").gmtoff)
+ assert_equal( -7*3600, Time.new(2019, 1, 1, 0, 0, 0, "T").gmtoff)
+ assert_equal( -8*3600, Time.new(2019, 1, 1, 0, 0, 0, "U").gmtoff)
+ assert_equal( -9*3600, Time.new(2019, 1, 1, 0, 0, 0, "V").gmtoff)
+ assert_equal(-10*3600, Time.new(2019, 1, 1, 0, 0, 0, "W").gmtoff)
+ assert_equal(-11*3600, Time.new(2019, 1, 1, 0, 0, 0, "X").gmtoff)
+ assert_equal(-12*3600, Time.new(2019, 1, 1, 0, 0, 0, "Y").gmtoff)
+ assert_equal( 0, Time.new(2019, 1, 1, 0, 0, 0, "Z").gmtoff)
+
+ assert_equal( +1*3600, Time.at(0, in: "A").gmtoff)
+ assert_equal( +2*3600, Time.at(0, in: "B").gmtoff)
+ assert_equal( +3*3600, Time.at(0, in: "C").gmtoff)
+ assert_equal( +4*3600, Time.at(0, in: "D").gmtoff)
+ assert_equal( +5*3600, Time.at(0, in: "E").gmtoff)
+ assert_equal( +6*3600, Time.at(0, in: "F").gmtoff)
+ assert_equal( +7*3600, Time.at(0, in: "G").gmtoff)
+ assert_equal( +8*3600, Time.at(0, in: "H").gmtoff)
+ assert_equal( +9*3600, Time.at(0, in: "I").gmtoff)
+ assert_equal(+10*3600, Time.at(0, in: "K").gmtoff)
+ assert_equal(+11*3600, Time.at(0, in: "L").gmtoff)
+ assert_equal(+12*3600, Time.at(0, in: "M").gmtoff)
+ assert_equal( -1*3600, Time.at(0, in: "N").gmtoff)
+ assert_equal( -2*3600, Time.at(0, in: "O").gmtoff)
+ assert_equal( -3*3600, Time.at(0, in: "P").gmtoff)
+ assert_equal( -4*3600, Time.at(0, in: "Q").gmtoff)
+ assert_equal( -5*3600, Time.at(0, in: "R").gmtoff)
+ assert_equal( -6*3600, Time.at(0, in: "S").gmtoff)
+ assert_equal( -7*3600, Time.at(0, in: "T").gmtoff)
+ assert_equal( -8*3600, Time.at(0, in: "U").gmtoff)
+ assert_equal( -9*3600, Time.at(0, in: "V").gmtoff)
+ assert_equal(-10*3600, Time.at(0, in: "W").gmtoff)
+ assert_equal(-11*3600, Time.at(0, in: "X").gmtoff)
+ assert_equal(-12*3600, Time.at(0, in: "Y").gmtoff)
+ assert_equal( 0, Time.at(0, in: "Z").gmtoff)
+ end
+
MON2NUM = {
"Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6,
"Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Nov" => 11, "Dec" => 12
@@ -364,9 +455,12 @@ America/Managua Fri Jan 1 06:00:00 1993 UTC = Fri Jan 1 01:00:00 1993 EST isd
America/Managua Wed Jan 1 04:59:59 1997 UTC = Tue Dec 31 23:59:59 1996 EST isdst=0 gmtoff=-18000
America/Managua Wed Jan 1 05:00:00 1997 UTC = Tue Dec 31 23:00:00 1996 CST isdst=0 gmtoff=-21600
Asia/Singapore Sun Aug 8 16:30:00 1965 UTC = Mon Aug 9 00:00:00 1965 SGT isdst=0 gmtoff=27000
-Asia/Singapore Thu Dec 31 16:29:59 1981 UTC = Thu Dec 31 23:59:59 1981 SGT isdst=0 gmtoff=27000
+Asia/Singapore Thu Dec 31 15:59:59 1981 UTC = Thu Dec 31 23:29:59 1981 SGT isdst=0 gmtoff=27000
Asia/Singapore Thu Dec 31 16:30:00 1981 UTC = Fri Jan 1 00:30:00 1982 SGT isdst=0 gmtoff=28800
End
+ gen_zdump_test <<'End' if CORRECT_SINGAPORE_1982
+Asia/Singapore Thu Dec 31 16:00:00 1981 UTC = Fri Jan 1 00:00:00 1982 SGT isdst=0 gmtoff=28800
+End
gen_zdump_test CORRECT_TOKYO_DST_1951 ? <<'End' + (CORRECT_TOKYO_DST_1951 < "2018f" ? <<'2018e' : <<'2018f') : <<'End'
Asia/Tokyo Sat May 5 14:59:59 1951 UTC = Sat May 5 23:59:59 1951 JST isdst=0 gmtoff=32400
Asia/Tokyo Sat May 5 15:00:00 1951 UTC = Sun May 6 01:00:00 1951 JDT isdst=1 gmtoff=36000
@@ -460,4 +554,275 @@ Europe/Lisbon Mon Jan 1 00:36:31 1912 UTC = Sun Dec 31 23:59:59 1911 LMT isdst
Europe/Lisbon Mon Jan 1 00:36:44 1912 UT = Sun Dec 31 23:59:59 1911 LMT isdst=0 gmtoff=-2205
Europe/Lisbon Sun Dec 31 23:59:59 1911 UT = Sun Dec 31 23:23:14 1911 LMT isdst=0 gmtoff=-2205
End
+
+ class TZ
+ attr_reader :name
+
+ def initialize(name, abbr, offset, abbr2 = nil, offset2 = nil)
+ @name = name
+ @abbr = abbr
+ @offset = offset
+ @abbr2 = abbr2
+ @offset2 = offset2
+ end
+
+ def dst?(t)
+ return false unless @offset2
+ case t when Integer
+ return nil
+ end
+ case t.mon
+ when 4..9
+ true
+ else
+ false
+ end
+ end
+
+ def offset(t)
+ (dst?(t) ? @offset2 : @offset)
+ end
+
+ def local_to_utc(t)
+ t - offset(t)
+ end
+
+ def utc_to_local(t)
+ t + offset(t)
+ end
+
+ def abbr(t)
+ dst?(t) ? @abbr2 : @abbr
+ end
+
+ def ==(other)
+ @name == other.name and abbr(0) == other.abbr(0) and offset(0) == other.offset(0)
+ end
+
+ def inspect
+ "#<TZ: #@name #@abbr #@offset>"
+ end
+ end
+end
+
+module TestTimeTZ::WithTZ
+ def subtest_new(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ abbr, abbr2 = *abbr
+ utc_offset, utc_offset2 = *utc_offset
+ t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg)
+ utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(t)
+ assert_equal([2018, 9, 1, 12, 0, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
+ h, m = (-utc_offset / 60).divmod(60)
+ assert_equal(time_class.utc(2018, 9, 1, 12+h, m, 0).to_i, t.to_i)
+ assert_equal(6, t.wday)
+ assert_equal(244, t.yday)
+ assert_equal(t, time_class.new(2018, 9, 1, 12, in: tzarg))
+ assert_raise(ArgumentError) {time_class.new(2018, 9, 1, 12, 0, 0, tzarg, in: tzarg)}
+ end
+
+ def subtest_hour24(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ t = time_class.new(2000, 1, 1, 24, 0, 0, tzarg)
+ assert_equal([0, 0, 0, 2, 1, 2000], [t.sec, t.min, t.hour, t.mday, t.mon, t.year])
+ end
+
+ def subtest_now(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ t = time_class.now(in: tzarg)
+ assert_equal(tz, t.zone)
+ end
+
+ def subtest_getlocal(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ abbr, abbr2 = *abbr
+ utc_offset, utc_offset2 = *utc_offset
+ utc = time_class.utc(2018, 9, 1, 12, 0, 0)
+ utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(utc)
+ t = utc.getlocal(tzarg)
+ h, m = (utc_offset / 60).divmod(60)
+ assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
+ assert_equal(time_class.utc(2018, 9, 1, 12, 0, 0), t)
+ end
+
+ def subtest_strftime(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ abbr, abbr2 = *abbr
+ utc_offset, utc_offset2 = *utc_offset
+ t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg)
+ utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(t)
+ h, m = (utc_offset.abs / 60).divmod(60)
+ h = -h if utc_offset < 0
+ assert_equal("%+.2d%.2d %s" % [h, m, abbr], t.strftime("%z %Z"))
+ assert_equal("34 35 35", t.strftime("%U %V %W"))
+ end
+
+ def subtest_plus(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ abbr, abbr2 = *abbr
+ utc_offset, utc_offset2 = *utc_offset
+ t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + 4000
+ utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(t)
+ assert_equal([2018, 9, 1, 13, 6, 40, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
+ m, s = (4000-utc_offset).divmod(60)
+ h, m = m.divmod(60)
+ assert_equal(time_class.utc(2018, 9, 1, 12+h, m, s), t)
+ end
+
+ def subtest_at(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ abbr, abbr2 = *abbr
+ utc_offset, utc_offset2 = *utc_offset
+ utc = time_class.utc(2018, 9, 1, 12, 0, 0)
+ utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(utc)
+ h, m = (utc_offset / 60).divmod(60)
+ t = time_class.at(utc, in: tzarg)
+ assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
+ assert_equal(utc.to_i, t.to_i)
+ utc = utc.to_i
+ t = time_class.at(utc, in: tzarg)
+ assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
+ assert_equal(utc, t.to_i)
+ end
+
+ def subtest_to_a(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg)
+ ary = t.to_a
+ assert_equal(ary, [t.sec, t.min, t.hour, t.mday, t.mon, t.year, t.wday, t.yday, t.isdst, t.zone])
+ end
+
+ def subtest_marshal(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg)
+ t2 = Marshal.load(Marshal.dump(t))
+ assert_equal(t, t2)
+ assert_equal(t.utc_offset, t2.utc_offset)
+ assert_equal(t.utc_offset, (t2+1).utc_offset)
+ assert_instance_of(t.zone.class, t2.zone)
+ assert_equal(t.dst?, t2.dst?)
+ end
+
+ def subtest_fractional_second(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ t = time_class.new(2024, 1, 1, 23, 59, 59.9r, tzarg)
+ assert_equal(utc_offset[t.dst? ? 1 : 0], t.utc_offset)
+ t = time_class.new(2024, 7, 1, 23, 59, 59.9r, tzarg)
+ assert_equal(utc_offset[t.dst? ? 1 : 0], t.utc_offset)
+ end
+
+ def test_invalid_zone
+ make_timezone("INVALID", "INV", 0)
+ rescue => e
+ assert_kind_of(StandardError, e)
+ else
+ assert false, "ArgumentError expected but nothing was raised."
+ end
+
+ def nametest_marshal_compatibility(time_class, tzname, abbr, utc_offset)
+ data = [
+ "\x04\x08Iu:".b, Marshal.dump(time_class)[3..-1],
+ "\x0d""\xEF\xA7\x1D\x80\x00\x00\x00\x00".b,
+ Marshal.dump({offset: utc_offset, zone: abbr})[3..-1],
+ ].join('')
+ t = Marshal.load(data)
+ assert_equal(utc_offset, t.utc_offset)
+ assert_equal(utc_offset, (t+1).utc_offset)
+ # t.zone may be a mere String or timezone object.
+ end
+
+ ZONES = {
+ "Asia/Tokyo" => ["JST", +9*3600],
+ "America/Los_Angeles" => ["PST", -8*3600, "PDT", -7*3600],
+ "Africa/Ndjamena" => ["WAT", +1*3600],
+ "Etc/UTC" => ["UTC", 0],
+ }
+
+ def make_timezone(tzname, abbr, utc_offset, abbr2 = nil, utc_offset2 = nil)
+ self.class::TIME_CLASS.find_timezone(tzname)
+ end
+
+ def subtest_dst?(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ t = time_class.new(2018, 6, 22, 12, 0, 0, tzarg)
+ return unless tz.dst?(t)
+ assert_predicate t, :dst?
+ t = time_class.new(2018, 12, 22, 12, 0, 0, tzarg)
+ assert_not_predicate t, :dst?
+ end
+
+ instance_methods(false).grep(/\Asub(?=test_)/) do |subtest|
+ test = $'
+ ZONES.each_pair do |tzname, (abbr, utc_offset, abbr2, utc_offset2)|
+ define_method("#{test}@#{tzname}") do
+ tz = make_timezone(tzname, abbr, utc_offset, abbr2, utc_offset2)
+ time_class = self.class::TIME_CLASS
+ __send__(subtest, time_class, tz, tz, tzname, [abbr, abbr2], [utc_offset, utc_offset2])
+ __send__(subtest, time_class, tz, tzname, tzname, [abbr, abbr2], [utc_offset, utc_offset2])
+ end
+ end
+ end
+
+ instance_methods(false).grep(/\Aname(?=test_)/) do |subtest|
+ test = $'
+ ZONES.each_pair do |tzname, (abbr, utc_offset)|
+ define_method("#{test}@#{tzname}") do
+ time_class = self.class::TIME_CLASS
+ __send__(subtest, time_class, tzname, abbr, utc_offset)
+ end
+ end
+ end
+end
+
+class TestTimeTZ::DummyTZ < Test::Unit::TestCase
+ include TestTimeTZ::WithTZ
+
+ class TIME_CLASS < ::Time
+ ZONES = TestTimeTZ::WithTZ::ZONES
+ def self.find_timezone(tzname)
+ tz = ZONES[tzname] or raise ArgumentError, "Unknown timezone: #{name}"
+ TestTimeTZ::TZ.new(tzname, *tz)
+ end
+ end
+
+ def self.make_timezone(tzname, abbr, utc_offset, abbr2 = nil, utc_offset2 = nil)
+ TestTimeTZ::TZ.new(tzname, abbr, utc_offset, abbr2, utc_offset2)
+ end
+
+ def test_fractional_second
+ x = Object.new
+ def x.local_to_utc(t); t + 8*3600; end
+ def x.utc_to_local(t); t - 8*3600; end
+
+ t1 = Time.new(2020,11,11,12,13,14.124r, '-08:00')
+ t2 = Time.new(2020,11,11,12,13,14.124r, x)
+ assert_equal(t1, t2)
+ end
+end
+
+begin
+ require "tzinfo"
+rescue LoadError
+else
+ class TestTimeTZ::GemTZInfo < Test::Unit::TestCase
+ include TestTimeTZ::WithTZ
+
+ class TIME_CLASS < ::Time
+ def self.find_timezone(tzname)
+ TZInfo::Timezone.get(tzname)
+ end
+ end
+
+ def tz
+ @tz ||= TZInfo::Timezone.get(tzname)
+ end
+ end
+end
+
+begin
+ require "timezone"
+rescue LoadError
+else
+ class TestTimeTZ::GemTimezone < Test::Unit::TestCase
+ include TestTimeTZ::WithTZ
+
+ class TIME_CLASS < ::Time
+ def self.find_timezone(name)
+ Timezone.fetch(name)
+ end
+ end
+
+ def tz
+ @tz ||= Timezone[tzname]
+ end
+ end
end
diff --git a/test/ruby/test_trace.rb b/test/ruby/test_trace.rb
index 77be94e9be..5842f11aee 100644
--- a/test/ruby/test_trace.rb
+++ b/test/ruby/test_trace.rb
@@ -20,17 +20,6 @@ class TestTrace < Test::Unit::TestCase
untrace_var :$x
end
- def test_trace_tainted_proc
- $x = 1234
- s = proc { $y = :foo }
- trace_var :$x, s
- s.taint
- $x = 42
- assert_equal(:foo, $y)
- ensure
- untrace_var :$x
- end
-
def test_trace_proc_that_raises_exception
$x = 1234
trace_var :$x, proc { raise }
diff --git a/test/ruby/test_transcode.rb b/test/ruby/test_transcode.rb
index 44d238ffd2..99b5ee8d43 100644
--- a/test/ruby/test_transcode.rb
+++ b/test/ruby/test_transcode.rb
@@ -10,9 +10,9 @@ class TestTranscode < Test::Unit::TestCase
assert_raise(Encoding::ConverterNotFoundError) { 'abc'.encode!('foo', 'bar') }
assert_raise(Encoding::ConverterNotFoundError) { 'abc'.force_encoding('utf-8').encode('foo') }
assert_raise(Encoding::ConverterNotFoundError) { 'abc'.force_encoding('utf-8').encode!('foo') }
- assert_raise(Encoding::UndefinedConversionError) { "\x80".encode('utf-8','ASCII-8BIT') }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x80".encode('utf-8','US-ASCII') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA5".encode('utf-8','iso-8859-3') }
+ assert_undefined_in("\x80", 'ASCII-8BIT')
+ assert_invalid_in("\x80", 'US-ASCII')
+ assert_undefined_in("\xA5", 'iso-8859-3')
assert_raise(FrozenError) { 'hello'.freeze.encode!('iso-8859-1') }
assert_raise(FrozenError) { '\u3053\u3093\u306b\u3061\u306f'.freeze.encode!('iso-8859-1') } # ã“ã‚“ã«ã¡ã¯
end
@@ -52,16 +52,6 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("\u20AC"*200000, ("\xA4"*200000).encode!('utf-8', 'iso-8859-15'))
end
- def check_both_ways(utf8, raw, encoding)
- assert_equal(utf8.force_encoding('utf-8'), raw.encode('utf-8', encoding),utf8.dump+raw.dump)
- assert_equal(raw.force_encoding(encoding), utf8.encode(encoding, 'utf-8'))
- end
-
- def check_both_ways2(str1, enc1, str2, enc2)
- assert_equal(str1.force_encoding(enc1), str2.encode(enc1, enc2))
- assert_equal(str2.force_encoding(enc2), str1.encode(enc2, enc1))
- end
-
def test_encoding_of_ascii_originating_from_binary
binary_string = [0x82, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20,
0x61, 0x20, 0x76, 0x65, 0x72, 0x79, 0x20, 0x6c, 0x6f,
@@ -126,6 +116,28 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("D\xFCrst".force_encoding('iso-8859-2'), "D\xFCrst".encode('iso-8859-2', 'iso-8859-1'))
end
+ def test_encode_xml_multibyte
+ encodings = %w'UTF-8 UTF-16LE UTF-16BE UTF-32LE UTF-32BE'
+ encodings.each do |src_enc|
+ encodings.each do |dst_enc|
+ escaped = "<>".encode(src_enc).encode(dst_enc, :xml=>:text)
+ assert_equal("&lt;&gt;", escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :text")
+
+ escaped = '<">'.encode(src_enc).encode(dst_enc, :xml=>:attr)
+ assert_equal('"&lt;&quot;&gt;"', escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :attr")
+
+ escaped = "<>".encode(src_enc).force_encoding("UTF-8").encode(dst_enc, src_enc, :xml=>:text)
+ assert_equal("&lt;&gt;", escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :text")
+
+ escaped = '<">'.encode(src_enc).force_encoding("UTF-8").encode(dst_enc, src_enc, :xml=>:attr)
+ assert_equal('"&lt;&quot;&gt;"', escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :attr")
+ end
+ end
+ # regression test; U+6E7F (湿) uses the same bytes in ISO-2022-JP as "<>"
+ assert_equal( "&lt;&gt;\u6E7F", "<>\u6E7F".encode("ISO-2022-JP").encode("ISO-2022-JP", :xml=>:text).encode("UTF-8"))
+ assert_equal("\"&lt;&gt;\u6E7F\"", "<>\u6E7F".encode("ISO-2022-JP").encode("ISO-2022-JP", :xml=>:attr).encode("UTF-8"))
+ end
+
def test_ascii_range
encodings = [
'US-ASCII', 'ASCII-8BIT',
@@ -166,16 +178,16 @@ class TestTranscode < Test::Unit::TestCase
def test_windows_874
check_both_ways("\u20AC", "\x80", 'windows-874') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-874') }
- assert_raise(Encoding::UndefinedConversionError) { "\x84".encode("utf-8", 'windows-874') }
+ assert_undefined_in("\x81", 'windows-874')
+ assert_undefined_in("\x84", 'windows-874')
check_both_ways("\u2026", "\x85", 'windows-874') # …
- assert_raise(Encoding::UndefinedConversionError) { "\x86".encode("utf-8", 'windows-874') }
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'windows-874') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-874') }
+ assert_undefined_in("\x86", 'windows-874')
+ assert_undefined_in("\x8F", 'windows-874')
+ assert_undefined_in("\x90", 'windows-874')
check_both_ways("\u2018", "\x91", 'windows-874') # ‘
check_both_ways("\u2014", "\x97", 'windows-874') # —
- assert_raise(Encoding::UndefinedConversionError) { "\x98".encode("utf-8", 'windows-874') }
- assert_raise(Encoding::UndefinedConversionError) { "\x9F".encode("utf-8", 'windows-874') }
+ assert_undefined_in("\x98", 'windows-874')
+ assert_undefined_in("\x9F", 'windows-874')
check_both_ways("\u00A0", "\xA0", 'windows-874') # non-breaking space
check_both_ways("\u0E0F", "\xAF", 'windows-874') # à¸
check_both_ways("\u0E10", "\xB0", 'windows-874') # à¸
@@ -184,31 +196,31 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u0E2F", "\xCF", 'windows-874') # ฯ
check_both_ways("\u0E30", "\xD0", 'windows-874') # ะ
check_both_ways("\u0E3A", "\xDA", 'windows-874') # ฺ
- assert_raise(Encoding::UndefinedConversionError) { "\xDB".encode("utf-8", 'windows-874') }
- assert_raise(Encoding::UndefinedConversionError) { "\xDE".encode("utf-8", 'windows-874') }
+ assert_undefined_in("\xDB", 'windows-874')
+ assert_undefined_in("\xDE", 'windows-874')
check_both_ways("\u0E3F", "\xDF", 'windows-874') # ฿
check_both_ways("\u0E40", "\xE0", 'windows-874') # เ
check_both_ways("\u0E4F", "\xEF", 'windows-874') # à¹
check_both_ways("\u0E50", "\xF0", 'windows-874') # à¹
check_both_ways("\u0E5B", "\xFB", 'windows-874') # ๛
- assert_raise(Encoding::UndefinedConversionError) { "\xFC".encode("utf-8", 'windows-874') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFF".encode("utf-8", 'windows-874') }
+ assert_undefined_in("\xFC", 'windows-874')
+ assert_undefined_in("\xFF", 'windows-874')
end
def test_windows_1250
check_both_ways("\u20AC", "\x80", 'windows-1250') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-1250') }
+ assert_undefined_in("\x81", 'windows-1250')
check_both_ways("\u201A", "\x82", 'windows-1250') # ‚
- assert_raise(Encoding::UndefinedConversionError) { "\x83".encode("utf-8", 'windows-1250') }
+ assert_undefined_in("\x83", 'windows-1250')
check_both_ways("\u201E", "\x84", 'windows-1250') # „
check_both_ways("\u2021", "\x87", 'windows-1250') # ‡
- assert_raise(Encoding::UndefinedConversionError) { "\x88".encode("utf-8", 'windows-1250') }
+ assert_undefined_in("\x88", 'windows-1250')
check_both_ways("\u2030", "\x89", 'windows-1250') # ‰
check_both_ways("\u0179", "\x8F", 'windows-1250') # Ź
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-1250') }
+ assert_undefined_in("\x90", 'windows-1250')
check_both_ways("\u2018", "\x91", 'windows-1250') # ‘
check_both_ways("\u2014", "\x97", 'windows-1250') # —
- assert_raise(Encoding::UndefinedConversionError) { "\x98".encode("utf-8", 'windows-1250') }
+ assert_undefined_in("\x98", 'windows-1250')
check_both_ways("\u2122", "\x99", 'windows-1250') # â„¢
check_both_ways("\u00A0", "\xA0", 'windows-1250') # non-breaking space
check_both_ways("\u017B", "\xAF", 'windows-1250') # Å»
@@ -229,7 +241,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u20AC", "\x88", 'windows-1251') # €
check_both_ways("\u040F", "\x8F", 'windows-1251') # Ð
check_both_ways("\u0452", "\x90", 'windows-1251') # Ñ’
- assert_raise(Encoding::UndefinedConversionError) { "\x98".encode("utf-8", 'windows-1251') }
+ assert_undefined_in("\x98", 'windows-1251')
check_both_ways("\u045F", "\x9F", 'windows-1251') # ÑŸ
check_both_ways("\u00A0", "\xA0", 'windows-1251') # non-breaking space
check_both_ways("\u0407", "\xAF", 'windows-1251') # Ї
@@ -247,16 +259,16 @@ class TestTranscode < Test::Unit::TestCase
def test_windows_1252
check_both_ways("\u20AC", "\x80", 'windows-1252') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-1252') }
+ assert_undefined_in("\x81", 'windows-1252')
check_both_ways("\u201A", "\x82", 'windows-1252') # ‚
check_both_ways("\u0152", "\x8C", 'windows-1252') # >Å’
- assert_raise(Encoding::UndefinedConversionError) { "\x8D".encode("utf-8", 'windows-1252') }
+ assert_undefined_in("\x8D", 'windows-1252')
check_both_ways("\u017D", "\x8E", 'windows-1252') # Ž
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'windows-1252') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-1252') }
+ assert_undefined_in("\x8F", 'windows-1252')
+ assert_undefined_in("\x90", 'windows-1252')
check_both_ways("\u2018", "\x91", 'windows-1252') #‘
check_both_ways("\u0153", "\x9C", 'windows-1252') # Å“
- assert_raise(Encoding::UndefinedConversionError) { "\x9D".encode("utf-8", 'windows-1252') }
+ assert_undefined_in("\x9D", 'windows-1252')
check_both_ways("\u017E", "\x9E", 'windows-1252') # ž
check_both_ways("\u00A0", "\xA0", 'windows-1252') # non-breaking space
check_both_ways("\u00AF", "\xAF", 'windows-1252') # ¯
@@ -274,24 +286,24 @@ class TestTranscode < Test::Unit::TestCase
def test_windows_1253
check_both_ways("\u20AC", "\x80", 'windows-1253') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x81", 'windows-1253')
check_both_ways("\u201A", "\x82", 'windows-1253') # ‚
check_both_ways("\u2021", "\x87", 'windows-1253') # ‡
- assert_raise(Encoding::UndefinedConversionError) { "\x88".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x88", 'windows-1253')
check_both_ways("\u2030", "\x89", 'windows-1253') # ‰
- assert_raise(Encoding::UndefinedConversionError) { "\x8A".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x8A", 'windows-1253')
check_both_ways("\u2039", "\x8B", 'windows-1253') # ‹
- assert_raise(Encoding::UndefinedConversionError) { "\x8C".encode("utf-8", 'windows-1253') }
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'windows-1253') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x8C", 'windows-1253')
+ assert_undefined_in("\x8F", 'windows-1253')
+ assert_undefined_in("\x90", 'windows-1253')
check_both_ways("\u2018", "\x91", 'windows-1253') # ‘
check_both_ways("\u2014", "\x97", 'windows-1253') # —
- assert_raise(Encoding::UndefinedConversionError) { "\x98".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x98", 'windows-1253')
check_both_ways("\u2122", "\x99", 'windows-1253') # â„¢
- assert_raise(Encoding::UndefinedConversionError) { "\x9A".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x9A", 'windows-1253')
check_both_ways("\u203A", "\x9B", 'windows-1253') # ›
- assert_raise(Encoding::UndefinedConversionError) { "\x9C".encode("utf-8", 'windows-1253') }
- assert_raise(Encoding::UndefinedConversionError) { "\x9F".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x9C", 'windows-1253')
+ assert_undefined_in("\x9F", 'windows-1253')
check_both_ways("\u00A0", "\xA0", 'windows-1253') # non-breaking space
check_both_ways("\u2015", "\xAF", 'windows-1253') # ―
check_both_ways("\u00B0", "\xB0", 'windows-1253') # °
@@ -300,28 +312,28 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u039F", "\xCF", 'windows-1253') # Ο
check_both_ways("\u03A0", "\xD0", 'windows-1253') # Π
check_both_ways("\u03A1", "\xD1", 'windows-1253') # Ρ
- assert_raise(Encoding::UndefinedConversionError) { "\xD2".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\xD2", 'windows-1253')
check_both_ways("\u03A3", "\xD3", 'windows-1253') # Σ
check_both_ways("\u03AF", "\xDF", 'windows-1253') # ί
check_both_ways("\u03B0", "\xE0", 'windows-1253') # ΰ
check_both_ways("\u03BF", "\xEF", 'windows-1253') # ο
check_both_ways("\u03C0", "\xF0", 'windows-1253') # π
check_both_ways("\u03CE", "\xFE", 'windows-1253') # ÏŽ
- assert_raise(Encoding::UndefinedConversionError) { "\xFF".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\xFF", 'windows-1253')
end
def test_windows_1254
check_both_ways("\u20AC", "\x80", 'windows-1254') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-1254') }
+ assert_undefined_in("\x81", 'windows-1254')
check_both_ways("\u201A", "\x82", 'windows-1254') # ‚
check_both_ways("\u0152", "\x8C", 'windows-1254') # Å’
- assert_raise(Encoding::UndefinedConversionError) { "\x8D".encode("utf-8", 'windows-1254') }
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'windows-1254') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-1254') }
+ assert_undefined_in("\x8D", 'windows-1254')
+ assert_undefined_in("\x8F", 'windows-1254')
+ assert_undefined_in("\x90", 'windows-1254')
check_both_ways("\u2018", "\x91", 'windows-1254') # ‘
check_both_ways("\u0153", "\x9C", 'windows-1254') # Å“
- assert_raise(Encoding::UndefinedConversionError) { "\x9D".encode("utf-8", 'windows-1254') }
- assert_raise(Encoding::UndefinedConversionError) { "\x9E".encode("utf-8", 'windows-1254') }
+ assert_undefined_in("\x9D", 'windows-1254')
+ assert_undefined_in("\x9E", 'windows-1254')
check_both_ways("\u0178", "\x9F", 'windows-1254') # Ÿ
check_both_ways("\u00A0", "\xA0", 'windows-1254') # non-breaking space
check_both_ways("\u00AF", "\xAF", 'windows-1254') # ¯
@@ -339,20 +351,20 @@ class TestTranscode < Test::Unit::TestCase
def test_windows_1255
check_both_ways("\u20AC", "\x80", 'windows-1255') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\x81", 'windows-1255')
check_both_ways("\u201A", "\x82", 'windows-1255') # ‚
check_both_ways("\u2030", "\x89", 'windows-1255') # ‰
- assert_raise(Encoding::UndefinedConversionError) { "\x8A".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\x8A", 'windows-1255')
check_both_ways("\u2039", "\x8B", 'windows-1255') # ‹
- assert_raise(Encoding::UndefinedConversionError) { "\x8C".encode("utf-8", 'windows-1255') }
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'windows-1255') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\x8C", 'windows-1255')
+ assert_undefined_in("\x8F", 'windows-1255')
+ assert_undefined_in("\x90", 'windows-1255')
check_both_ways("\u2018", "\x91", 'windows-1255') # ‘
check_both_ways("\u2122", "\x99", 'windows-1255') # â„¢
- assert_raise(Encoding::UndefinedConversionError) { "\x9A".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\x9A", 'windows-1255')
check_both_ways("\u203A", "\x9B", 'windows-1255') # ›
- assert_raise(Encoding::UndefinedConversionError) { "\x9C".encode("utf-8", 'windows-1255') }
- assert_raise(Encoding::UndefinedConversionError) { "\x9F".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\x9C", 'windows-1255')
+ assert_undefined_in("\x9F", 'windows-1255')
check_both_ways("\u00A0", "\xA0", 'windows-1255') # non-breaking space
check_both_ways("\u00A1", "\xA1", 'windows-1255') # ¡
check_both_ways("\u00D7", "\xAA", 'windows-1255') # ×
@@ -369,17 +381,17 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u05C0", "\xD0", 'windows-1255') # ×€
check_both_ways("\u05F3", "\xD7", 'windows-1255') # ׳
check_both_ways("\u05F4", "\xD8", 'windows-1255') # ×´
- assert_raise(Encoding::UndefinedConversionError) { "\xD9".encode("utf-8", 'windows-1255') }
- assert_raise(Encoding::UndefinedConversionError) { "\xDF".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\xD9", 'windows-1255')
+ assert_undefined_in("\xDF", 'windows-1255')
check_both_ways("\u05D0", "\xE0", 'windows-1255') # ×
check_both_ways("\u05DF", "\xEF", 'windows-1255') # ן
check_both_ways("\u05E0", "\xF0", 'windows-1255') # × 
check_both_ways("\u05EA", "\xFA", 'windows-1255') # ת
- assert_raise(Encoding::UndefinedConversionError) { "\xFB".encode("utf-8", 'windows-1255') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFC".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\xFB", 'windows-1255')
+ assert_undefined_in("\xFC", 'windows-1255')
check_both_ways("\u200E", "\xFD", 'windows-1255') # left-to-right mark
check_both_ways("\u200F", "\xFE", 'windows-1255') # right-to-left mark
- assert_raise(Encoding::UndefinedConversionError) { "\xFF".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\xFF", 'windows-1255')
end
def test_windows_1256
@@ -407,35 +419,35 @@ class TestTranscode < Test::Unit::TestCase
def test_windows_1257
check_both_ways("\u20AC", "\x80", 'windows-1257') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x81", 'windows-1257')
check_both_ways("\u201A", "\x82", 'windows-1257') # ‚
- assert_raise(Encoding::UndefinedConversionError) { "\x83".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x83", 'windows-1257')
check_both_ways("\u201E", "\x84", 'windows-1257') # „
check_both_ways("\u2021", "\x87", 'windows-1257') # ‡
- assert_raise(Encoding::UndefinedConversionError) { "\x88".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x88", 'windows-1257')
check_both_ways("\u2030", "\x89", 'windows-1257') # ‰
- assert_raise(Encoding::UndefinedConversionError) { "\x8A".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x8A", 'windows-1257')
check_both_ways("\u2039", "\x8B", 'windows-1257') # ‹
- assert_raise(Encoding::UndefinedConversionError) { "\x8C".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x8C", 'windows-1257')
check_both_ways("\u00A8", "\x8D", 'windows-1257') # ¨
check_both_ways("\u02C7", "\x8E", 'windows-1257') # ˇ
check_both_ways("\u00B8", "\x8F", 'windows-1257') # ¸
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x90", 'windows-1257')
check_both_ways("\u2018", "\x91", 'windows-1257') # ‘
check_both_ways("\u2014", "\x97", 'windows-1257') # —
- assert_raise(Encoding::UndefinedConversionError) { "\x98".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x98", 'windows-1257')
check_both_ways("\u2122", "\x99", 'windows-1257') # â„¢
- assert_raise(Encoding::UndefinedConversionError) { "\x9A".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x9A", 'windows-1257')
check_both_ways("\u203A", "\x9B", 'windows-1257') # ›
- assert_raise(Encoding::UndefinedConversionError) { "\x9C".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x9C", 'windows-1257')
check_both_ways("\u00AF", "\x9D", 'windows-1257') # ¯
check_both_ways("\u02DB", "\x9E", 'windows-1257') # Ë›
- assert_raise(Encoding::UndefinedConversionError) { "\x9F".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x9F", 'windows-1257')
check_both_ways("\u00A0", "\xA0", 'windows-1257') # non-breaking space
- assert_raise(Encoding::UndefinedConversionError) { "\xA1".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\xA1", 'windows-1257')
check_both_ways("\u00A2", "\xA2", 'windows-1257') # ¢
check_both_ways("\u00A4", "\xA4", 'windows-1257') # ¤
- assert_raise(Encoding::UndefinedConversionError) { "\xA5".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\xA5", 'windows-1257')
check_both_ways("\u00A6", "\xA6", 'windows-1257') # ¦
check_both_ways("\u00C6", "\xAF", 'windows-1257') # Æ
check_both_ways("\u00B0", "\xB0", 'windows-1257') # °
@@ -469,6 +481,25 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u00A0", "\xFF", 'IBM437') # non-breaking space
end
+ def test_IBM720
+ assert_undefined_in("\x80", 'IBM720')
+ assert_undefined_in("\x8F", 'IBM720')
+ assert_undefined_in("\x90", 'IBM720')
+ check_both_ways("\u0627", "\x9F", 'IBM720') # ا
+ check_both_ways("\u0628", "\xA0", 'IBM720') # ب
+ check_both_ways("\u00BB", "\xAF", 'IBM720') # »
+ check_both_ways("\u2591", "\xB0", 'IBM720') # â–‘
+ check_both_ways("\u2510", "\xBF", 'IBM720') # â”
+ check_both_ways("\u2514", "\xC0", 'IBM720') # â””
+ check_both_ways("\u2567", "\xCF", 'IBM720') # â•§
+ check_both_ways("\u2568", "\xD0", 'IBM720') # ╨
+ check_both_ways("\u2580", "\xDF", 'IBM720') # â–€
+ check_both_ways("\u0636", "\xE0", 'IBM720') # ض
+ check_both_ways("\u064A", "\xEF", 'IBM720') # ÙŠ
+ check_both_ways("\u2261", "\xF0", 'IBM720') # ≡
+ check_both_ways("\u00A0", "\xFF", 'IBM720') # non-breaking space
+ end
+
def test_IBM775
check_both_ways("\u0106", "\x80", 'IBM775') # Ć
check_both_ways("\u00C5", "\x8F", 'IBM775') # Ã…
@@ -539,17 +570,17 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u00A4", "\xCF", 'IBM857') # ¤
check_both_ways("\u00BA", "\xD0", 'IBM857') # º
check_both_ways("\u00C8", "\xD4", 'IBM857') # È
- assert_raise(Encoding::UndefinedConversionError) { "\xD5".encode("utf-8", 'IBM857') }
+ assert_undefined_in("\xD5", 'IBM857')
check_both_ways("\u00CD", "\xD6", 'IBM857') # Ã
check_both_ways("\u2580", "\xDF", 'IBM857') # â–€
check_both_ways("\u00D3", "\xE0", 'IBM857') # Ó
check_both_ways("\u00B5", "\xE6", 'IBM857') # µ
- assert_raise(Encoding::UndefinedConversionError) { "\xE7".encode("utf-8", 'IBM857') }
+ assert_undefined_in("\xE7", 'IBM857')
check_both_ways("\u00D7", "\xE8", 'IBM857') # ×
check_both_ways("\u00B4", "\xEF", 'IBM857') # ´
check_both_ways("\u00AD", "\xF0", 'IBM857') # soft hyphen
check_both_ways("\u00B1", "\xF1", 'IBM857') # ±
- assert_raise(Encoding::UndefinedConversionError) { "\xF2".encode("utf-8", 'IBM857') }
+ assert_undefined_in("\xF2", 'IBM857')
check_both_ways("\u00BE", "\xF3", 'IBM857') # ¾
check_both_ways("\u00A0", "\xFF", 'IBM857') # non-breaking space
end
@@ -630,6 +661,25 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u00A0", "\xFF", 'IBM863') # non-breaking space
end
+ def test_IBM864
+ check_both_ways("\u00B0", "\x80", 'IBM864') # °
+ check_both_ways("\u2518", "\x8F", 'IBM864') # ┘
+ check_both_ways("\u03B2", "\x90", 'IBM864') # β
+ check_both_ways("\uFE73", "\x9F", 'IBM864') # ï¹³
+ check_both_ways("\u00A0", "\xA0", 'IBM864') # non-breaking space
+ check_both_ways("\uFEA5", "\xAF", 'IBM864') # ﺥ
+ check_both_ways("\u0660", "\xB0", 'IBM864') # Ù 
+ check_both_ways("\u061F", "\xBF", 'IBM864') # ØŸ
+ check_both_ways("\u00A2", "\xC0", 'IBM864') # ¢
+ check_both_ways("\uFEA9", "\xCF", 'IBM864') # ﺩ
+ check_both_ways("\uFEAB", "\xD0", 'IBM864') # ﺫ
+ check_both_ways("\uFEC9", "\xDF", 'IBM864') # ﻉ
+ check_both_ways("\u0640", "\xE0", 'IBM864') # Ù€
+ check_both_ways("\uFEE1", "\xEF", 'IBM864') # ﻡ
+ check_both_ways("\uFE7D", "\xF0", 'IBM864') # ï¹½
+ check_both_ways("\u25A0", "\xFE", 'IBM864') # â– 
+ end
+
def test_IBM865
check_both_ways("\u00C7", "\x80", 'IBM865') # Ç
check_both_ways("\u00C5", "\x8F", 'IBM865') # Ã…
@@ -669,16 +719,16 @@ class TestTranscode < Test::Unit::TestCase
end
def test_IBM869
- assert_raise(Encoding::UndefinedConversionError) { "\x80".encode("utf-8", 'IBM869') }
- assert_raise(Encoding::UndefinedConversionError) { "\x85".encode("utf-8", 'IBM869') }
+ assert_undefined_in("\x80", 'IBM869')
+ assert_undefined_in("\x85", 'IBM869')
check_both_ways("\u0386", "\x86", 'IBM869') # Ά
- assert_raise(Encoding::UndefinedConversionError) { "\x87".encode("utf-8", 'IBM869') }
+ assert_undefined_in("\x87", 'IBM869')
check_both_ways("\u00B7", "\x88", 'IBM869') # ·
check_both_ways("\u0389", "\x8F", 'IBM869') # Ή
check_both_ways("\u038A", "\x90", 'IBM869') # Ί
check_both_ways("\u038C", "\x92", 'IBM869') # Ό
- assert_raise(Encoding::UndefinedConversionError) { "\x93".encode("utf-8", 'IBM869') }
- assert_raise(Encoding::UndefinedConversionError) { "\x94".encode("utf-8", 'IBM869') }
+ assert_undefined_in("\x93", 'IBM869')
+ assert_undefined_in("\x94", 'IBM869')
check_both_ways("\u038E", "\x95", 'IBM869') # ÎŽ
check_both_ways("\u03AF", "\x9F", 'IBM869') # ί
check_both_ways("\u03CA", "\xA0", 'IBM869') # ÏŠ
@@ -767,7 +817,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u03BF", "\xEF", 'macGreek') # ο
check_both_ways("\u03C0", "\xF0", 'macGreek') # π
check_both_ways("\u03B0", "\xFE", 'macGreek') # ΰ
- assert_raise(Encoding::UndefinedConversionError) { "\xFF".encode("utf-8", 'macGreek') }
+ assert_undefined_in("\xFF", 'macGreek')
end
def test_macIceland
@@ -846,7 +896,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u00D4", "\xEF", 'macTurkish') # Ô
#check_both_ways("\uF8FF", "\xF0", 'macTurkish') # Apple logo
check_both_ways("\u00D9", "\xF4", 'macTurkish') # Ù
- assert_raise(Encoding::UndefinedConversionError) { "\xF5".encode("utf-8", 'macTurkish') }
+ assert_undefined_in("\xF5", 'macTurkish')
check_both_ways("\u02C6", "\xF6", 'macTurkish') # ˆ
check_both_ways("\u02C7", "\xFF", 'macTurkish') # ˇ
end
@@ -917,11 +967,11 @@ class TestTranscode < Test::Unit::TestCase
end
def test_TIS_620
- assert_raise(Encoding::UndefinedConversionError) { "\x80".encode("utf-8", 'TIS-620') }
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'TIS-620') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'TIS-620') }
- assert_raise(Encoding::UndefinedConversionError) { "\x9F".encode("utf-8", 'TIS-620') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA0".encode("utf-8", 'TIS-620') }
+ assert_undefined_in("\x80", 'TIS-620')
+ assert_undefined_in("\x8F", 'TIS-620')
+ assert_undefined_in("\x90", 'TIS-620')
+ assert_undefined_in("\x9F", 'TIS-620')
+ assert_undefined_in("\xA0", 'TIS-620')
check_both_ways("\u0E01", "\xA1", 'TIS-620') # à¸
check_both_ways("\u0E0F", "\xAF", 'TIS-620') # à¸
check_both_ways("\u0E10", "\xB0", 'TIS-620') # à¸
@@ -930,15 +980,15 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u0E2F", "\xCF", 'TIS-620') # ฯ
check_both_ways("\u0E30", "\xD0", 'TIS-620') # ะ
check_both_ways("\u0E3A", "\xDA", 'TIS-620') # ฺ
- assert_raise(Encoding::UndefinedConversionError) { "\xDB".encode("utf-8", 'TIS-620') }
- assert_raise(Encoding::UndefinedConversionError) { "\xDE".encode("utf-8", 'TIS-620') }
+ assert_undefined_in("\xDB", 'TIS-620')
+ assert_undefined_in("\xDE", 'TIS-620')
check_both_ways("\u0E3F", "\xDF", 'TIS-620') # ฿
check_both_ways("\u0E40", "\xE0", 'TIS-620') # เ
check_both_ways("\u0E4F", "\xEF", 'TIS-620') # à¹
check_both_ways("\u0E50", "\xF0", 'TIS-620') # à¹
check_both_ways("\u0E5B", "\xFB", 'TIS-620') # ๛
- assert_raise(Encoding::UndefinedConversionError) { "\xFC".encode("utf-8", 'TIS-620') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFF".encode("utf-8", 'TIS-620') }
+ assert_undefined_in("\xFC", 'TIS-620')
+ assert_undefined_in("\xFF", 'TIS-620')
end
def test_CP850
@@ -1141,15 +1191,15 @@ class TestTranscode < Test::Unit::TestCase
expected = "\u{3042}\u{3044}\u{20bb7}"
assert_equal(expected, %w/fffe4230443042d8b7df/.pack("H*").encode("UTF-8","UTF-16"))
check_both_ways(expected, %w/feff30423044d842dfb7/.pack("H*"), "UTF-16")
- assert_raise(Encoding::InvalidByteSequenceError){%w/feffdfb7/.pack("H*").encode("UTF-8","UTF-16")}
- assert_raise(Encoding::InvalidByteSequenceError){%w/fffeb7df/.pack("H*").encode("UTF-8","UTF-16")}
+ assert_invalid_in(%w/feffdfb7/.pack("H*"), "UTF-16")
+ assert_invalid_in(%w/fffeb7df/.pack("H*"), "UTF-16")
end
def test_utf_32_bom
expected = "\u{3042}\u{3044}\u{20bb7}"
assert_equal(expected, %w/fffe00004230000044300000b70b0200/.pack("H*").encode("UTF-8","UTF-32"))
check_both_ways(expected, %w/0000feff000030420000304400020bb7/.pack("H*"), "UTF-32")
- assert_raise(Encoding::InvalidByteSequenceError){%w/0000feff00110000/.pack("H*").encode("UTF-8","UTF-32")}
+ assert_invalid_in(%w/0000feff00110000/.pack("H*"), "UTF-32")
end
def check_utf_32_both_ways(utf8, raw)
@@ -1331,24 +1381,24 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u71FC", "\xE0\x9E", 'shift_jis') # 燼
check_both_ways("\u71F9", "\xE0\x9F", 'shift_jis') # 燹
check_both_ways("\u73F1", "\xE0\xFC", 'shift_jis') # ç±
- assert_raise(Encoding::UndefinedConversionError) { "\xEF\x40".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xEF\x7E".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xEF\x80".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xEF\x9E".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xEF\x9F".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xEF\xFC".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xF0\x40".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xF0\x7E".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xF0\x80".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xF0\x9E".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xF0\x9F".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xF0\xFC".encode("utf-8", 'shift_jis') }
+ assert_undefined_in("\xEF\x40", 'shift_jis')
+ assert_undefined_in("\xEF\x7E", 'shift_jis')
+ assert_undefined_in("\xEF\x80", 'shift_jis')
+ assert_undefined_in("\xEF\x9E", 'shift_jis')
+ assert_undefined_in("\xEF\x9F", 'shift_jis')
+ assert_undefined_in("\xEF\xFC", 'shift_jis')
+ assert_undefined_in("\xF0\x40", 'shift_jis')
+ assert_undefined_in("\xF0\x7E", 'shift_jis')
+ assert_undefined_in("\xF0\x80", 'shift_jis')
+ assert_undefined_in("\xF0\x9E", 'shift_jis')
+ assert_undefined_in("\xF0\x9F", 'shift_jis')
+ assert_undefined_in("\xF0\xFC", 'shift_jis')
#check_both_ways("\u9ADC", "\xFC\x40", 'shift_jis') # 髜 (IBM extended)
- assert_raise(Encoding::UndefinedConversionError) { "\xFC\x7E".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFC\x80".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFC\x9E".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFC\x9F".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFC\xFC".encode("utf-8", 'shift_jis') }
+ assert_undefined_in("\xFC\x7E", 'shift_jis')
+ assert_undefined_in("\xFC\x80", 'shift_jis')
+ assert_undefined_in("\xFC\x9E", 'shift_jis')
+ assert_undefined_in("\xFC\x9F", 'shift_jis')
+ assert_undefined_in("\xFC\xFC", 'shift_jis')
check_both_ways("\u677E\u672C\u884C\u5F18", "\x8f\xbc\x96\x7b\x8d\x73\x8d\x4f", 'shift_jis') # æ¾æœ¬è¡Œå¼˜
check_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\x90\xC2\x8E\x52\x8A\x77\x89\x40\x91\xE5\x8A\x77", 'shift_jis') # é’山学院大学
check_both_ways("\u795E\u6797\u7FA9\u535A", "\x90\x5F\x97\xD1\x8B\x60\x94\x8E", 'shift_jis') # 神林義åš
@@ -1368,34 +1418,34 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u00F7", "\xA1\xE0", 'euc-jp') # ÷
check_both_ways("\u25C7", "\xA1\xFE", 'euc-jp') # â—‡
check_both_ways("\u25C6", "\xA2\xA1", 'euc-jp') # â—†
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xAF".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xB9".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xC2".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xC9".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xD1".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xDB".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xEB".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xF1".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xFA".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xFD".encode("utf-8", 'euc-jp') }
+ assert_undefined_in("\xA2\xAF", 'euc-jp')
+ assert_undefined_in("\xA2\xB9", 'euc-jp')
+ assert_undefined_in("\xA2\xC2", 'euc-jp')
+ assert_undefined_in("\xA2\xC9", 'euc-jp')
+ assert_undefined_in("\xA2\xD1", 'euc-jp')
+ assert_undefined_in("\xA2\xDB", 'euc-jp')
+ assert_undefined_in("\xA2\xEB", 'euc-jp')
+ assert_undefined_in("\xA2\xF1", 'euc-jp')
+ assert_undefined_in("\xA2\xFA", 'euc-jp')
+ assert_undefined_in("\xA2\xFD", 'euc-jp')
check_both_ways("\u25EF", "\xA2\xFE", 'euc-jp') # â—¯
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xAF".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xBA".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xC0".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xDB".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xE0".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xFB".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA4\xF4".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA5\xF7".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA6\xB9".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA6\xC0".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA6\xD9".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA7\xC2".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA7\xD0".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA7\xF2".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA8\xC1".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xCF\xD4".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xCF\xFE".encode("utf-8", 'euc-jp') }
+ assert_undefined_in("\xA3\xAF", 'euc-jp')
+ assert_undefined_in("\xA3\xBA", 'euc-jp')
+ assert_undefined_in("\xA3\xC0", 'euc-jp')
+ assert_undefined_in("\xA3\xDB", 'euc-jp')
+ assert_undefined_in("\xA3\xE0", 'euc-jp')
+ assert_undefined_in("\xA3\xFB", 'euc-jp')
+ assert_undefined_in("\xA4\xF4", 'euc-jp')
+ assert_undefined_in("\xA5\xF7", 'euc-jp')
+ assert_undefined_in("\xA6\xB9", 'euc-jp')
+ assert_undefined_in("\xA6\xC0", 'euc-jp')
+ assert_undefined_in("\xA6\xD9", 'euc-jp')
+ assert_undefined_in("\xA7\xC2", 'euc-jp')
+ assert_undefined_in("\xA7\xD0", 'euc-jp')
+ assert_undefined_in("\xA7\xF2", 'euc-jp')
+ assert_undefined_in("\xA8\xC1", 'euc-jp')
+ assert_undefined_in("\xCF\xD4", 'euc-jp')
+ assert_undefined_in("\xCF\xFE", 'euc-jp')
check_both_ways("\u6A97", "\xDD\xA1", 'euc-jp') # 檗
check_both_ways("\u6BEF", "\xDD\xDF", 'euc-jp') # 毯
check_both_ways("\u9EBE", "\xDD\xE0", 'euc-jp') # 麾
@@ -1408,7 +1458,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u71FC", "\xDF\xFE", 'euc-jp') # 燼
check_both_ways("\u71F9", "\xE0\xA1", 'euc-jp') # 燹
check_both_ways("\u73F1", "\xE0\xFE", 'euc-jp') # ç±
- assert_raise(Encoding::UndefinedConversionError) { "\xF4\xA7".encode("utf-8", 'euc-jp') }
+ assert_undefined_in("\xF4\xA7", 'euc-jp')
#check_both_ways("\u9ADC", "\xFC\xE3", 'euc-jp') # 髜 (IBM extended)
check_both_ways("\u677E\u672C\u884C\u5F18", "\xBE\xBE\xCB\xDC\xB9\xD4\xB9\xB0", 'euc-jp') # æ¾æœ¬è¡Œå¼˜
@@ -1440,7 +1490,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u2127", "\xA3\xE0", 'euc-jis-2004') # â„§
check_both_ways("\u30A0", "\xA3\xFB", 'euc-jis-2004') # ã‚ 
check_both_ways("\uFF54", "\xA3\xF4", 'euc-jis-2004') # ï½”
- assert_raise(Encoding::UndefinedConversionError) { "\xA5\xF7".encode("utf-8", 'euc-jis-2004') }
+ assert_undefined_in("\xA5\xF7", 'euc-jis-2004')
check_both_ways("\u2664", "\xA6\xB9", 'euc-jis-2004') # ♤
check_both_ways("\u2663", "\xA6\xC0", 'euc-jis-2004') # ♣
check_both_ways("\u03C2", "\xA6\xD9", 'euc-jis-2004') # Ï‚
@@ -1525,33 +1575,33 @@ class TestTranscode < Test::Unit::TestCase
end
def test_eucjp_sjis_undef
- assert_raise(Encoding::UndefinedConversionError) { "\x8e\xe0".encode("Shift_JIS", "EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\x8e\xfe".encode("Shift_JIS", "EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\x8f\xa1\xa1".encode("Shift_JIS", "EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\x8f\xa1\xfe".encode("Shift_JIS", "EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\x8f\xfe\xa1".encode("Shift_JIS", "EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\x8f\xfe\xfe".encode("Shift_JIS", "EUC-JP") }
-
- assert_raise(Encoding::UndefinedConversionError) { "\xf0\x40".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xf0\x7e".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xf0\x80".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xf0\xfc".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xfc\x40".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xfc\x7e".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xfc\x80".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xfc\xfc".encode("EUC-JP", "Shift_JIS") }
+ assert_undefined_conversion("\x8e\xe0", "Shift_JIS", "EUC-JP")
+ assert_undefined_conversion("\x8e\xfe", "Shift_JIS", "EUC-JP")
+ assert_undefined_conversion("\x8f\xa1\xa1", "Shift_JIS", "EUC-JP")
+ assert_undefined_conversion("\x8f\xa1\xfe", "Shift_JIS", "EUC-JP")
+ assert_undefined_conversion("\x8f\xfe\xa1", "Shift_JIS", "EUC-JP")
+ assert_undefined_conversion("\x8f\xfe\xfe", "Shift_JIS", "EUC-JP")
+
+ assert_undefined_conversion("\xf0\x40", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xf0\x7e", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xf0\x80", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xf0\xfc", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xfc\x40", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xfc\x7e", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xfc\x80", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xfc\xfc", "EUC-JP", "Shift_JIS")
end
def test_iso_2022_jp
- assert_raise(Encoding::InvalidByteSequenceError) { "\x1b(A".encode("utf-8", "iso-2022-jp") }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x1b$(A".encode("utf-8", "iso-2022-jp") }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x1b$C".encode("utf-8", "iso-2022-jp") }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x0e".encode("utf-8", "iso-2022-jp") }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x80".encode("utf-8", "iso-2022-jp") }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x1b$(Dd!\x1b(B".encode("utf-8", "iso-2022-jp") }
- assert_raise(Encoding::UndefinedConversionError) { "\u9299".encode("iso-2022-jp") }
- assert_raise(Encoding::UndefinedConversionError) { "\uff71\uff72\uff73\uff74\uff75".encode("iso-2022-jp") }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x1b(I12345\x1b(B".encode("utf-8", "iso-2022-jp") }
+ assert_invalid_in("\x1b(A", "iso-2022-jp")
+ assert_invalid_in("\x1b$(A", "iso-2022-jp")
+ assert_invalid_in("\x1b$C", "iso-2022-jp")
+ assert_invalid_in("\x0e", "iso-2022-jp")
+ assert_invalid_in("\x80", "iso-2022-jp")
+ assert_invalid_in("\x1b$(Dd!\x1b(B", "iso-2022-jp")
+ assert_undefined_conversion("\u9299", "iso-2022-jp")
+ assert_undefined_conversion("\uff71\uff72\uff73\uff74\uff75", "iso-2022-jp")
+ assert_invalid_in("\x1b(I12345\x1b(B", "iso-2022-jp")
assert_equal("\xA1\xA1".force_encoding("euc-jp"),
"\e$B!!\e(B".encode("EUC-JP", "ISO-2022-JP"))
assert_equal("\e$B!!\e(B".force_encoding("ISO-2022-JP"),
@@ -1584,6 +1634,8 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("\e$B%*!+%,%I%J!+%N!+%P%\\%^!+%Q%]%\"\e(B".force_encoding("cp50220"),
"\xB5\xDE\xB6\xDE\xC4\xDE\xC5\xDE\xC9\xDE\xCA\xDE\xCE\xDE\xCF\xDE\xCA\xDF\xCE\xDF\xB1".
encode("cp50220", "sjis"))
+ assert_equal("\e$B\x21\x23\e(I\x7E\e(B".force_encoding("cp50220"),
+ "\x8E\xA1\x8E\xFE".encode("cp50220", "cp51932"))
end
def test_iso_2022_jp_1
@@ -1614,11 +1666,11 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("\u005C", "\e(J\x5C\e(B".encode("UTF-8", "ISO-2022-JP"))
assert_equal("\u005C", "\x5C".encode("stateless-ISO-2022-JP", "ISO-2022-JP"))
assert_equal("\u005C", "\e(J\x5C\e(B".encode("stateless-ISO-2022-JP", "ISO-2022-JP"))
- assert_raise(Encoding::UndefinedConversionError) { "\u00A5".encode("Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\u00A5".encode("Windows-31J") }
- assert_raise(Encoding::UndefinedConversionError) { "\u00A5".encode("EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\u00A5".encode("eucJP-ms") }
- assert_raise(Encoding::UndefinedConversionError) { "\u00A5".encode("CP51932") }
+ assert_undefined_conversion("\u00A5", "Shift_JIS")
+ assert_undefined_conversion("\u00A5", "Windows-31J")
+ assert_undefined_conversion("\u00A5", "EUC-JP")
+ assert_undefined_conversion("\u00A5", "eucJP-ms")
+ assert_undefined_conversion("\u00A5", "CP51932")
# FULLWIDTH REVERSE SOLIDUS
check_both_ways("\uFF3C", "\x81\x5F", "Shift_JIS")
@@ -1639,21 +1691,21 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("\u007E", "\e(J\x7E\e(B".encode("UTF-8", "ISO-2022-JP"))
assert_equal("\u007E", "\x7E".encode("stateless-ISO-2022-JP", "ISO-2022-JP"))
assert_equal("\u007E", "\e(J\x7E\e(B".encode("stateless-ISO-2022-JP", "ISO-2022-JP"))
- assert_raise(Encoding::UndefinedConversionError) { "\u203E".encode("Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\u203E".encode("Windows-31J") }
- assert_raise(Encoding::UndefinedConversionError) { "\u203E".encode("EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\u203E".encode("eucJP-ms") }
- assert_raise(Encoding::UndefinedConversionError) { "\u203E".encode("CP51932") }
+ assert_undefined_conversion("\u203E", "Shift_JIS")
+ assert_undefined_conversion("\u203E", "Windows-31J")
+ assert_undefined_conversion("\u203E", "EUC-JP")
+ assert_undefined_conversion("\u203E", "eucJP-ms")
+ assert_undefined_conversion("\u203E", "CP51932")
end
def test_gb2312
check_both_ways("\u3000", "\xA1\xA1", 'GB2312') # full-width space
check_both_ways("\u3013", "\xA1\xFE", 'GB2312') # 〓
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xB0".encode("utf-8", 'GB2312') }
+ assert_undefined_in("\xA2\xB0", 'GB2312')
check_both_ways("\u2488", "\xA2\xB1", 'GB2312') # â’ˆ
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xE4".encode("utf-8", 'GB2312') }
+ assert_undefined_in("\xA2\xE4", 'GB2312')
check_both_ways("\u3220", "\xA2\xE5", 'GB2312') # ㈠
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xF0".encode("utf-8", 'GB2312') }
+ assert_undefined_in("\xA2\xF0", 'GB2312')
check_both_ways("\u2160", "\xA2\xF1", 'GB2312') # â… 
check_both_ways("\uFF01", "\xA3\xA1", 'GB2312') # ï¼
check_both_ways("\uFFE3", "\xA3\xFE", 'GB2312') # ï¿£
@@ -1664,9 +1716,9 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u0410", "\xA7\xA1", 'GB2312') # Ð
check_both_ways("\u0430", "\xA7\xD1", 'GB2312') # а
check_both_ways("\u0101", "\xA8\xA1", 'GB2312') # Ä
- assert_raise(Encoding::UndefinedConversionError) { "\xA8\xC4".encode("utf-8", 'GB2312') }
+ assert_undefined_in("\xA8\xC4", 'GB2312')
check_both_ways("\u3105", "\xA8\xC5", 'GB2312') # ã„…
- assert_raise(Encoding::UndefinedConversionError) { "\xA9\xA3".encode("utf-8", 'GB2312') }
+ assert_undefined_in("\xA9\xA3", 'GB2312')
check_both_ways("\u2500", "\xA9\xA4", 'GB2312') # ─
check_both_ways("\u554A", "\xB0\xA1", 'GB2312') # 啊
check_both_ways("\u5265", "\xB0\xFE", 'GB2312') # 剥
@@ -1680,7 +1732,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u7384", "\xD0\xFE", 'GB2312') # 玄
check_both_ways("\u4F4F", "\xD7\xA1", 'GB2312') # ä½
check_both_ways("\u5EA7", "\xD7\xF9", 'GB2312') # 座
- assert_raise(Encoding::UndefinedConversionError) { "\xD7\xFA".encode("utf-8", 'GB2312') }
+ assert_undefined_in("\xD7\xFA", 'GB2312')
check_both_ways("\u647A", "\xDF\xA1", 'GB2312') # 摺
check_both_ways("\u553C", "\xDF\xFE", 'GB2312') # 唼
check_both_ways("\u5537", "\xE0\xA1", 'GB2312') # å”·
@@ -1718,48 +1770,48 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u3000", "\xA1\xA1", 'GBK') # full-width space
check_both_ways("\u3001", "\xA1\xA2", 'GBK') # ã€
check_both_ways("\u3013", "\xA1\xFE", 'GBK') # 〓
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xA0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA2\xA0", 'GBK')
check_both_ways("\u2170", "\xA2\xA1", 'GBK') # â…°
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xB0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA2\xB0", 'GBK')
check_both_ways("\u2488", "\xA2\xB1", 'GBK') # â’ˆ
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xE4".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA2\xE4", 'GBK')
check_both_ways("\u3220", "\xA2\xE5", 'GBK') # ㈠
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xF0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA2\xF0", 'GBK')
check_both_ways("\u2160", "\xA2\xF1", 'GBK') # â… 
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xA0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA3\xA0", 'GBK')
check_both_ways("\uFF01", "\xA3\xA1", 'GBK') # ï¼
check_both_ways("\uFFE3", "\xA3\xFE", 'GBK') # ï¿£
- assert_raise(Encoding::UndefinedConversionError) { "\xA4\xA0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA4\xA0", 'GBK')
check_both_ways("\u3041", "\xA4\xA1", 'GBK') # ã
- assert_raise(Encoding::UndefinedConversionError) { "\xA5\xA0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA5\xA0", 'GBK')
check_both_ways("\u30A1", "\xA5\xA1", 'GBK') # ã‚¡
check_both_ways("\u0391", "\xA6\xA1", 'GBK') # Α
check_both_ways("\u03B1", "\xA6\xC1", 'GBK') # α
- assert_raise(Encoding::UndefinedConversionError) { "\xA6\xED".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA6\xED", 'GBK')
check_both_ways("\uFE3B", "\xA6\xEE", 'GBK') # ︻
check_both_ways("\u0410", "\xA7\xA1", 'GBK') # Ð
check_both_ways("\u0430", "\xA7\xD1", 'GBK') # а
check_both_ways("\u02CA", "\xA8\x40", 'GBK') # ËŠ
check_both_ways("\u2587", "\xA8\x7E", 'GBK') # â–‡
- assert_raise(Encoding::UndefinedConversionError) { "\xA8\x96".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA8\x96", 'GBK')
check_both_ways("\u0101", "\xA8\xA1", 'GBK') # Ä
- assert_raise(Encoding::UndefinedConversionError) { "\xA8\xBC".encode("utf-8", 'GBK') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA8\xBF".encode("utf-8", 'GBK') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA8\xC4".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA8\xBC", 'GBK')
+ assert_undefined_in("\xA8\xBF", 'GBK')
+ assert_undefined_in("\xA8\xC4", 'GBK')
check_both_ways("\u3105", "\xA8\xC5", 'GBK') # ã„…
check_both_ways("\u3021", "\xA9\x40", 'GBK') # 〡
- assert_raise(Encoding::UndefinedConversionError) { "\xA9\x58".encode("utf-8", 'GBK') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA9\x5B".encode("utf-8", 'GBK') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA9\x5D".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA9\x58", 'GBK')
+ assert_undefined_in("\xA9\x5B", 'GBK')
+ assert_undefined_in("\xA9\x5D", 'GBK')
check_both_ways("\u3007", "\xA9\x96", 'GBK') # 〇
- assert_raise(Encoding::UndefinedConversionError) { "\xA9\xA3".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA9\xA3", 'GBK')
check_both_ways("\u2500", "\xA9\xA4", 'GBK') # ─
- assert_raise(Encoding::UndefinedConversionError) { "\xA9\xF0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA9\xF0", 'GBK')
check_both_ways("\u7588", "\xAF\x40", 'GBK') # ç–ˆ
check_both_ways("\u7607", "\xAF\x7E", 'GBK') # 瘇
check_both_ways("\u7608", "\xAF\x80", 'GBK') # 瘈
check_both_ways("\u7644", "\xAF\xA0", 'GBK') # 癄
- assert_raise(Encoding::UndefinedConversionError) { "\xAF\xA1".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xAF\xA1", 'GBK')
check_both_ways("\u7645", "\xB0\x40", 'GBK') # ç™…
check_both_ways("\u769B", "\xB0\x7E", 'GBK') # çš›
check_both_ways("\u769C", "\xB0\x80", 'GBK') # 皜
@@ -1800,10 +1852,10 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u9F78", "\xFD\x7E", 'GBK') # 齸
check_both_ways("\u9F79", "\xFD\x80", 'GBK') # é½¹
check_both_ways("\uF9F1", "\xFD\xA0", 'GBK') # ï§±
- assert_raise(Encoding::UndefinedConversionError) { "\xFD\xA1".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xFD\xA1", 'GBK')
check_both_ways("\uFA0C", "\xFE\x40", 'GBK') # 兀
check_both_ways("\uFA29", "\xFE\x4F", 'GBK') # 﨩
- assert_raise(Encoding::UndefinedConversionError) { "\xFE\x50".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xFE\x50", 'GBK')
check_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\xC7\xE0\xC9\xBD\xD1\xA7\xD4\xBA\xB4\xF3\xD1\xA7", 'GBK') # é’山学院大学
check_both_ways("\u795E\u6797\u7FA9\u535A", "\xC9\xF1\xC1\xD6\xC1\x78\xB2\xA9", 'GBK') # 神林義åš
end
@@ -1839,48 +1891,48 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u3000", "\xA1\xA1", 'GB18030') # full-width space
check_both_ways("\u3001", "\xA1\xA2", 'GB18030') #
check_both_ways("\u3013", "\xA1\xFE", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xA2\xA0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA2\xA0", 'GB18030')
check_both_ways("\u2170", "\xA2\xA1", 'GB18030') # â…°
- #assert_raise(Encoding::UndefinedConversionError) { "\xA2\xB0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA2\xB0", 'GB18030')
check_both_ways("\u2488", "\xA2\xB1", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xA2\xE4".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA2\xE4", 'GB18030')
check_both_ways("\u3220", "\xA2\xE5", 'GB18030') # ㈠
- #assert_raise(Encoding::UndefinedConversionError) { "\xA2\xF0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA2\xF0", 'GB18030')
check_both_ways("\u2160", "\xA2\xF1", 'GB18030') # â… 
- #assert_raise(Encoding::UndefinedConversionError) { "\xA3\xA0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA3\xA0", 'GB18030')
check_both_ways("\uFF01", "\xA3\xA1", 'GB18030') # E
check_both_ways("\uFFE3", "\xA3\xFE", 'GB18030') # E
- #assert_raise(Encoding::UndefinedConversionError) { "\xA4\xA0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA4\xA0", 'GB18030')
check_both_ways("\u3041", "\xA4\xA1", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xA5\xA0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA5\xA0", 'GB18030')
check_both_ways("\u30A1", "\xA5\xA1", 'GB18030') # ã‚¡
check_both_ways("\u0391", "\xA6\xA1", 'GB18030') #
check_both_ways("\u03B1", "\xA6\xC1", 'GB18030') # α
- #assert_raise(Encoding::UndefinedConversionError) { "\xA6\xED".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA6\xED", 'GB18030')
check_both_ways("\uFE3B", "\xA6\xEE", 'GB18030') # E
check_both_ways("\u0410", "\xA7\xA1", 'GB18030') #
check_both_ways("\u0430", "\xA7\xD1", 'GB18030') # а
check_both_ways("\u02CA", "\xA8\x40", 'GB18030') #
check_both_ways("\u2587", "\xA8\x7E", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xA8\x96".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA8\x96", 'GB18030')
check_both_ways("\u0101", "\xA8\xA1", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xA8\xBC".encode("utf-8", 'GB18030') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xA8\xBF".encode("utf-8", 'GB18030') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xA8\xC4".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA8\xBC", 'GB18030')
+ #assert_undefined_in("\xA8\xBF", 'GB18030')
+ #assert_undefined_in("\xA8\xC4", 'GB18030')
check_both_ways("\u3105", "\xA8\xC5", 'GB18030') #
check_both_ways("\u3021", "\xA9\x40", 'GB18030') # 〡
- #assert_raise(Encoding::UndefinedConversionError) { "\xA9\x58".encode("utf-8", 'GB18030') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xA9\x5B".encode("utf-8", 'GB18030') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xA9\x5D".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA9\x58", 'GB18030')
+ #assert_undefined_in("\xA9\x5B", 'GB18030')
+ #assert_undefined_in("\xA9\x5D", 'GB18030')
check_both_ways("\u3007", "\xA9\x96", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xA9\xA3".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA9\xA3", 'GB18030')
check_both_ways("\u2500", "\xA9\xA4", 'GB18030') # ─
- #assert_raise(Encoding::UndefinedConversionError) { "\xA9\xF0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA9\xF0", 'GB18030')
check_both_ways("\u7588", "\xAF\x40", 'GB18030') #
check_both_ways("\u7607", "\xAF\x7E", 'GB18030') #
check_both_ways("\u7608", "\xAF\x80", 'GB18030') #
check_both_ways("\u7644", "\xAF\xA0", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xAF\xA1".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xAF\xA1", 'GB18030')
check_both_ways("\u7645", "\xB0\x40", 'GB18030') #
check_both_ways("\u769B", "\xB0\x7E", 'GB18030') #
check_both_ways("\u769C", "\xB0\x80", 'GB18030') #
@@ -1921,10 +1973,10 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u9F78", "\xFD\x7E", 'GB18030') # 齸
check_both_ways("\u9F79", "\xFD\x80", 'GB18030') # é½¹
check_both_ways("\uF9F1", "\xFD\xA0", 'GB18030') # E
- #assert_raise(Encoding::UndefinedConversionError) { "\xFD\xA1".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xFD\xA1", 'GB18030')
check_both_ways("\uFA0C", "\xFE\x40", 'GB18030') # E
check_both_ways("\uFA29", "\xFE\x4F", 'GB18030') # E
- #assert_raise(Encoding::UndefinedConversionError) { "\xFE\x50".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xFE\x50", 'GB18030')
check_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\xC7\xE0\xC9\xBD\xD1\xA7\xD4\xBA\xB4\xF3\xD1\xA7", 'GB18030') # é’山学院大学
check_both_ways("\u795E\u6797\u7FA9\u535A", "\xC9\xF1\xC1\xD6\xC1\x78\xB2\xA9", 'GB18030') # 神林義
@@ -1979,7 +2031,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u310F", "\xA3\x7E", 'Big5') # ã„
check_both_ways("\u3110", "\xA3\xA1", 'Big5') # ã„
check_both_ways("\u02CB", "\xA3\xBF", 'Big5') # Ë‹
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xC0".encode("utf-8", 'Big5') }
+ assert_undefined_in("\xA3\xC0", 'Big5')
check_both_ways("\u6D6C", "\xAF\x40", 'Big5') # 浬
check_both_ways("\u7837", "\xAF\x7E", 'Big5') # ç ·
check_both_ways("\u7825", "\xAF\xA1", 'Big5') # ç ¥
@@ -1998,9 +2050,9 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u77AC", "\xC0\xFE", 'Big5') # 瞬
check_both_ways("\u8B96", "\xC6\x40", 'Big5') # è®–
check_both_ways("\u7C72", "\xC6\x7E", 'Big5') # ç±²
- #assert_raise(Encoding::UndefinedConversionError) { "\xC6\xA1".encode("utf-8", 'Big5') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xC7\x40".encode("utf-8", 'Big5') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xC8\x40".encode("utf-8", 'Big5') }
+ #assert_undefined_in("\xC6\xA1", 'Big5')
+ #assert_undefined_in("\xC7\x40", 'Big5')
+ #assert_undefined_in("\xC8\x40", 'Big5')
check_both_ways("\u4E42", "\xC9\x40", 'Big5') # 乂
check_both_ways("\u6C15", "\xC9\x7E", 'Big5') # æ°•
check_both_ways("\u6C36", "\xC9\xA1", 'Big5') # æ°¶
@@ -2033,7 +2085,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u9F0A", "\xF9\x7E", 'Big5') # 鼊
check_both_ways("\u9FA4", "\xF9\xA1", 'Big5') # 龤
check_both_ways("\u9F98", "\xF9\xD5", 'Big5') # 龘
- #assert_raise(Encoding::UndefinedConversionError) { "\xF9\xD6".encode("utf-8", 'Big5') }
+ #assert_undefined_in("\xF9\xD6", 'Big5')
check_both_ways("\u795E\u6797\u7FA9\u535A", "\xAF\xAB\xAA\x4C\xB8\x71\xB3\xD5", 'Big5') # 神林義åš
end
@@ -2046,7 +2098,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u310F", "\xA3\x7E", 'Big5-HKSCS') # ã„
check_both_ways("\u3110", "\xA3\xA1", 'Big5-HKSCS') # ã„
check_both_ways("\u02CB", "\xA3\xBF", 'Big5-HKSCS') # Ë‹
- #assert_raise(Encoding::UndefinedConversionError) { "\xA3\xC0".encode("utf-8", 'Big5-HKSCS') }
+ #assert_undefined_in("\xA3\xC0", 'Big5-HKSCS')
check_both_ways("\u6D6C", "\xAF\x40", 'Big5-HKSCS') # 浬
check_both_ways("\u7837", "\xAF\x7E", 'Big5-HKSCS') # ç ·
check_both_ways("\u7825", "\xAF\xA1", 'Big5-HKSCS') # ç ¥
@@ -2065,9 +2117,9 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u77AC", "\xC0\xFE", 'Big5-HKSCS') # 瞬
check_both_ways("\u8B96", "\xC6\x40", 'Big5-HKSCS') # è®–
check_both_ways("\u7C72", "\xC6\x7E", 'Big5-HKSCS') # ç±²
- #assert_raise(Encoding::UndefinedConversionError) { "\xC6\xA1".encode("utf-8", 'Big5-HKSCS') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xC7\x40".encode("utf-8", 'Big5-HKSCS') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xC8\x40".encode("utf-8", 'Big5-HKSCS') }
+ #assert_undefined_in("\xC6\xA1", 'Big5-HKSCS')
+ #assert_undefined_in("\xC7\x40", 'Big5-HKSCS')
+ #assert_undefined_in("\xC8\x40", 'Big5-HKSCS')
check_both_ways("\u4E42", "\xC9\x40", 'Big5-HKSCS') # 乂
check_both_ways("\u6C15", "\xC9\x7E", 'Big5-HKSCS') # æ°•
check_both_ways("\u6C36", "\xC9\xA1", 'Big5-HKSCS') # æ°¶
@@ -2101,7 +2153,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u9FA4", "\xF9\xA1", 'Big5-HKSCS') # 龤
check_both_ways("\u9F98", "\xF9\xD5", 'Big5-HKSCS') # 龘
#check_both_ways("\u{23ED7}", "\x8E\x40", 'Big5-HKSCS') # 𣻗
- #assert_raise(Encoding::UndefinedConversionError) { "\xF9\xD6".encode("utf-8", 'Big5-HKSCS') }
+ #assert_undefined_in("\xF9\xD6", 'Big5-HKSCS')
check_both_ways("\u795E\u6797\u7FA9\u535A", "\xAF\xAB\xAA\x4C\xB8\x71\xB3\xD5", 'Big5-HKSCS') # 神林義åš
end
@@ -2116,6 +2168,28 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("D\u00FCrst", "\xC4\xDC\x99\xA2\xA3", 'IBM037') # Dürst
end
+ def test_CESU_8
+ check_both_ways("aijrszAIJRSZ09", "aijrszAIJRSZ09", 'CESU-8') # single bytes
+
+ # check NULL explicitly
+ # this is different in CESU-8 and in Java modified UTF-8 strings
+ check_both_ways("\0", "\0", 'CESU-8')
+
+ # U+0080 U+00FC U+00FF U+0100 U+0400 U+0700 U+07FF
+ two_byte_chars = "\xC2\x80\x20\xC3\xBC\x20\xC3\xBF\x20\xC4\x80\x20\xD0\x80\x20\xDC\x80\x20\xDF\xBF"
+ check_both_ways(two_byte_chars, two_byte_chars, 'CESU-8')
+
+ # U+0800 U+2200 U+4E00 U+D7FF U+E000 U+FFFF
+ three_byte_chars = "\xE0\xA0\x80\x20\xE2\x88\x80\x20\xE4\xB8\x80\x20\xED\x9F\xBF\x20\xEE\x80\x80\x20\xEF\xBF\xBF"
+ check_both_ways(three_byte_chars, three_byte_chars, 'CESU-8')
+
+ # characters outside BMP (double surrogates in CESU-8)
+ # U+10000 U+20000 U+50000 U+10FFFF
+ utf8 = "\xF0\x90\x80\x80 \xF0\xA0\x80\x80 \xF1\x90\x80\x80 \xF4\x8F\xBF\xBF"
+ cesu = "\xED\xA0\x80\xED\xB0\x80 \xED\xA1\x80\xED\xB0\x80 \xED\xA4\x80\xED\xB0\x80 \xED\xAF\xBF\xED\xBF\xBF"
+ check_both_ways(utf8, cesu, 'CESU-8')
+ end
+
def test_nothing_changed
a = "James".force_encoding("US-ASCII")
b = a.encode("Shift_JIS")
@@ -2161,12 +2235,20 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("U+3042", "\u{3042}".encode("US-ASCII", fallback: fallback.method(:escape)))
end
- bug8940 = '[ruby-core:57318] [Bug #8940]'
- %w[UTF-32 UTF-16].each do |enc|
- define_method("test_pseudo_encoding_inspect(#{enc})") do
- assert_normal_exit("'aaa'.encode('#{enc}').inspect", bug8940)
- assert_equal(4, 'aaa'.encode(enc).length, "should count in #{enc} with BOM")
+ def test_fallback_aref
+ fallback = Object.new
+ def fallback.[](x)
+ "U+%.4X" % x.unpack("U")
end
+ assert_equal("U+3042", "\u{3042}".encode("US-ASCII", fallback: fallback))
+ end
+
+ def test_pseudo_encoding_inspect
+ s = 'aaa'.encode "UTF-16"
+ assert_equal '"\xFE\xFF\x00\x61\x00\x61\x00\x61"', s.inspect
+
+ s = 'aaa'.encode "UTF-32"
+ assert_equal '"\x00\x00\xFE\xFF\x00\x00\x00\x61\x00\x00\x00\x61\x00\x00\x00\x61"', s.inspect
end
def test_encode_with_invalid_chars
@@ -2204,7 +2286,7 @@ class TestTranscode < Test::Unit::TestCase
result = th.map(&:value)
end
end
- expected = "\xa4\xa2".force_encoding(Encoding::EUC_JP)
+ expected = "\xa4\xa2".dup.force_encoding(Encoding::EUC_JP)
assert_equal([expected]*num, result, bug11277)
end;
end
@@ -2220,12 +2302,138 @@ class TestTranscode < Test::Unit::TestCase
"#{bug} coderange should not have side effects")
end
- def test_universal_newline
+ def test_newline_options
bug11324 = '[ruby-core:69841] [Bug #11324]'
usascii = Encoding::US_ASCII
s = "A\nB\r\nC".force_encoding(usascii)
assert_equal("A\nB\nC", s.encode(usascii, universal_newline: true), bug11324)
assert_equal("A\nB\nC", s.encode(usascii, universal_newline: true, undef: :replace), bug11324)
assert_equal("A\nB\nC", s.encode(usascii, universal_newline: true, undef: :replace, replace: ''), bug11324)
+ assert_equal("A\nB\nC", s.encode(usascii, newline: :universal))
+ assert_equal("A\nB\nC", s.encode(usascii, newline: :universal, undef: :replace))
+ assert_equal("A\nB\nC", s.encode(usascii, newline: :universal, undef: :replace, replace: ''))
+ assert_equal("A\rB\r\rC", s.encode(usascii, cr_newline: true))
+ assert_equal("A\rB\r\rC", s.encode(usascii, newline: :cr))
+ assert_equal("A\r\nB\r\r\nC", s.encode(usascii, crlf_newline: true))
+ assert_equal("A\r\nB\r\r\nC", s.encode(usascii, newline: :crlf))
+ assert_equal("A\nB\nC", s.encode(usascii, lf_newline: true))
+ 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)
+ assert_conversion_both_ways(utf8, 'utf-8', raw, encoding)
+ end
+ alias check_both_ways assert_conversion_both_ways_utf8
+
+ def assert_conversion_both_ways(str1, enc1, str2, enc2)
+ message = str1.dump+str2.dump
+ assert_equal(str1.force_encoding(enc1), str2.encode(enc1, enc2), message)
+ assert_equal(str2.force_encoding(enc2), str1.encode(enc2, enc1), message)
+ end
+ alias check_both_ways2 assert_conversion_both_ways
+
+ def assert_undefined_conversion(str, to, from = nil)
+ assert_raise(Encoding::UndefinedConversionError) { str.encode(to, from) }
+ end
+
+ def assert_undefined_in(str, encoding)
+ assert_undefined_conversion(str, 'utf-8', encoding)
+ end
+
+ def assert_invalid_byte_sequence(str, to, from = nil)
+ assert_raise(Encoding::InvalidByteSequenceError) { str.encode(to, from) }
+ end
+
+ def assert_invalid_in(str, encoding)
+ assert_invalid_byte_sequence(str, 'utf-8', encoding)
end
end
diff --git a/test/ruby/test_undef.rb b/test/ruby/test_undef.rb
index 6d513a238f..074b92be55 100644
--- a/test/ruby/test_undef.rb
+++ b/test/ruby/test_undef.rb
@@ -25,7 +25,7 @@ class TestUndef < Test::Unit::TestCase
y = Undef1.new
assert_equal "bar", y.bar
z = Undef2.new
- assert_raise(NoMethodError) { z.foo }
+ assert_raise(NoMethodError) { z.bar }
end
def test_special_const_undef
@@ -35,4 +35,20 @@ class TestUndef < Test::Unit::TestCase
end
end
end
+
+ def test_singleton_undef
+ klass = Class.new do
+ def foo
+ :ok
+ end
+ end
+
+ klass.new.foo
+
+ klass.new.instance_eval do
+ undef foo
+ end
+
+ klass.new.foo
+ end
end
diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb
index a9b1fd50ff..13b8a7905f 100644
--- a/test/ruby/test_variable.rb
+++ b/test/ruby/test_variable.rb
@@ -29,9 +29,163 @@ class TestVariable < Test::Unit::TestCase
@@rule = "Cronus" # modifies @@rule in Gods
include Olympians
def ruler4
- EnvUtil.suppress_warning {
- @@rule
- }
+ @@rule
+ end
+ end
+
+ Athena = Gods.clone
+
+ def test_cloned_classes_copy_cvar_cache
+ assert_equal "Cronus", Athena.new.ruler0
+ end
+
+ def test_setting_class_variable_on_module_through_inheritance
+ mod = Module.new
+ mod.class_variable_set(:@@foo, 1)
+ mod.freeze
+ c = Class.new { include(mod) }
+ assert_raise(FrozenError) { c.class_variable_set(:@@foo, 2) }
+ assert_raise(FrozenError) { c.class_eval("@@foo = 2") }
+ assert_equal(1, c.class_variable_get(:@@foo))
+ end
+
+ Zeus = Gods.clone
+
+ def test_cloned_allows_setting_cvar
+ Zeus.class_variable_set(:@@rule, "Athena")
+
+ god = Gods.new.ruler0
+ zeus = Zeus.new.ruler0
+
+ assert_equal "Cronus", god
+ assert_equal "Athena", zeus
+ assert_not_equal god.object_id, zeus.object_id
+ end
+
+ def test_singleton_class_included_class_variable
+ c = Class.new
+ c.extend(Olympians)
+ assert_empty(c.singleton_class.class_variables)
+ assert_raise(NameError){ c.singleton_class.class_variable_get(:@@rule) }
+ c.class_variable_set(:@@foo, 1)
+ assert_equal([:@@foo], c.singleton_class.class_variables)
+ assert_equal(1, c.singleton_class.class_variable_get(:@@foo))
+
+ c = Class.new
+ c.extend(Olympians)
+ sc = Class.new(c)
+ assert_empty(sc.singleton_class.class_variables)
+ assert_raise(NameError){ sc.singleton_class.class_variable_get(:@@rule) }
+ c.class_variable_set(:@@foo, 1)
+ assert_equal([:@@foo], sc.singleton_class.class_variables)
+ assert_equal(1, sc.singleton_class.class_variable_get(:@@foo))
+
+ c = Class.new
+ o = c.new
+ o.extend(Olympians)
+ assert_equal([:@@rule], o.singleton_class.class_variables)
+ assert_equal("Zeus", o.singleton_class.class_variable_get(:@@rule))
+ c.class_variable_set(:@@foo, 1)
+ assert_equal([:@@foo, :@@rule], o.singleton_class.class_variables.sort)
+ assert_equal(1, o.singleton_class.class_variable_get(:@@foo))
+ end
+
+ def test_cvar_overtaken_by_parent_class
+ error = eval <<~EORB
+ class Parent
+ end
+
+ class Child < Parent
+ @@cvar = 1
+
+ def self.cvar
+ @@cvar
+ end
+ end
+
+ assert_equal 1, Child.cvar
+
+ class Parent
+ @@cvar = 2
+ end
+
+ assert_raise RuntimeError do
+ Child.cvar
+ end
+ EORB
+
+ assert_equal "class variable @@cvar of TestVariable::Child is overtaken by TestVariable::Parent", error.message
+ ensure
+ TestVariable.send(:remove_const, :Child) rescue nil
+ TestVariable.send(:remove_const, :Parent) rescue nil
+ end
+
+ def test_cvar_overtaken_by_module
+ error = eval <<~EORB
+ class ParentForModule
+ @@cvar = 1
+
+ def self.cvar
+ @@cvar
+ end
+ end
+
+ assert_equal 1, ParentForModule.cvar
+
+ module Mixin
+ @@cvar = 2
+ end
+
+ class ParentForModule
+ include Mixin
+ end
+
+ assert_raise RuntimeError do
+ ParentForModule.cvar
+ end
+ EORB
+
+ assert_equal "class variable @@cvar of TestVariable::ParentForModule is overtaken by TestVariable::Mixin", error.message
+ ensure
+ TestVariable.send(:remove_const, :Mixin) rescue nil
+ TestVariable.send(:remove_const, :ParentForModule) rescue nil
+ end
+
+ class IncludeRefinedModuleClassVariableNoWarning
+ module Mod
+ @@_test_include_refined_module_class_variable = true
+ end
+
+ module Mod2
+ refine Mod do
+ end
+ end
+
+ include Mod
+
+ def t
+ @@_test_include_refined_module_class_variable
+ end
+ end
+
+ def test_include_refined_module_class_variable
+ assert_warning('') do
+ IncludeRefinedModuleClassVariableNoWarning.new.t
+ end
+ end
+
+ def test_set_class_variable_on_frozen_object
+ set_cvar = EnvUtil.labeled_class("SetCVar")
+ set_cvar.class_eval "#{<<~"begin;"}\n#{<<~'end;'}"
+ begin;
+ def self.set(val)
+ @@a = val # inline cache
+ end
+ end;
+ set_cvar.set(1) # fill write cache
+ set_cvar.freeze
+ assert_raise(FrozenError, "[Bug #19341]") do
+ set_cvar.set(2) # hit write cache, but should check frozen status
end
end
@@ -56,7 +210,7 @@ class TestVariable < Test::Unit::TestCase
atlas = Titans.new
assert_equal("Cronus", atlas.ruler0)
assert_equal("Zeus", atlas.ruler3)
- assert_equal("Cronus", atlas.ruler4)
+ assert_raise(RuntimeError) { atlas.ruler4 }
assert_nothing_raised do
class << Gods
defined?(@@rule) && @@rule
@@ -112,6 +266,84 @@ class TestVariable < Test::Unit::TestCase
assert_include(gv, :$12)
end
+ def prepare_klass_for_test_svar_with_ifunc
+ Class.new do
+ include Enumerable
+ def each(&b)
+ @b = b
+ end
+
+ def check1
+ check2.merge({check1: $1})
+ end
+
+ def check2
+ @b.call('foo')
+ {check2: $1}
+ end
+ end
+ end
+
+ def test_svar_with_ifunc
+ c = prepare_klass_for_test_svar_with_ifunc
+
+ expected_check1_result = {
+ check1: nil, check2: nil
+ }.freeze
+
+ obj = c.new
+ result = nil
+ obj.grep(/(f..)/){
+ result = $1
+ }
+ assert_equal nil, result
+ assert_equal nil, $1
+ assert_equal expected_check1_result, obj.check1
+ assert_equal 'foo', result
+ assert_equal 'foo', $1
+
+ # this frame was escaped so try it again
+ $~ = nil
+ obj = c.new
+ result = nil
+ obj.grep(/(f..)/){
+ result = $1
+ }
+ assert_equal nil, result
+ assert_equal nil, $1
+ assert_equal expected_check1_result, obj.check1
+ assert_equal 'foo', result
+ assert_equal 'foo', $1
+
+ # different context
+ result = nil
+ Fiber.new{
+ obj = c.new
+ obj.grep(/(f..)/){
+ result = $1
+ }
+ }.resume # obj is created in antoher Fiber
+ assert_equal nil, result
+ assert_equal expected_check1_result, obj.check1
+ assert_equal 'foo', result
+ assert_equal 'foo', $1
+
+ # different thread context
+ result = nil
+ Thread.new{
+ obj = c.new
+ obj.grep(/(f..)/){
+ result = $1
+ }
+ }.join # obj is created in another Thread
+
+ assert_equal nil, result
+ assert_equal expected_check1_result, obj.check1
+ assert_equal 'foo', result
+ assert_equal 'foo', $1
+ end
+
+
def test_global_variable_0
assert_in_out_err(["-e", "$0='t'*1000;print $0"], "", /\At+\z/, [])
end
@@ -135,12 +367,18 @@ class TestVariable < Test::Unit::TestCase
def test_special_constant_ivars
[ true, false, :symbol, "dsym#{rand(9999)}".to_sym, 1, 1.0 ].each do |v|
assert_empty v.instance_variables
- msg = "can't modify frozen #{v.class}"
+ msg = "can't modify frozen #{v.class}: #{v.inspect}"
assert_raise_with_message(FrozenError, msg) do
v.instance_variable_set(:@foo, :bar)
end
+ assert_raise_with_message(FrozenError, msg, "[Bug #19339]") do
+ v.instance_eval do
+ @a = 1
+ end
+ end
+
assert_nil EnvUtil.suppress_warning {v.instance_variable_get(:@foo)}
assert_not_send([v, :instance_variable_defined?, :@foo])
@@ -150,12 +388,163 @@ 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
+ @b = 2
+ @c = 3
+ end
+
+ def ivars
+ [@a, @b, @c]
+ end
+ end
+
+ def test_external_ivars
+ 3.times{
+ # check inline cache for external ivar access
+ assert_equal [1, 2, 3], ExIvar.new.ivars
+ }
+ 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)
assert_equal(%i(v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11), v, bug11674)
end
+ def test_many_instance_variables
+ objects = [Object.new, Hash.new, Module.new]
+ objects.each do |obj|
+ 1000.times do |i|
+ obj.instance_variable_set("@var#{i}", i)
+ end
+ 1000.times do |i|
+ assert_equal(i, obj.instance_variable_get("@var#{i}"))
+ end
+ end
+ end
+
+ def test_local_variables_encoding
+ α = 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 68f0fa7f27..d183e03391 100644
--- a/test/ruby/test_vm_dump.rb
+++ b/test/ruby/test_vm_dump.rb
@@ -1,21 +1,23 @@
# frozen_string_literal: true
require 'test/unit'
+return unless /darwin/ =~ RUBY_PLATFORM
+
class TestVMDump < Test::Unit::TestCase
- def assert_darwin_vm_dump_works(args)
- skip if RUBY_PLATFORM !~ /darwin/
- assert_in_out_err(args, "", [], /^\[IMPORTANT\]/)
+ def assert_darwin_vm_dump_works(args, timeout=nil)
+ args.unshift({"RUBY_ON_BUG" => nil, "RUBY_CRASH_REPORT" => nil})
+ assert_in_out_err(args, "", [], /^\[IMPORTANT\]/, timeout: timeout || 300)
end
def test_darwin_invalid_call
- assert_darwin_vm_dump_works(['-rfiddle', '-eFiddle::Function.new(Fiddle::Pointer.new(1), [], Fiddle::TYPE_VOID).call'])
+ assert_darwin_vm_dump_works(['-r-test-/fatal', '-eBug.invalid_call(1)'])
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
- assert_darwin_vm_dump_works(['-rfiddle', '-eFiddle.dlunwrap(100).class'])
+ assert_darwin_vm_dump_works(['-r-test-/fatal', '-eBug.invalid_access(100)'])
end
end
diff --git a/test/ruby/test_warning.rb b/test/ruby/test_warning.rb
new file mode 100644
index 0000000000..cd220ff00f
--- /dev/null
+++ b/test/ruby/test_warning.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+
+class TestWarning < Test::Unit::TestCase
+ def test_warn_called_only_when_category_enabled
+ # Assert that warn is called when the category is enabled
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ Warning[:deprecated] = true
+ $warnings = []
+ def Warning.warn(msg, category:)
+ $warnings << [msg, category]
+ end
+ assert_equal(0, $warnings.length)
+ "" << 12
+ assert_equal(1, $warnings.length)
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ Warning[:deprecated] = false
+ $warnings = []
+ def Warning.warn(msg, category:)
+ $warnings << [msg, category]
+ end
+ assert_equal(0, $warnings.length)
+ "" << 12
+ assert_equal(0, $warnings.length, $warnings.join)
+ end;
+ end
+end
diff --git a/test/ruby/test_weakkeymap.rb b/test/ruby/test_weakkeymap.rb
new file mode 100644
index 0000000000..91c1538076
--- /dev/null
+++ b/test/ruby/test_weakkeymap.rb
@@ -0,0 +1,159 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestWeakKeyMap < Test::Unit::TestCase
+ def setup
+ @wm = ObjectSpace::WeakKeyMap.new
+ end
+
+ def test_map
+ x = Object.new
+ k = "foo"
+ @wm[k] = x
+ assert_same(x, @wm[k])
+ assert_same(x, @wm["FOO".downcase])
+ end
+
+ def test_aset_const
+ x = Object.new
+ assert_raise(ArgumentError) { @wm[true] = x }
+ assert_raise(ArgumentError) { @wm[false] = x }
+ assert_raise(ArgumentError) { @wm[nil] = x }
+ assert_raise(ArgumentError) { @wm[42] = x }
+ assert_raise(ArgumentError) { @wm[2**128] = x }
+ assert_raise(ArgumentError) { @wm[1.23] = x }
+ assert_raise(ArgumentError) { @wm[:foo] = x }
+ assert_raise(ArgumentError) { @wm["foo#{rand}".to_sym] = x }
+ end
+
+ def test_getkey
+ k = "foo"
+ @wm[k] = true
+ assert_same(k, @wm.getkey("FOO".downcase))
+ end
+
+ def test_key?
+ assert_weak_include(:key?, "foo")
+ assert_not_send([@wm, :key?, "bar"])
+ end
+
+ def test_delete
+ k1 = "foo"
+ x1 = Object.new
+ @wm[k1] = x1
+ assert_equal x1, @wm[k1]
+ assert_equal x1, @wm.delete(k1)
+ assert_nil @wm[k1]
+ assert_nil @wm.delete(k1)
+
+ fallback = @wm.delete(k1) do |key|
+ assert_equal k1, key
+ 42
+ end
+ assert_equal 42, fallback
+ end
+
+ def test_clear
+ k = "foo"
+ @wm[k] = true
+ assert @wm[k]
+ assert_same @wm, @wm.clear
+ refute @wm[k]
+ end
+
+ def test_clear_bug_20691
+ assert_normal_exit(<<~RUBY)
+ map = ObjectSpace::WeakKeyMap.new
+
+ 1_000.times do
+ 1_000.times do
+ map[Object.new] = nil
+ end
+
+ map.clear
+ end
+ RUBY
+ end
+
+ def test_inspect
+ x = Object.new
+ k = Object.new
+ @wm[k] = x
+ assert_match(/\A\#<#{@wm.class.name}:[\dxa-f]+ size=\d+>\z/, @wm.inspect)
+
+ 1000.times do |i|
+ @wm[i.to_s] = Object.new
+ @wm.inspect
+ end
+ assert_match(/\A\#<#{@wm.class.name}:[\dxa-f]+ size=\d+>\z/, @wm.inspect)
+ end
+
+ def test_no_hash_method
+ k = BasicObject.new
+ assert_raise NoMethodError do
+ @wm[k] = 42
+ end
+ end
+
+ def test_frozen_object
+ o = Object.new.freeze
+ assert_nothing_raised(FrozenError) {@wm[o] = 'foo'}
+ assert_nothing_raised(FrozenError) {@wm['foo'] = o}
+ end
+
+ def test_inconsistent_hash_key_memory_leak
+ assert_no_memory_leak [], '', <<~RUBY
+ class BadHash
+ def initialize
+ @hash = 0
+ end
+
+ def hash
+ @hash += 1
+ end
+ end
+
+ k = BadHash.new
+ wm = ObjectSpace::WeakKeyMap.new
+
+ 100_000.times do |i|
+ wm[k] = i
+ end
+ RUBY
+ end
+
+ def test_compaction
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ assert_separately(%w(-robjspace), <<-'end;')
+ wm = ObjectSpace::WeakKeyMap.new
+ key = Object.new
+ val = Object.new
+ wm[key] = val
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_equal(val, wm[key])
+ end;
+ end
+
+ def test_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress { ObjectSpace::WeakKeyMap.new }
+ end
+
+ private
+
+ def assert_weak_include(m, k, n = 100)
+ if n > 0
+ return assert_weak_include(m, k, n-1)
+ end
+ 1.times do
+ x = Object.new
+ @wm[k] = x
+ assert_send([@wm, m, k])
+ assert_send([@wm, m, "FOO".downcase])
+ x = Object.new
+ end
+ end
+end
diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb
index cde186c5f3..4f5823ecf4 100644
--- a/test/ruby/test_weakmap.rb
+++ b/test/ruby/test_weakmap.rb
@@ -16,30 +16,50 @@ class TestWeakMap < Test::Unit::TestCase
def test_aset_const
x = Object.new
- assert_raise(ArgumentError) {@wm[true] = x}
- assert_raise(ArgumentError) {@wm[false] = x}
- assert_raise(ArgumentError) {@wm[nil] = x}
- assert_raise(ArgumentError) {@wm[42] = x}
- assert_raise(ArgumentError) {@wm[:foo] = x}
- assert_raise(ArgumentError) {@wm[x] = true}
- assert_raise(ArgumentError) {@wm[x] = false}
- assert_raise(ArgumentError) {@wm[x] = nil}
- assert_raise(ArgumentError) {@wm[x] = 42}
- assert_raise(ArgumentError) {@wm[x] = :foo}
+ @wm[true] = x
+ assert_same(x, @wm[true])
+ @wm[false] = x
+ assert_same(x, @wm[false])
+ @wm[nil] = x
+ assert_same(x, @wm[nil])
+ @wm[42] = x
+ assert_same(x, @wm[42])
+ @wm[:foo] = x
+ assert_same(x, @wm[:foo])
+
+ @wm[x] = true
+ assert_same(true, @wm[x])
+ @wm[x] = false
+ assert_same(false, @wm[x])
+ @wm[x] = nil
+ assert_same(nil, @wm[x])
+ @wm[x] = 42
+ assert_same(42, @wm[x])
+ @wm[x] = :foo
+ assert_same(:foo, @wm[x])
end
- def test_include?
- m = __callee__[/test_(.*)/, 1]
- k = "foo"
+ def assert_weak_include(m, k, n = 100)
+ if n > 0
+ return assert_weak_include(m, k, n-1)
+ end
1.times do
x = Object.new
@wm[k] = x
assert_send([@wm, m, k])
assert_not_send([@wm, m, "FOO".downcase])
- x = nil
+ x = Object.new
+ end
+ end
+
+ def test_include?
+ m = __callee__[/test_(.*)/, 1]
+ k = "foo"
+ 1.times do
+ assert_weak_include(m, k)
end
GC.start
- skip # TODO: failure introduced from r60440
+ pend('TODO: failure introduced from 837fd5e494731d7d44786f29e7d6e8c27029806f')
assert_not_send([@wm, m, k])
end
alias test_member? test_include?
@@ -53,6 +73,31 @@ class TestWeakMap < Test::Unit::TestCase
@wm.inspect)
end
+ def test_inspect_garbage
+ 1000.times do |i|
+ @wm[i] = Object.new
+ @wm.inspect
+ end
+ assert_match(/\A\#<#{@wm.class.name}:0x[\da-f]+(?::(?: \d+ => \#<(?:Object|collected):0x[\da-f]+>,?)+)?>\z/,
+ @wm.inspect)
+ end
+
+ def test_delete
+ k1 = "foo"
+ x1 = Object.new
+ @wm[k1] = x1
+ assert_equal x1, @wm[k1]
+ assert_equal x1, @wm.delete(k1)
+ assert_nil @wm[k1]
+ assert_nil @wm.delete(k1)
+
+ fallback = @wm.delete(k1) do |key|
+ assert_equal k1, key
+ 42
+ end
+ assert_equal 42, fallback
+ end
+
def test_each
m = __callee__[/test_(.*)/, 1]
x1 = Object.new
@@ -132,4 +177,115 @@ class TestWeakMap < Test::Unit::TestCase
assert_equal(2, @wm.__send__(m))
end
alias test_length test_size
+
+ def test_frozen_object
+ o = Object.new.freeze
+ assert_nothing_raised(FrozenError) {@wm[o] = 'foo'}
+ assert_nothing_raised(FrozenError) {@wm['foo'] = o}
+ end
+
+ def test_no_memory_leak
+ assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #19398]", rss: true, limit: 1.5, timeout: 60)
+ begin;
+ 1_000_000.times do
+ ObjectSpace::WeakMap.new
+ end
+ end;
+ end
+
+ def test_compaction
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ # [Bug #19529]
+ obj = Object.new
+ 100.times do |i|
+ GC.compact
+ @wm[i] = obj
+ end
+
+ assert_ruby_status([], <<-'end;')
+ wm = ObjectSpace::WeakMap.new
+ obj = Object.new
+ 100.times do
+ wm[Object.new] = obj
+ GC.start
+ end
+ GC.compact
+ end;
+
+ assert_separately(%w(-robjspace), <<-'end;')
+ wm = ObjectSpace::WeakMap.new
+ key = Object.new
+ val = Object.new
+ wm[key] = val
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_equal(val, wm[key])
+ end;
+
+ assert_ruby_status(["-W0"], <<-'end;')
+ wm = ObjectSpace::WeakMap.new
+
+ ary = 10_000.times.map do
+ o = Object.new
+ wm[o] = 1
+ o
+ end
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+ end;
+ end
+
+ def test_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress { ObjectSpace::WeakMap.new }
+ end
+
+ def test_replaced_values_bug_19531
+ a = "A".dup
+ b = "B".dup
+
+ @wm[1] = a
+ @wm[1] = a
+ @wm[1] = a
+
+ @wm[1] = b
+ assert_equal b, @wm[1]
+
+ a = nil
+ GC.start
+
+ assert_equal b, @wm[1]
+ end
+
+ def test_use_after_free_bug_20688
+ assert_normal_exit(<<~RUBY)
+ weakmap = ObjectSpace::WeakMap.new
+ 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_whileuntil.rb b/test/ruby/test_whileuntil.rb
index 121c44817d..ff6d29ac4a 100644
--- a/test/ruby/test_whileuntil.rb
+++ b/test/ruby/test_whileuntil.rb
@@ -73,6 +73,24 @@ class TestWhileuntil < Test::Unit::TestCase
}
end
+ def test_begin_while
+ i = 0
+ sum = 0
+ begin
+ i += 1
+ sum += i
+ end while i < 10
+ assert_equal([10, 55], [i, sum])
+
+ i = 0
+ sum = 0
+ (
+ i += 1
+ sum += i
+ ) while false
+ assert_equal([0, 0], [i, sum])
+ end
+
def test_until
i = 0
until i>4
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
new file mode 100644
index 0000000000..d6b9b75648
--- /dev/null
+++ b/test/ruby/test_yjit.rb
@@ -0,0 +1,1985 @@
+# frozen_string_literal: true
+#
+# This set of tests can be run with:
+# make test-all TESTS='test/ruby/test_yjit.rb'
+
+require 'test/unit'
+require 'envutil'
+require 'tmpdir'
+require_relative '../lib/jit_support'
+
+return unless JITSupport.yjit_supported?
+
+require 'stringio'
+
+# Tests for YJIT with assertions on compilation and side exits
+# insipired by the RJIT tests in test/ruby/test_rjit.rb
+class TestYJIT < Test::Unit::TestCase
+ running_with_yjit = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
+
+ def test_yjit_in_ruby_description
+ assert_includes(RUBY_DESCRIPTION, '+YJIT')
+ end if running_with_yjit
+
+ # Check that YJIT is in the version string
+ def test_yjit_in_version
+ [
+ %w(--version --yjit),
+ %w(--version --disable-yjit --yjit),
+ %w(--version --disable-yjit --enable-yjit),
+ %w(--version --disable-yjit --enable=yjit),
+ %w(--version --disable=yjit --yjit),
+ %w(--version --disable=yjit --enable-yjit),
+ %w(--version --disable=yjit --enable=yjit),
+ %w(--version --jit),
+ %w(--version --disable-jit --jit),
+ %w(--version --disable-jit --enable-jit),
+ %w(--version --disable-jit --enable=jit),
+ %w(--version --disable=jit --yjit),
+ %w(--version --disable=jit --enable-jit),
+ %w(--version --disable=jit --enable=jit),
+ ].each do |version_args|
+ assert_in_out_err(version_args) do |stdout, stderr|
+ assert_equal(RUBY_DESCRIPTION, stdout.first)
+ assert_equal([], stderr)
+ end
+ end
+ end if running_with_yjit
+
+ def test_command_line_switches
+ assert_in_out_err('--yjit-', '', [], /invalid option --yjit-/)
+ assert_in_out_err('--yjithello', '', [], /invalid option --yjithello/)
+ #assert_in_out_err('--yjit-call-threshold', '', [], /--yjit-call-threshold needs an argument/)
+ #assert_in_out_err('--yjit-call-threshold=', '', [], /--yjit-call-threshold needs an argument/)
+ end
+
+ def test_yjit_enable
+ args = []
+ args << "--disable=yjit" if RubyVM::YJIT.enabled?
+ assert_separately(args, <<~'RUBY')
+ refute_predicate RubyVM::YJIT, :enabled?
+ refute_includes RUBY_DESCRIPTION, "+YJIT"
+
+ RubyVM::YJIT.enable
+
+ assert_predicate RubyVM::YJIT, :enabled?
+ assert_includes RUBY_DESCRIPTION, "+YJIT"
+ RUBY
+ end
+
+ def test_yjit_disable
+ assert_separately(["--yjit", "--yjit-disable"], <<~'RUBY')
+ refute_predicate RubyVM::YJIT, :enabled?
+ refute_includes RUBY_DESCRIPTION, "+YJIT"
+
+ RubyVM::YJIT.enable
+
+ assert_predicate RubyVM::YJIT, :enabled?
+ assert_includes RUBY_DESCRIPTION, "+YJIT"
+ RUBY
+ end
+
+ def test_yjit_enable_stats_false
+ assert_separately(["--yjit-disable", "--yjit-stats"], <<~RUBY, ignore_stderr: true)
+ assert_false RubyVM::YJIT.enabled?
+ assert_nil RubyVM::YJIT.runtime_stats
+
+ RubyVM::YJIT.enable
+
+ assert_true RubyVM::YJIT.enabled?
+ assert_true RubyVM::YJIT.runtime_stats[:all_stats]
+ RUBY
+ end
+
+ def test_yjit_enable_stats_true
+ args = []
+ args << "--disable=yjit" if RubyVM::YJIT.enabled?
+ assert_separately(args, <<~RUBY, ignore_stderr: true)
+ assert_false RubyVM::YJIT.enabled?
+ assert_nil RubyVM::YJIT.runtime_stats
+
+ RubyVM::YJIT.enable(stats: true)
+
+ assert_true RubyVM::YJIT.enabled?
+ assert_true RubyVM::YJIT.runtime_stats[:all_stats]
+ RUBY
+ end
+
+ def test_yjit_enable_stats_quiet
+ assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(stats: true)']) do |_stdout, stderr, _status|
+ assert_not_empty stderr
+ end
+ assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(stats: :quiet)']) do |_stdout, stderr, _status|
+ assert_empty stderr
+ end
+ end
+
+ def test_yjit_enable_with_call_threshold
+ assert_separately(%w[--yjit-disable --yjit-call-threshold=1], <<~RUBY)
+ def not_compiled = nil
+ def will_compile = nil
+ def compiled_counts = RubyVM::YJIT.runtime_stats&.dig(:compiled_iseq_count)
+
+ not_compiled
+ assert_nil compiled_counts
+ assert_false RubyVM::YJIT.enabled?
+
+ RubyVM::YJIT.enable
+
+ will_compile
+ assert compiled_counts > 0
+ assert_true RubyVM::YJIT.enabled?
+ RUBY
+ end
+
+ def test_yjit_enable_with_monkey_patch
+ assert_ruby_status(%w[--yjit-disable], <<~RUBY)
+ # This lets rb_method_entry_at(rb_mKernel, ...) return NULL
+ Kernel.prepend(Module.new)
+
+ # This must not crash with "undefined optimized method!"
+ RubyVM::YJIT.enable
+ RUBY
+ end
+
+ def test_yjit_enable_with_valid_runtime_call_threshold_option
+ assert_in_out_err(['--yjit-disable', '-e',
+ 'RubyVM::YJIT.enable(call_threshold: 1); puts RubyVM::YJIT.enabled?']) do |stdout, stderr, _status|
+ assert_empty stderr
+ assert_include stdout.join, "true"
+ end
+ end
+
+ def test_yjit_enable_with_invalid_runtime_call_threshold_option
+ assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(mem_size: 0)']) do |stdout, stderr, status|
+ assert_not_empty stderr
+ assert_match(/ArgumentError/, stderr.join)
+ assert_equal 1, status.exitstatus
+ end
+ end
+
+ def test_yjit_enable_with_invalid_runtime_mem_size_option
+ assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(mem_size: 0)']) do |stdout, stderr, status|
+ assert_not_empty stderr
+ assert_match(/ArgumentError/, stderr.join)
+ assert_equal 1, status.exitstatus
+ 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)
+ refute_includes(stderr, "NoMethodError")
+ end
+
+ def test_enable_from_env_var
+ yjit_child_env = {'RUBY_YJIT_ENABLE' => '1'}
+ assert_in_out_err([yjit_child_env, '--version'], '') do |stdout, stderr|
+ assert_equal(RUBY_DESCRIPTION, stdout.first)
+ assert_equal([], stderr)
+ end
+ assert_in_out_err([yjit_child_env, '-e puts RUBY_DESCRIPTION'], '', [RUBY_DESCRIPTION])
+ assert_in_out_err([yjit_child_env, '-e p RubyVM::YJIT.enabled?'], '', ['true'])
+ end if running_with_yjit
+
+ def test_compile_setclassvariable
+ script = 'class Foo; def self.foo; @@foo = 1; end; end; Foo.foo'
+ assert_compiles(script, insns: %i[setclassvariable], result: 1)
+ end
+
+ def test_compile_getclassvariable
+ script = 'class Foo; @@foo = 1; def self.foo; @@foo; end; end; Foo.foo'
+ assert_compiles(script, insns: %i[getclassvariable], result: 1)
+ end
+
+ def test_compile_putnil
+ assert_compiles('nil', insns: %i[putnil], result: nil)
+ end
+
+ def test_compile_putobject
+ assert_compiles('true', insns: %i[putobject], result: true)
+ assert_compiles('123', insns: %i[putobject], result: 123)
+ assert_compiles(':foo', insns: %i[putobject], result: :foo)
+ end
+
+ def test_compile_opt_succ
+ assert_compiles('1.succ', insns: %i[opt_succ], result: 2)
+ end
+
+ def test_compile_opt_not
+ assert_compiles('!false', insns: %i[opt_not], result: true)
+ assert_compiles('!nil', insns: %i[opt_not], result: true)
+ assert_compiles('!true', insns: %i[opt_not], result: false)
+ assert_compiles('![]', insns: %i[opt_not], result: false)
+ end
+
+ def test_compile_opt_newarray
+ assert_compiles('[]', insns: %i[newarray], result: [])
+ assert_compiles('[1+1]', insns: %i[newarray opt_plus], result: [2])
+ assert_compiles('[1,1+1,3,4,5,6]', insns: %i[newarray opt_plus], result: [1, 2, 3, 4, 5, 6])
+ end
+
+ def test_compile_opt_duparray
+ assert_compiles('[1]', insns: %i[duparray], result: [1])
+ assert_compiles('[1, 2, 3]', insns: %i[duparray], result: [1, 2, 3])
+ end
+
+ def test_compile_newrange
+ assert_compiles('s = 1; (s..5)', insns: %i[newrange], result: 1..5)
+ assert_compiles('s = 1; e = 5; (s..e)', insns: %i[newrange], result: 1..5)
+ assert_compiles('s = 1; (s...5)', insns: %i[newrange], result: 1...5)
+ assert_compiles('s = 1; (s..)', insns: %i[newrange], result: 1..)
+ assert_compiles('e = 5; (..e)', insns: %i[newrange], result: ..5)
+ end
+
+ def test_compile_duphash
+ assert_compiles('{ two: 2 }', insns: %i[duphash], result: { two: 2 })
+ end
+
+ def test_compile_newhash
+ assert_compiles('{}', insns: %i[newhash], result: {})
+ assert_compiles('{ two: 1 + 1 }', insns: %i[newhash], result: { two: 2 })
+ assert_compiles('{ 1 + 1 => :two }', insns: %i[newhash], result: { 2 => :two })
+ end
+
+ def test_compile_opt_nil_p
+ assert_compiles('nil.nil?', insns: %i[opt_nil_p], result: true)
+ assert_compiles('false.nil?', insns: %i[opt_nil_p], result: false)
+ assert_compiles('true.nil?', insns: %i[opt_nil_p], result: false)
+ assert_compiles('(-"").nil?', insns: %i[opt_nil_p], result: false)
+ assert_compiles('123.nil?', insns: %i[opt_nil_p], result: false)
+ end
+
+ def test_compile_eq_fixnum
+ assert_compiles('123 == 123', insns: %i[opt_eq], result: true)
+ assert_compiles('123 == 456', insns: %i[opt_eq], result: false)
+ end
+
+ def test_compile_eq_string
+ assert_compiles('-"" == -""', insns: %i[opt_eq], result: true)
+ assert_compiles('-"foo" == -"foo"', insns: %i[opt_eq], result: true)
+ assert_compiles('-"foo" == -"bar"', insns: %i[opt_eq], result: false)
+ end
+
+ def test_compile_eq_symbol
+ assert_compiles(':foo == :foo', insns: %i[opt_eq], result: true)
+ assert_compiles(':foo == :bar', insns: %i[opt_eq], result: false)
+ assert_compiles(':foo == "foo".to_sym', insns: %i[opt_eq], result: true)
+ end
+
+ def test_compile_eq_object
+ assert_compiles(<<~RUBY, insns: %i[opt_eq], result: false)
+ def eq(a, b)
+ a == b
+ end
+
+ eq(Object.new, Object.new)
+ RUBY
+
+ assert_compiles(<<~RUBY, insns: %i[opt_eq], result: true)
+ def eq(a, b)
+ a == b
+ end
+
+ obj = Object.new
+ eq(obj, obj)
+ RUBY
+ end
+
+ def test_compile_eq_arbitrary_class
+ assert_compiles(<<~RUBY, insns: %i[opt_eq], result: "yes")
+ def eq(a, b)
+ a == b
+ end
+
+ class Foo
+ def ==(other)
+ "yes"
+ end
+ end
+
+ eq(Foo.new, Foo.new)
+ eq(Foo.new, Foo.new)
+ RUBY
+ end
+
+ def test_compile_opt_lt
+ assert_compiles('1 < 2', insns: %i[opt_lt])
+ assert_compiles('"a" < "b"', insns: %i[opt_lt])
+ end
+
+ def test_compile_opt_le
+ assert_compiles('1 <= 2', insns: %i[opt_le])
+ assert_compiles('"a" <= "b"', insns: %i[opt_le])
+ end
+
+ def test_compile_opt_gt
+ assert_compiles('1 > 2', insns: %i[opt_gt])
+ assert_compiles('"a" > "b"', insns: %i[opt_gt])
+ end
+
+ def test_compile_opt_ge
+ assert_compiles('1 >= 2', insns: %i[opt_ge])
+ assert_compiles('"a" >= "b"', insns: %i[opt_ge])
+ end
+
+ def test_compile_opt_plus
+ assert_compiles('1 + 2', insns: %i[opt_plus])
+ assert_compiles('"a" + "b"', insns: %i[opt_plus])
+ assert_compiles('[:foo] + [:bar]', insns: %i[opt_plus])
+ end
+
+ def test_compile_opt_minus
+ assert_compiles('1 - 2', insns: %i[opt_minus])
+ assert_compiles('[:foo, :bar] - [:bar]', insns: %i[opt_minus])
+ end
+
+ def test_compile_opt_or
+ assert_compiles('1 | 2', insns: %i[opt_or])
+ assert_compiles('[:foo] | [:bar]', insns: %i[opt_or])
+ end
+
+ def test_compile_opt_and
+ assert_compiles('1 & 2', insns: %i[opt_and])
+ assert_compiles('[:foo, :bar] & [:bar]', insns: %i[opt_and])
+ end
+
+ def test_compile_set_and_get_global
+ assert_compiles('$foo = 123; $foo', insns: %i[setglobal], result: 123)
+ end
+
+ def test_compile_putspecialobject
+ assert_compiles('-> {}', insns: %i[putspecialobject])
+ end
+
+ def test_compile_tostring
+ assert_no_exits('"i am a string #{true}"')
+ end
+
+ def test_compile_opt_aset
+ assert_compiles('[1,2,3][2] = 4', insns: %i[opt_aset], frozen_string_literal: false)
+ assert_compiles('{}[:foo] = :bar', insns: %i[opt_aset], frozen_string_literal: false)
+ assert_compiles('[1,2,3][0..-1] = []', insns: %i[opt_aset], frozen_string_literal: false)
+ assert_compiles('"foo"[3] = "d"', insns: %i[opt_aset], frozen_string_literal: false)
+ end
+
+ def test_compile_attr_set
+ assert_no_exits(<<~EORB)
+ class Foo
+ attr_accessor :bar
+ end
+
+ foo = Foo.new
+ foo.bar = 3
+ foo.bar = 3
+ foo.bar = 3
+ foo.bar = 3
+ EORB
+ end
+
+ def test_compile_regexp
+ assert_no_exits('/#{true}/')
+ end
+
+ def test_compile_dynamic_symbol
+ assert_compiles(':"#{"foo"}"', insns: %i[intern])
+ assert_compiles('s = "bar"; :"foo#{s}"', insns: %i[intern])
+ end
+
+ def test_getlocal_with_level
+ assert_compiles(<<~RUBY, insns: %i[getlocal opt_plus], result: [[7]])
+ def foo(foo, bar)
+ [1].map do |x|
+ [1].map do |y|
+ foo + bar
+ end
+ end
+ end
+
+ foo(5, 2)
+ RUBY
+ end
+
+ def test_setlocal_with_level
+ assert_no_exits(<<~RUBY)
+ def sum(arr)
+ sum = 0
+ arr.each do |x|
+ sum += x
+ end
+ sum
+ end
+
+ sum([1,2,3])
+ RUBY
+ end
+
+ def test_string_then_nil
+ assert_compiles(<<~RUBY, insns: %i[opt_nil_p], result: true)
+ def foo(val)
+ val.nil?
+ end
+
+ foo("foo")
+ foo(nil)
+ RUBY
+ end
+
+ def test_nil_then_string
+ assert_compiles(<<~RUBY, insns: %i[opt_nil_p], result: false)
+ def foo(val)
+ val.nil?
+ end
+
+ foo(nil)
+ foo("foo")
+ RUBY
+ end
+
+ def test_string_concat_utf8
+ assert_compiles(<<~RUBY, frozen_string_literal: true, result: true)
+ def str_cat_utf8
+ s = String.new
+ 10.times { s << "✅" }
+ s
+ end
+
+ str_cat_utf8 == "✅" * 10
+ RUBY
+ end
+
+ def test_string_concat_ascii
+ # Constant-get for classes (e.g. String, Encoding) can cause a side-exit in getinlinecache. For now, ignore exits.
+ assert_compiles(<<~RUBY, exits: :any)
+ str_arg = "b".encode(Encoding::ASCII)
+ def str_cat_ascii(arg)
+ s = String.new(encoding: Encoding::ASCII)
+ 10.times { s << arg }
+ s
+ end
+
+ str_cat_ascii(str_arg) == str_arg * 10
+ RUBY
+ end
+
+ def test_opt_length_in_method
+ assert_compiles(<<~RUBY, insns: %i[opt_length], result: 5)
+ def foo(str)
+ str.length
+ end
+
+ foo("hello, ")
+ foo("world")
+ RUBY
+ end
+
+ def test_opt_regexpmatch2
+ assert_compiles(<<~RUBY, insns: %i[opt_regexpmatch2], result: 0)
+ def foo(str)
+ str =~ /foo/
+ end
+
+ foo("foobar")
+ RUBY
+ end
+
+ def test_expandarray
+ assert_compiles(<<~'RUBY', insns: %i[expandarray], result: [1, 2])
+ a, b = [1, 2]
+ RUBY
+ end
+
+ def test_expandarray_nil
+ assert_compiles(<<~'RUBY', insns: %i[expandarray], result: [nil, nil])
+ a, b = nil
+ [a, b]
+ RUBY
+ end
+
+ def test_getspecial_backref
+ assert_compiles("'foo' =~ /(o)./; $&", insns: %i[getspecial], result: "oo")
+ assert_compiles("'foo' =~ /(o)./; $`", insns: %i[getspecial], result: "f")
+ assert_compiles("'foo' =~ /(o)./; $'", insns: %i[getspecial], result: "")
+ assert_compiles("'foo' =~ /(o)./; $+", insns: %i[getspecial], result: "o")
+ assert_compiles("'foo' =~ /(o)./; $1", insns: %i[getspecial], result: "o")
+ assert_compiles("'foo' =~ /(o)./; $2", insns: %i[getspecial], result: nil)
+ end
+
+ def test_compile_getconstant
+ assert_compiles(<<~RUBY, insns: %i[getconstant], result: [], call_threshold: 1)
+ def get_argv(klass)
+ klass::ARGV
+ end
+
+ get_argv(Object)
+ RUBY
+ end
+
+ def test_compile_getconstant_with_sp_offset
+ assert_compiles(<<~RUBY, insns: %i[getconstant], result: 2, call_threshold: 1)
+ class Foo
+ Bar = 1
+ end
+
+ 2.times do
+ s = Foo # this opt_getconstant_path needs warmup, so 2.times is needed
+ Class.new(Foo).const_set(:Bar, s::Bar)
+ end
+ RUBY
+ end
+
+ def test_compile_opt_getconstant_path
+ assert_compiles(<<~RUBY, insns: %i[opt_getconstant_path], result: 123, call_threshold: 2)
+ def get_foo
+ FOO
+ end
+
+ FOO = 123
+
+ get_foo # warm inline cache
+ get_foo
+ RUBY
+ end
+
+ def test_opt_getconstant_path_slowpath
+ assert_compiles(<<~RUBY, exits: { opt_getconstant_path: 1 }, result: [42, 42, 1, 1], call_threshold: 2)
+ class A
+ FOO = 42
+ class << self
+ def foo
+ _foo = nil
+ FOO
+ end
+ end
+ end
+
+ result = []
+
+ result << A.foo
+ result << A.foo
+
+ class << A
+ FOO = 1
+ end
+
+ result << A.foo
+ result << A.foo
+
+ result
+ RUBY
+ end
+
+ def test_opt_getconstant_path_general
+ assert_compiles(<<~RUBY, result: [1, 1])
+ module Base
+ Const = 1
+ end
+
+ class Sub
+ def const
+ _const = nil # make a non-entry block for opt_getconstant_path
+ Const
+ end
+
+ def self.const_missing(n)
+ Base.const_get(n)
+ end
+ end
+
+
+ sub = Sub.new
+ result = []
+ result << sub.const # generate the general case
+ result << sub.const # const_missing does not invalidate the block
+ result
+ RUBY
+ end
+
+ def test_string_interpolation
+ assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "foobar", call_threshold: 2)
+ def make_str(foo, bar)
+ "#{foo}#{bar}"
+ end
+
+ make_str("foo", "bar")
+ make_str("foo", "bar")
+ RUBY
+ end
+
+ def test_string_interpolation_cast
+ assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "123")
+ def make_str(foo, bar)
+ "#{foo}#{bar}"
+ end
+
+ make_str(1, 23)
+ RUBY
+ end
+
+ def test_checkkeyword
+ assert_compiles(<<~'RUBY', insns: %i[checkkeyword], result: [2, 5])
+ def foo(foo: 1+1)
+ foo
+ end
+
+ [foo, foo(foo: 5)]
+ RUBY
+ end
+
+ def test_struct_aref
+ assert_compiles(<<~RUBY)
+ def foo(obj)
+ obj.foo
+ obj.bar
+ end
+
+ Foo = Struct.new(:foo, :bar)
+ foo(Foo.new(123))
+ foo(Foo.new(123))
+ RUBY
+ end
+
+ def test_struct_aset
+ assert_compiles(<<~RUBY)
+ def foo(obj)
+ obj.foo = 123
+ obj.bar = 123
+ end
+
+ Foo = Struct.new(:foo, :bar)
+ foo(Foo.new(123))
+ foo(Foo.new(123))
+ 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
+ 2.times do
+ blk
+ end
+ end
+
+ foo {}
+ foo {}
+ RUBY
+ end
+
+ def test_getblockparamproxy
+ assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: {})
+ def foo &blk
+ p blk.call
+ p blk.call
+ end
+
+ foo { 1 }
+ foo { 2 }
+ RUBY
+ end
+
+ def test_ifunc_getblockparamproxy
+ assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: {})
+ class Foo
+ include Enumerable
+
+ def each(&block)
+ block.call 1
+ block.call 2
+ block.call 3
+ end
+ end
+
+ foo = Foo.new
+ foo.map { _1 * 2 }
+ foo.map { _1 * 2 }
+ RUBY
+ end
+
+ def test_send_blockarg
+ assert_compiles(<<~'RUBY', insns: [:getblockparamproxy, :send], exits: {})
+ def bar
+ end
+
+ def foo &blk
+ bar(&blk)
+ bar(&blk)
+ end
+
+ foo
+ foo
+
+ foo { }
+ foo { }
+ RUBY
+ end
+
+ def test_send_splat
+ assert_compiles(<<~'RUBY', result: "3#1,2,3/P", exits: {})
+ def internal_method(*args)
+ "#{args.size}##{args.join(",")}"
+ end
+
+ def jit_method
+ send(:internal_method, *[1, 2, 3]) + "/P"
+ end
+
+ jit_method
+ RUBY
+ end
+
+ def test_send_multiarg
+ assert_compiles(<<~'RUBY', result: "3#1,2,3/Q")
+ def internal_method(*args)
+ "#{args.size}##{args.join(",")}"
+ end
+
+ def jit_method
+ send(:internal_method, 1, 2, 3) + "/Q"
+ end
+
+ jit_method
+ RUBY
+ end
+
+ def test_send_kwargs
+ # For now, this side-exits when calls include keyword args
+ assert_compiles(<<~'RUBY', result: "2#a:1,b:2/A")
+ def internal_method(**kw)
+ "#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}"
+ end
+
+ def jit_method
+ send(:internal_method, a: 1, b: 2) + "/A"
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_kwargs_in_receiver_only
+ assert_compiles(<<~'RUBY', result: "0/RK", exits: {})
+ def internal_method(**kw)
+ "#{kw.size}"
+ end
+
+ def jit_method
+ send(:internal_method) + "/RK"
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_with_underscores
+ assert_compiles(<<~'RUBY', result: "0/RK", exits: {})
+ def internal_method(**kw)
+ "#{kw.size}"
+ end
+
+ def jit_method
+ __send__(:internal_method) + "/RK"
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_kwargs_splat
+ # For now, this side-exits when calling with a splat
+ assert_compiles(<<~'RUBY', result: "2#a:1,b:2/B")
+ def internal_method(**kw)
+ "#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}"
+ end
+
+ def jit_method
+ send(:internal_method, **{ a: 1, b: 2 }) + "/B"
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_block
+ # Setlocal_wc_0 sometimes side-exits on write barrier
+ assert_compiles(<<~'RUBY', result: "b:n/b:y/b:y/b:n")
+ def internal_method(&b)
+ "b:#{block_given? ? "y" : "n"}"
+ end
+
+ def jit_method
+ b7 = proc { 7 }
+ [
+ send(:internal_method),
+ send(:internal_method, &b7),
+ send(:internal_method) { 7 },
+ send(:internal_method, &nil),
+ ].join("/")
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_block_calling
+ assert_compiles(<<~'RUBY', result: "1a2", exits: {})
+ def internal_method
+ out = yield
+ "1" + out + "2"
+ end
+
+ def jit_method
+ __send__(:internal_method) { "a" }
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_block_only_receiver
+ assert_compiles(<<~'RUBY', result: "b:n", exits: {})
+ def internal_method(&b)
+ "b:#{block_given? ? "y" : "n"}"
+ end
+
+ def jit_method
+ send(:internal_method)
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_block_only_sender
+ assert_compiles(<<~'RUBY', result: "Y/Y/Y/Y", exits: {})
+ def internal_method
+ "Y"
+ end
+
+ def jit_method
+ b7 = proc { 7 }
+ [
+ send(:internal_method),
+ send(:internal_method, &b7),
+ send(:internal_method) { 7 },
+ send(:internal_method, &nil),
+ ].join("/")
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_multisend
+ assert_compiles(<<~'RUBY', result: "77")
+ def internal_method
+ "7"
+ end
+
+ def jit_method
+ send(:send, :internal_method) + send(:send, :send, :internal_method)
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_getivar_opt_plus
+ assert_no_exits(<<~RUBY)
+ class TheClass
+ def initialize
+ @levar = 1
+ end
+
+ def get_sum
+ sum = 0
+ # The type of levar is unknown,
+ # but this still should not exit
+ sum += @levar
+ sum
+ end
+ end
+
+ obj = TheClass.new
+ obj.get_sum
+ RUBY
+ end
+
+ def test_super_iseq
+ assert_compiles(<<~'RUBY', insns: %i[invokesuper opt_plus opt_mult], result: 15)
+ class A
+ def foo
+ 1 + 2
+ end
+ end
+
+ class B < A
+ def foo
+ super * 5
+ end
+ end
+
+ B.new.foo
+ RUBY
+ end
+
+ def test_super_with_alias
+ assert_compiles(<<~'RUBY', insns: %i[invokesuper opt_plus opt_mult], result: 15)
+ class A
+ def foo = 1 + 2
+ end
+
+ module M
+ def foo = super() * 5
+ alias bar foo
+
+ def foo = :bad
+ end
+
+ A.prepend M
+
+ A.new.bar
+ RUBY
+ end
+
+ def test_super_cfunc
+ assert_compiles(<<~'RUBY', insns: %i[invokesuper], result: "Hello")
+ class Gnirts < String
+ def initialize
+ super(-"olleH")
+ end
+
+ def to_s
+ super().reverse
+ end
+ end
+
+ Gnirts.new.to_s
+ RUBY
+ end
+
+ # Tests calling a variadic cfunc with many args
+ def test_build_large_struct
+ assert_compiles(<<~RUBY, insns: %i[opt_send_without_block], call_threshold: 2)
+ ::Foo = Struct.new(:a, :b, :c, :d, :e, :f, :g, :h)
+
+ def build_foo
+ ::Foo.new(:a, :b, :c, :d, :e, :f, :g, :h)
+ end
+
+ build_foo
+ build_foo
+ RUBY
+ end
+
+ def test_fib_recursion
+ assert_compiles(<<~'RUBY', insns: %i[opt_le opt_minus opt_plus opt_send_without_block], result: 34)
+ def fib(n)
+ return n if n <= 1
+ fib(n-1) + fib(n-2)
+ end
+
+ fib(9)
+ RUBY
+ end
+
+ def test_optarg_and_kwarg
+ assert_no_exits(<<~'RUBY')
+ def opt_and_kwarg(a, b=nil, c: nil)
+ end
+
+ 2.times do
+ opt_and_kwarg(1, 2, c: 3)
+ end
+ RUBY
+ end
+
+ def test_cfunc_kwarg
+ assert_no_exits('{}.store(:value, foo: 123)')
+ assert_no_exits('{}.store(:value, foo: 123, bar: 456, baz: 789)')
+ assert_no_exits('{}.merge(foo: 123)')
+ assert_no_exits('{}.merge(foo: 123, bar: 456, baz: 789)')
+ end
+
+ # regression test simplified from URI::Generic#hostname=
+ def test_ctx_different_mappings
+ assert_compiles(<<~'RUBY', frozen_string_literal: true)
+ def foo(v)
+ !(v&.start_with?('[')) && v&.index(':')
+ end
+
+ foo(nil)
+ foo("example.com")
+ RUBY
+ end
+
+ def test_no_excessive_opt_getinlinecache_invalidation
+ assert_compiles(<<~'RUBY', exits: :any, result: :ok)
+ objects = [Object.new, Object.new]
+
+ objects.each do |o|
+ class << o
+ def foo
+ Object
+ end
+ end
+ end
+
+ 9000.times {
+ objects[0].foo
+ objects[1].foo
+ }
+
+ stats = RubyVM::YJIT.runtime_stats
+ return :ok unless stats[:all_stats]
+ return :ok if stats[:invalidation_count] < 10
+
+ :fail
+ RUBY
+ end
+
+ def test_int_equal
+ assert_compiles(<<~'RUBY', exits: :any, result: [true, false, true, false, true, false, true, false])
+ def eq(a, b)
+ a == b
+ end
+
+ def eqq(a, b)
+ a === b
+ end
+
+ big1 = 2 ** 65
+ big2 = big1 + 1
+ [eq(1, 1), eq(1, 2), eq(big1, big1), eq(big1, big2), eqq(1, 1), eqq(1, 2), eqq(big1, big1), eqq(big1, big2)]
+ RUBY
+ end
+
+ def test_opt_case_dispatch
+ assert_compiles(<<~'RUBY', exits: :any, result: [:"1", "2", 3])
+ def case_dispatch(val)
+ case val
+ when 1
+ :"#{val}"
+ when 2
+ "#{val}"
+ else
+ val
+ end
+ end
+
+ [case_dispatch(1), case_dispatch(2), case_dispatch(3)]
+ RUBY
+ end
+
+ def test_code_gc
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok)
+ return :not_paged unless add_pages(100) # prepare freeable pages
+ RubyVM::YJIT.code_gc # first code GC
+ return :not_compiled1 unless compiles { nil } # should be JITable again
+
+ RubyVM::YJIT.code_gc # second code GC
+ return :not_compiled2 unless compiles { nil } # should be JITable again
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count != 2
+
+ :ok
+ RUBY
+ end
+
+ def test_on_stack_code_gc_call
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok)
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while true
+ Fiber.yield(nil.to_i)
+ end
+ }
+
+ return :not_paged1 unless add_pages(400) # go to a page without initial ocb code
+ return :broken_resume1 if fiber.resume != 0 # JIT the fiber
+ RubyVM::YJIT.code_gc # first code GC, which should not free the fiber page
+ return :broken_resume2 if fiber.resume != 0 # The code should be still callable
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count != 1
+
+ :ok
+ RUBY
+ end
+
+ def test_on_stack_code_gc_twice
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok)
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while Fiber.yield(nil.to_i); end
+ }
+
+ return :not_paged1 unless add_pages(400) # go to a page without initial ocb code
+ return :broken_resume1 if fiber.resume(true) != 0 # JIT the fiber
+ RubyVM::YJIT.code_gc # first code GC, which should not free the fiber page
+
+ return :not_paged2 unless add_pages(300) # add some stuff to be freed
+ # Not calling fiber.resume here to test the case that the YJIT payload loses some
+ # information at the previous code GC. The payload should still be there, and
+ # thus we could know the fiber ISEQ is still on stack on this second code GC.
+ RubyVM::YJIT.code_gc # second code GC, which should still not free the fiber page
+
+ return :not_paged3 unless add_pages(200) # attempt to overwrite the fiber page (it shouldn't)
+ return :broken_resume2 if fiber.resume(true) != 0 # The fiber code should be still fine
+
+ return :broken_resume3 if fiber.resume(false) != nil # terminate the fiber
+ RubyVM::YJIT.code_gc # third code GC, freeing a page that used to be on stack
+
+ return :not_paged4 unless add_pages(100) # check everything still works
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count != 3
+
+ :ok
+ RUBY
+ end
+
+ def test_disable_code_gc_with_many_iseqs
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok, mem_size: 1, code_gc: false)
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while true
+ Fiber.yield(nil.to_i)
+ end
+ }
+
+ return :not_paged1 unless add_pages(250) # use some pages
+ return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well
+
+ add_pages(2000) # use a whole lot of pages to run out of 1MiB
+ return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count != 0
+
+ :ok
+ RUBY
+ end
+
+ def test_code_gc_with_many_iseqs
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok, mem_size: 1, code_gc: true)
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while true
+ Fiber.yield(nil.to_i)
+ end
+ }
+
+ return :not_paged1 unless add_pages(250) # use some pages
+ return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well
+
+ add_pages(2000) # use a whole lot of pages to run out of 1MiB
+ return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count == 0
+
+ :ok
+ RUBY
+ end
+
+ def test_code_gc_with_auto_compact
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ assert_compiles((code_gc_helpers + <<~'RUBY'), exits: :any, result: :ok, mem_size: 1, code_gc: true)
+ # Test ISEQ moves in the middle of code GC
+ GC.auto_compact = true
+
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while true
+ Fiber.yield(nil.to_i)
+ end
+ }
+
+ return :not_paged1 unless add_pages(250) # use some pages
+ return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well
+
+ add_pages(2000) # use a whole lot of pages to run out of 1MiB
+ return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count == 0
+
+ :ok
+ RUBY
+ end
+
+ def test_code_gc_partial_last_page
+ # call_threshold: 2 to avoid JIT-ing code_gc itself. If code_gc were JITed right before
+ # code_gc is called, the last page would be on stack.
+ assert_compiles(<<~'RUBY', exits: :any, result: :ok, call_threshold: 2)
+ # Leave a bunch of off-stack pages
+ i = 0
+ while i < 1000
+ eval("x = proc { 1.to_s }; x.call; x.call")
+ i += 1
+ end
+
+ # On Linux, memory page size != code page size. So the last code page could be partially
+ # mapped. This call tests that assertions and other things work fine under the situation.
+ RubyVM::YJIT.code_gc
+
+ :ok
+ RUBY
+ end
+
+ def test_trace_script_compiled # not ISEQ_TRACE_EVENTS
+ assert_compiles(<<~'RUBY', exits: :any, result: :ok)
+ @eval_counter = 0
+ def eval_script
+ eval('@eval_counter += 1')
+ end
+
+ @trace_counter = 0
+ trace = TracePoint.new(:script_compiled) do |t|
+ @trace_counter += 1
+ end
+
+ eval_script # JIT without TracePoint
+ trace.enable
+ eval_script # call with TracePoint
+ trace.disable
+
+ return :"eval_#{@eval_counter}" if @eval_counter != 2
+ return :"trace_#{@trace_counter}" if @trace_counter != 1
+
+ :ok
+ RUBY
+ end
+
+ def test_trace_b_call # ISEQ_TRACE_EVENTS
+ assert_compiles(<<~'RUBY', exits: :any, result: :ok)
+ @call_counter = 0
+ def block_call
+ 1.times { @call_counter += 1 }
+ end
+
+ @trace_counter = 0
+ trace = TracePoint.new(:b_call) do |t|
+ @trace_counter += 1
+ end
+
+ block_call # JIT without TracePoint
+ trace.enable
+ block_call # call with TracePoint
+ trace.disable
+
+ return :"call_#{@call_counter}" if @call_counter != 2
+ return :"trace_#{@trace_counter}" if @trace_counter != 1
+
+ :ok
+ RUBY
+ end
+
+ def test_send_to_call
+ assert_compiles(<<~'RUBY', result: :ok)
+ ->{ :ok }.send(:call)
+ RUBY
+ end
+
+ def test_invokeblock_many_locals
+ # [Bug #19299]
+ assert_compiles(<<~'RUBY', result: :ok)
+ def foo
+ yield
+ end
+
+ foo do
+ a1=a2=a3=a4=a5=a6=a7=a8=a9=a10=a11=a12=a13=a14=a15=a16=a17=a18=a19=a20=a21=a22=a23=a24=a25=a26=a27=a28=a29=a30 = :ok
+ a30
+ end
+ RUBY
+ end
+
+ def test_bug_19316
+ n = 2 ** 64
+ # foo's extra param and the splats are relevant
+ assert_compiles(<<~'RUBY', result: [[n, -n], [n, -n]], exits: :any)
+ def foo(_, a, b, c)
+ [a & b, ~c]
+ end
+
+ n = 2 ** 64
+ args = [0, -n, n, n-1]
+
+ GC.stress = true
+ [foo(*args), foo(*args)]
+ RUBY
+ end
+
+ def test_gc_compact_cyclic_branch
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ assert_compiles(<<~'RUBY', result: 2)
+ def foo
+ i = 0
+ while i < 2
+ i += 1
+ end
+ i
+ end
+
+ foo
+ GC.compact
+ foo
+ RUBY
+ end
+
+ def test_invalidate_cyclic_branch
+ assert_compiles(<<~'RUBY', result: 2, exits: { opt_plus: 1 })
+ def foo
+ i = 0
+ while i < 2
+ i += 1
+ end
+ i
+ end
+
+ foo
+ class Integer
+ def +(x) = self - -x
+ end
+ foo
+ RUBY
+ end
+
+ def test_tracing_str_uplus
+ assert_compiles(<<~RUBY, frozen_string_literal: true, result: :ok, exits: { putspecialobject: 1 })
+ def str_uplus
+ _ = 1
+ _ = 2
+ ret = [+"frfr", __LINE__]
+ _ = 3
+ _ = 4
+
+ ret
+ end
+
+ str_uplus
+ require 'objspace'
+ ObjectSpace.trace_object_allocations_start
+
+ str, expected_line = str_uplus
+ alloc_line = ObjectSpace.allocation_sourceline(str)
+
+ if expected_line == alloc_line
+ :ok
+ else
+ [expected_line, alloc_line]
+ end
+ RUBY
+ end
+
+ def test_str_uplus_subclass
+ assert_compiles(<<~RUBY, frozen_string_literal: true, result: :subclass)
+ class S < String
+ def encoding
+ :subclass
+ end
+ end
+
+ def test(str)
+ (+str).encoding
+ end
+
+ test ""
+ test S.new
+ RUBY
+ end
+
+ def test_return_to_invalidated_block
+ # [Bug #19463]
+ assert_compiles(<<~RUBY, result: [1, 1, :ugokanai], exits: { definesmethod: 1, getlocal_WC_0: 1 })
+ klass = Class.new do
+ def self.lookup(hash, key) = hash[key]
+
+ def self.foo(a, b) = []
+
+ def self.test(hash, key)
+ [lookup(hash, key), key, "".freeze]
+ # 05 opt_send_without_block :lookup
+ # 07 getlocal_WC_0 :hash
+ # 09 opt_str_freeze ""
+ # 12 newarray 3
+ # 14 leave
+ #
+ # YJIT will put instructions (07..14) into a block.
+ # When String#freeze is redefined from within lookup(),
+ # the return address to the block is still on-stack. We rely
+ # on invalidation patching the code at the return address
+ # to service this situation correctly.
+ end
+ end
+
+ # get YJIT to compile test()
+ hash = { 1 => [] }
+ 31.times { klass.test(hash, 1) }
+
+ # inject invalidation into lookup()
+ evil_hash = Hash.new do |_, key|
+ class String
+ undef :freeze
+ def freeze = :ugokanai
+ end
+
+ key
+ end
+ klass.test(evil_hash, 1)
+ RUBY
+ end
+
+ def test_return_to_invalidated_frame
+ assert_compiles(code_gc_helpers + <<~RUBY, exits: :any, result: :ok)
+ def jump
+ [] # something not inlined
+ end
+
+ def entry(code_gc)
+ jit_exception(code_gc)
+ jump # faulty jump after code GC. #jit_exception should not come back.
+ end
+
+ def jit_exception(code_gc)
+ if code_gc
+ tap do
+ RubyVM::YJIT.code_gc
+ break # jit_exec_exception catches TAG_BREAK and re-enters JIT code
+ end
+ end
+ end
+
+ add_pages(100)
+ jump # Compile #jump in a non-first page
+ add_pages(100)
+ entry(false) # Compile #entry and its call to #jump in another page
+ entry(true) # Free #jump but not #entry
+
+ :ok
+ RUBY
+ end
+
+ def test_setivar_on_class
+ # Bug in https://github.com/ruby/ruby/pull/8152
+ assert_compiles(<<~RUBY, result: :ok)
+ class Base
+ def self.or_equal
+ @or_equal ||= Object.new
+ end
+ end
+
+ Base.or_equal # ensure compiled
+
+ class Child < Base
+ end
+
+ 200.times do |iv| # Need to be more than MAX_IVAR
+ Child.instance_variable_set("@_iv_\#{iv}", Object.new)
+ end
+
+ Child.or_equal
+ :ok
+ RUBY
+ end
+
+ def test_nested_send
+ #[Bug #19464]
+ assert_compiles(<<~RUBY, result: [:ok, :ok], exits: { defineclass: 1 })
+ klass = Class.new do
+ class << self
+ alias_method :my_send, :send
+
+ def bar = :ok
+
+ def foo = bar
+ end
+ end
+
+ with_break = -> { break klass.send(:my_send, :foo) }
+ wo_break = -> { klass.send(:my_send, :foo) }
+
+ [with_break[], wo_break[]]
+ RUBY
+ end
+
+ def test_str_concat_encoding_mismatch
+ assert_compiles(<<~'RUBY', result: "incompatible character encodings: BINARY (ASCII-8BIT) and EUC-JP")
+ def bar(a, b)
+ a << b
+ rescue => e
+ e.message
+ end
+
+ def foo(a, b, h)
+ h[nil]
+ bar(a, b) # Ruby call, not set cfp->pc
+ end
+
+ h = Hash.new { nil }
+ foo("\x80".b, "\xA1A1".dup.force_encoding("EUC-JP"), h)
+ foo("\x80".b, "\xA1A1".dup.force_encoding("EUC-JP"), h)
+ RUBY
+ end
+
+ def test_io_reopen_clobbering_singleton_class
+ assert_compiles(<<~RUBY, result: [:ok, :ok], exits: { definesmethod: 1, opt_eq: 2 })
+ def $stderr.to_i = :i
+
+ def test = $stderr.to_i
+
+ [test, test]
+ $stderr.reopen($stderr.dup)
+ [test, test].map { :ok unless _1 == :i }
+ RUBY
+ end
+
+ def test_proc_block_arg
+ assert_compiles(<<~RUBY, result: [:proc, :no_block])
+ def yield_if_given = block_given? ? yield : :no_block
+
+ def call(block_arg = nil) = yield_if_given(&block_arg)
+
+ [call(-> { :proc }), call]
+ RUBY
+ end
+
+ def test_opt_mult_overflow
+ assert_no_exits('0xfff_ffff_ffff_ffff * 0x10')
+ end
+
+ def test_disable_stats
+ assert_in_out_err(%w[--yjit-stats --yjit-disable])
+ end
+
+ def test_odd_calls_to_attr_reader
+ # Use of delegate from ActiveSupport use these kind of calls to getter methods.
+ assert_compiles(<<~RUBY, result: [1, 1, 1], no_send_fallbacks: true)
+ class One
+ attr_reader :one
+ def initialize
+ @one = 1
+ end
+ end
+
+ def calls(obj, empty, &)
+ [obj.one(*empty), obj.one(&), obj.one(*empty, &)]
+ end
+
+ calls(One.new, [])
+ RUBY
+ end
+
+ def test_kwrest
+ assert_compiles(<<~RUBY, result: true, no_send_fallbacks: true)
+ def req_rest(r1:, **kwrest) = [r1, kwrest]
+ def opt_rest(r1: 1.succ, **kwrest) = [r1, kwrest]
+ def kwrest(**kwrest) = kwrest
+
+ def calls
+ [
+ [1, {}] == req_rest(r1: 1),
+ [1, {:r2=>2, :r3=>3}] == req_rest(r1: 1, r2: 2, r3: 3),
+ [1, {:r2=>2, :r3=>3}] == req_rest(r2: 2, r1:1, r3: 3),
+ [1, {:r2=>2, :r3=>3}] == req_rest(r2: 2, r3: 3, r1: 1),
+
+ [2, {}] == opt_rest,
+ [2, { r2: 2, r3: 3 }] == opt_rest(r2: 2, r3: 3),
+ [0, { r2: 2, r3: 3 }] == opt_rest(r1: 0, r3: 3, r2: 2),
+ [0, { r2: 2, r3: 3 }] == opt_rest(r2: 2, r1: 0, r3: 3),
+ [1, { r2: 2, r3: 3 }] == opt_rest(r2: 2, r3: 3, r1: 1),
+
+ {} == kwrest,
+ { r0: 88, r1: 99 } == kwrest(r0: 88, r1: 99),
+ ]
+ end
+
+ calls.all?
+ RUBY
+ end
+
+ def test_send_polymorphic_method_name
+ assert_compiles(<<~'RUBY', result: %i[ok ok], no_send_fallbacks: true)
+ mid = "dynamic_mid_#{rand(100..200)}"
+ mid_dsym = mid.to_sym
+
+ define_method(mid) { :ok }
+
+ define_method(:send_site) { send(_1) }
+
+ [send_site(mid), send_site(mid_dsym)]
+ RUBY
+ end
+
+ def test_kw_splat_nil
+ assert_compiles(<<~'RUBY', result: %i[ok ok], no_send_fallbacks: true)
+ def id(x) = x
+ def kw_fw(arg, **) = id(arg, **)
+ def use = [kw_fw(:ok), :ok.itself(**nil)]
+
+ use
+ RUBY
+ end
+
+ def test_empty_splat
+ assert_compiles(<<~'RUBY', result: :ok, no_send_fallbacks: true)
+ def foo = :ok
+ def use(empty) = foo(*empty)
+
+ use([])
+ RUBY
+ end
+
+ def test_byteslice_sp_invalidation
+ assert_compiles(<<~'RUBY', result: 'ok', no_send_fallbacks: true)
+ "okng".itself.byteslice(0, 2)
+ RUBY
+ end
+
+ def test_leaf_builtin
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: 1)
+ before = RubyVM::YJIT.runtime_stats[:num_send_iseq_leaf]
+ return 1 if before.nil?
+
+ def entry = self.class
+ entry
+
+ after = RubyVM::YJIT.runtime_stats[:num_send_iseq_leaf]
+ after - before
+ RUBY
+ end
+
+ def test_runtime_stats_types
+ assert_compiles(<<~'RUBY', exits: :any, result: true)
+ def test = :ok
+ 3.times { test }
+
+ stats = RubyVM::YJIT.runtime_stats
+ return true unless stats[:all_stats]
+
+ [
+ stats[:object_shape_count].is_a?(Integer),
+ stats[:ratio_in_yjit].nil? || stats[:ratio_in_yjit].is_a?(Float),
+ ].all?
+ RUBY
+ end
+
+ def test_runtime_stats_key_arg
+ assert_compiles(<<~'RUBY', exits: :any, result: true)
+ def test = :ok
+ 3.times { test }
+
+ # Collect single stat.
+ stat = RubyVM::YJIT.runtime_stats(:yjit_alloc_size)
+
+ # Ensure this invocation had stats.
+ return true unless RubyVM::YJIT.runtime_stats[:all_stats]
+
+ stat > 0.0
+ RUBY
+ end
+
+ def test_runtime_stats_arg_error
+ assert_compiles(<<~'RUBY', exits: :any, result: true)
+ begin
+ RubyVM::YJIT.runtime_stats(Object.new)
+ :no_error
+ rescue TypeError => e
+ e.message == "non-symbol given"
+ end
+ RUBY
+ end
+
+ def test_runtime_stats_unknown_key
+ assert_compiles(<<~'RUBY', exits: :any, result: true)
+ def test = :ok
+ 3.times { test }
+
+ RubyVM::YJIT.runtime_stats(:some_key_unlikely_to_exist).nil?
+ RUBY
+ end
+
+ def test_yjit_option_uses_array_each_in_ruby
+ assert_separately(["--yjit"], <<~'RUBY')
+ # Array#each should be implemented in Ruby for YJIT
+ assert_equal "<internal:array>", Array.instance_method(:each).source_location.first
+
+ # The backtrace, however, should not be `from <internal:array>:XX:in 'Array#each'`
+ begin
+ [nil].each { raise }
+ rescue => e
+ assert_equal "-:11:in 'Array#each'", e.backtrace[1]
+ end
+ RUBY
+ end
+
+ def test_yjit_enable_replaces_array_each
+ assert_separately([*("--disable=yjit" if RubyVM::YJIT.enabled?)], <<~'RUBY')
+ # Array#each should be implemented in C for the interpreter
+ assert_nil Array.instance_method(:each).source_location
+
+ # The backtrace should not be `from <internal:array>:XX:in 'Array#each'`
+ begin
+ [nil].each { raise }
+ rescue => e
+ assert_equal "-:11:in 'Array#each'", e.backtrace[1]
+ end
+
+ RubyVM::YJIT.enable
+
+ # Array#each should be implemented in Ruby for YJIT
+ assert_equal "<internal:array>", Array.instance_method(:each).source_location.first
+
+ # However, the backtrace should still not be `from <internal:array>:XX:in 'Array#each'`
+ begin
+ [nil].each { raise }
+ rescue => e
+ assert_equal "-:23:in 'Array#each'", e.backtrace[1]
+ end
+ RUBY
+ end
+
+ def test_yjit_enable_preserves_array_each_monkey_patch
+ assert_separately([*("--disable=yjit" if RubyVM::YJIT.enabled?)], <<~'RUBY')
+ # Array#each should be implemented in C initially
+ assert_nil Array.instance_method(:each).source_location
+
+ # Override Array#each
+ $called = false
+ Array.prepend(Module.new {
+ def each
+ $called = true
+ super
+ end
+ })
+
+ RubyVM::YJIT.enable
+
+ # The monkey-patch should still be alive
+ [].each {}
+ assert_true $called
+
+ # YJIT should not replace Array#each with the "<internal:array>" one
+ assert_equal "-", Array.instance_method(:each).source_location.first
+ RUBY
+ end
+
+ def test_yield_kwargs
+ assert_compiles(<<~RUBY, result: 3, no_send_fallbacks: true)
+ def req2kws = yield a: 1, b: 2
+
+ req2kws { |a:, b:| a + b }
+ 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
+ <<~'RUBY'
+ def compiles(&block)
+ failures = RubyVM::YJIT.runtime_stats[:compilation_failure]
+ block.call
+ failures == RubyVM::YJIT.runtime_stats[:compilation_failure]
+ end
+
+ def add_pages(num_jits)
+ pages = RubyVM::YJIT.runtime_stats[:live_page_count]
+ num_jits.times { return false unless eval('compiles { nil.to_i }') }
+ pages.nil? || pages < RubyVM::YJIT.runtime_stats[:live_page_count]
+ end
+ RUBY
+ end
+
+ def assert_no_exits(script)
+ assert_compiles(script)
+ end
+
+ ANY = Object.new
+ def assert_compiles(
+ test_script, insns: [],
+ call_threshold: 1,
+ stdout: nil,
+ exits: {},
+ result: ANY,
+ frozen_string_literal: nil,
+ mem_size: nil,
+ code_gc: false,
+ no_send_fallbacks: false
+ )
+ reset_stats = <<~RUBY
+ RubyVM::YJIT.runtime_stats
+ RubyVM::YJIT.reset_stats!
+ RUBY
+
+ write_results = <<~RUBY
+ stats = RubyVM::YJIT.runtime_stats
+
+ def collect_insns(iseq)
+ insns = RubyVM::YJIT.insns_compiled(iseq)
+ iseq.each_child { |c| insns.concat collect_insns(c) }
+ insns
+ end
+
+ iseq = RubyVM::InstructionSequence.of(_test_proc)
+ IO.open(3).write Marshal.dump({
+ result: #{result == ANY ? "nil" : "result"},
+ stats: stats,
+ insns: collect_insns(iseq),
+ disasm: iseq.disasm
+ })
+ RUBY
+
+ script = <<~RUBY
+ #{"# frozen_string_literal: " + frozen_string_literal.to_s unless frozen_string_literal.nil?}
+ _test_proc = -> {
+ #{test_script}
+ }
+ #{reset_stats}
+ result = _test_proc.call
+ #{write_results}
+ RUBY
+
+ status, out, err, stats = eval_with_jit(script, call_threshold:, mem_size:, code_gc:)
+
+ assert status.success?, "exited with status #{status.to_i}, stderr:\n#{err}"
+
+ assert_equal stdout.chomp, out.chomp if stdout
+
+ unless ANY.equal?(result)
+ assert_equal result, stats[:result]
+ end
+
+ runtime_stats = stats[:stats]
+ insns_compiled = stats[:insns]
+ disasm = stats[:disasm]
+
+ # Check that exit counts are as expected
+ # Full stats are only available when --enable-yjit=dev
+ if runtime_stats[:all_stats]
+ recorded_exits = runtime_stats.select { |k, v| k.to_s.start_with?("exit_") }
+ recorded_exits = recorded_exits.reject { |k, v| v == 0 }
+
+ recorded_exits.transform_keys! { |k| k.to_s.gsub("exit_", "").to_sym }
+ # Exits can be specified as a hash of stat-name symbol to integer for exact exits.
+ # or stat-name symbol to range if the number of side exits might vary (e.g. write
+ # barriers, cache misses.)
+ if exits != :any &&
+ exits != recorded_exits &&
+ (exits.keys != recorded_exits.keys || !exits.all? { |k, v| v === recorded_exits[k] }) # triple-equal checks range membership or integer equality
+ stats_reasons = StringIO.new
+ ::RubyVM::YJIT.send(:_print_stats_reasons, runtime_stats, stats_reasons)
+ stats_reasons = stats_reasons.string
+ flunk <<~EOM
+ Expected #{exits.empty? ? "no" : exits.inspect} exits, but got:
+ #{recorded_exits.inspect}
+ Reasons:
+ #{stats_reasons}
+ EOM
+ end
+ end
+
+ if no_send_fallbacks
+ assert_equal(0, runtime_stats[:num_send_dynamic], "Expected no use of fallback implementation")
+ end
+
+ # Only available when --enable-yjit=dev
+ if runtime_stats[:all_stats]
+ missed_insns = insns.dup
+
+ insns_compiled.each do |op|
+ if missed_insns.include?(op)
+ # This instruction was compiled
+ missed_insns.delete(op)
+ end
+ end
+
+ unless missed_insns.empty?
+ flunk "Expected to compile instructions #{missed_insns.join(", ")} but didn't.\niseq:\n#{disasm}"
+ end
+ end
+ end
+
+ def script_shell_encode(s)
+ # We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants.
+ s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join
+ end
+
+ def eval_with_jit(script, call_threshold: 1, timeout: 1000, mem_size: nil, code_gc: false)
+ args = [
+ "--disable-gems",
+ "--yjit-call-threshold=#{call_threshold}",
+ "--yjit-stats=quiet"
+ ]
+ args << "--yjit-exec-mem-size=#{mem_size}" if mem_size
+ args << "--yjit-code-gc" if code_gc
+ args << "-e" << script_shell_encode(script)
+ stats_r, stats_w = IO.pipe
+ # Separate thread so we don't deadlock when
+ # the child ruby blocks writing the stats to fd 3
+ stats = ''
+ stats_reader = Thread.new do
+ stats = stats_r.read
+ stats_r.close
+ end
+ out, err, status = invoke_ruby(args, '', true, true, timeout: timeout, ios: { 3 => stats_w })
+ stats_w.close
+ stats_reader.join(timeout)
+ stats = Marshal.load(stats) if !stats.empty?
+ [status, out, err, stats]
+ ensure
+ stats_reader&.kill
+ stats_reader&.join(timeout)
+ stats_r&.close
+ stats_w&.close
+ end
+
+ # A wrapper of EnvUtil.invoke_ruby that uses RbConfig.ruby instead of EnvUtil.ruby
+ # that might use a wrong Ruby depending on your environment.
+ def invoke_ruby(*args, **kwargs)
+ EnvUtil.invoke_ruby(*args, rubybin: RbConfig.ruby, **kwargs)
+ end
+end
diff --git a/test/ruby/test_yjit_exit_locations.rb b/test/ruby/test_yjit_exit_locations.rb
new file mode 100644
index 0000000000..816ab457ce
--- /dev/null
+++ b/test/ruby/test_yjit_exit_locations.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+#
+# This set of tests can be run with:
+# make test-all TESTS='test/ruby/test_yjit_exit_locations.rb'
+
+require 'test/unit'
+require 'envutil'
+require 'tmpdir'
+require_relative '../lib/jit_support'
+
+return unless JITSupport.yjit_supported?
+
+# Tests for YJIT with assertions on tracing exits
+# insipired by the RJIT tests in test/ruby/test_yjit.rb
+class TestYJITExitLocations < Test::Unit::TestCase
+ def test_yjit_trace_exits_and_v_no_error
+ _stdout, stderr, _status = EnvUtil.invoke_ruby(%w(-v --yjit-trace-exits), '', true, true)
+ refute_includes(stderr, "NoMethodError")
+ end
+
+ def test_trace_exits_expandarray_splat
+ assert_exit_locations('*arr = []')
+ end
+
+ private
+
+ def assert_exit_locations(test_script)
+ write_results = <<~RUBY
+ IO.open(3).write Marshal.dump({
+ enabled: RubyVM::YJIT.trace_exit_locations_enabled?,
+ exit_locations: RubyVM::YJIT.exit_locations
+ })
+ RUBY
+
+ script = <<~RUBY
+ _test_proc = -> {
+ #{test_script}
+ }
+ result = _test_proc.call
+ #{write_results}
+ RUBY
+
+ run_script = eval_with_jit(script)
+ # If stats are disabled when configuring, --yjit-exit-locations
+ # can't be true. We don't want to check if exit_locations hash
+ # is not empty because that could indicate a bug in the exit
+ # locations collection.
+ return unless run_script[:enabled]
+ exit_locations = run_script[:exit_locations]
+
+ assert exit_locations.key?(:raw)
+ assert exit_locations.key?(:frames)
+ assert exit_locations.key?(:lines)
+ assert exit_locations.key?(:samples)
+ assert exit_locations.key?(:missed_samples)
+ assert exit_locations.key?(:gc_samples)
+
+ assert_equal 0, exit_locations[:missed_samples]
+ assert_equal 0, exit_locations[:gc_samples]
+
+ assert_not_empty exit_locations[:raw]
+ assert_not_empty exit_locations[:frames]
+ assert_not_empty exit_locations[:lines]
+
+ exit_locations[:frames].each do |frame_id, frame|
+ assert frame.key?(:name)
+ assert frame.key?(:file)
+ assert frame.key?(:samples)
+ assert frame.key?(:total_samples)
+ assert frame.key?(:edges)
+ end
+ end
+
+ def eval_with_jit(script)
+ args = [
+ "--disable-gems",
+ "--yjit-call-threshold=1",
+ "--yjit-trace-exits"
+ ]
+ args << "-e" << script_shell_encode(script)
+ stats_r, stats_w = IO.pipe
+ _out, _err, _status = EnvUtil.invoke_ruby(args,
+ '', true, true, timeout: 1000, ios: { 3 => stats_w }
+ )
+ stats_w.close
+ stats = stats_r.read
+ stats = Marshal.load(stats) if !stats.empty?
+ stats_r.close
+ stats
+ end
+
+ def script_shell_encode(s)
+ # We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants.
+ s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join
+ end
+end
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
new file mode 100644
index 0000000000..ad2df806d5
--- /dev/null
+++ b/test/ruby/test_zjit.rb
@@ -0,0 +1,4542 @@
+# frozen_string_literal: true
+#
+# This set of tests can be run with:
+# make test-all TESTS=test/ruby/test_zjit.rb
+
+require 'test/unit'
+require 'envutil'
+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
+ test
+ test
+ RUBY
+ end
+
+ def test_nil
+ assert_compiles 'nil', %q{
+ def test = nil
+ test
+ }
+ end
+
+ def test_putobject
+ assert_compiles '1', %q{
+ def test = 1
+ test
+ }
+ 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
+ test(5)
+ }
+ 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)
+ m = n
+ m
+ end
+ test(3)
+ }
+ 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
+ def bar(a) = a - 1
+ def baz(a, b) = a - b
+
+ def test1 = foo
+ def test2 = bar(3)
+ def test3 = baz(4, 1)
+
+ [test1, test2, test3]
+ }
+ 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
+ test # profile opt_plus
+ test
+ }, call_threshold: 2
+ end
+
+ def test_opt_plus_fixnum
+ assert_compiles '3', %q{
+ def test(a, b) = a + b
+ test(0, 1) # profile opt_plus
+ test(1, 2)
+ }, call_threshold: 2
+ end
+
+ def test_opt_plus_chain
+ assert_compiles '6', %q{
+ def test(a, b, c) = a + b + c
+ test(0, 1, 2) # profile opt_plus
+ test(1, 2, 3)
+ }, 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{
+ def test(a, b) = a - b
+ test(2, 1) # profile opt_minus
+ test(6, 4)
+ }, call_threshold: 2
+ end
+
+ def test_opt_mult
+ assert_compiles '6', %q{
+ def test(a, b) = a * b
+ test(1, 2) # profile opt_mult
+ test(2, 3)
+ }, call_threshold: 2
+ end
+
+ def test_opt_mult_overflow
+ assert_compiles '[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]', %q{
+ def test(a, b)
+ a * b
+ end
+ test(1, 1) # profile opt_mult
+
+ r1 = test(2, 3)
+ r2 = test(2, -3)
+ r3 = test(2 << 40, 2 << 41)
+ r4 = test(2 << 40, -2 << 41)
+ r5 = test(1 << 62, 1 << 62)
+
+ [r1, r2, r3, r4, r5]
+ }, call_threshold: 2
+ end
+
+ def test_opt_eq
+ assert_compiles '[true, false]', %q{
+ def test(a, b) = a == b
+ test(0, 2) # profile opt_eq
+ [test(1, 1), test(0, 1)]
+ }, 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
+ # TODO(max): Don't split this test; instead, run all tests with and without
+ # profiling.
+ assert_compiles '[false, true]', %q{
+ def test(a, b) = a != b
+ test(0, 2) # profile opt_neq
+ [test(1, 1), test(0, 1)]
+ }, insns: [:opt_neq], call_threshold: 1
+ end
+
+ def test_opt_neq_fixnum
+ assert_compiles '[false, true]', %q{
+ def test(a, b) = a != b
+ test(0, 2) # profile opt_neq
+ [test(1, 1), test(0, 1)]
+ }, call_threshold: 2
+ end
+
+ def test_opt_lt
+ assert_compiles '[true, false, false]', %q{
+ def test(a, b) = a < b
+ test(2, 3) # profile opt_lt
+ [test(0, 1), test(0, 0), test(1, 0)]
+ }, insns: [:opt_lt], call_threshold: 2
+ end
+
+ def test_opt_lt_with_literal_lhs
+ assert_compiles '[false, false, true]', %q{
+ def test(n) = 2 < n
+ test(2) # profile opt_lt
+ [test(1), test(2), test(3)]
+ }, insns: [:opt_lt], call_threshold: 2
+ end
+
+ def test_opt_le
+ assert_compiles '[true, true, false]', %q{
+ def test(a, b) = a <= b
+ test(2, 3) # profile opt_le
+ [test(0, 1), test(0, 0), test(1, 0)]
+ }, insns: [:opt_le], call_threshold: 2
+ end
+
+ def test_opt_gt
+ assert_compiles '[false, false, true]', %q{
+ 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
+ assert_compiles '[5]', %q{
+ def a = 5
+ def test = [a]
+ test
+ }
+ end
+
+ def test_new_array_order
+ assert_compiles '[3, 2, 1]', %q{
+ def a = 3
+ def b = 2
+ def c = 1
+ def test = [a, b, c]
+ test
+ }
+ end
+
+ def test_array_dup
+ assert_compiles '[1, 2, 3]', %q{
+ def test = [1,2,3]
+ test
+ }
+ 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)
+ if n < 5
+ 0
+ end
+ end
+ [test(3), test(7)]
+ }
+ end
+
+ def test_if_else
+ assert_compiles '[0, 1]', %q{
+ def test(n)
+ if n < 5
+ 0
+ else
+ 1
+ end
+ end
+ [test(3), test(7)]
+ }
+ end
+
+ def test_if_else_params
+ assert_compiles '[1, 20]', %q{
+ def test(n, a, b)
+ if n < 5
+ a
+ else
+ b
+ end
+ end
+ [test(3, 1, 2), test(7, 10, 20)]
+ }
+ end
+
+ def test_if_else_nested
+ assert_compiles '[3, 8, 9, 14]', %q{
+ def test(a, b, c, d, e)
+ if 2 < a
+ if a < 4
+ b
+ else
+ c
+ end
+ else
+ if a < 0
+ d
+ else
+ e
+ end
+ end
+ end
+ [
+ test(-1, 1, 2, 3, 4),
+ test( 0, 5, 6, 7, 8),
+ test( 3, 9, 10, 11, 12),
+ test( 5, 13, 14, 15, 16),
+ ]
+ }
+ end
+
+ def test_if_else_chained
+ assert_compiles '[12, 11, 21]', %q{
+ def test(a)
+ (if 2 < a then 1 else 2 end) + (if a < 4 then 10 else 20 end)
+ end
+ [test(0), test(3), test(5)]
+ }
+ end
+
+ def test_if_elsif_else
+ assert_compiles '[0, 2, 1]', %q{
+ def test(n)
+ if n < 5
+ 0
+ elsif 8 < n
+ 1
+ else
+ 2
+ end
+ end
+ [test(3), test(7), test(9)]
+ }
+ end
+
+ def test_ternary_operator
+ assert_compiles '[1, 20]', %q{
+ def test(n, a, b)
+ n < 5 ? a : b
+ end
+ [test(3, 1, 2), test(7, 10, 20)]
+ }
+ end
+
+ def test_ternary_operator_nested
+ assert_compiles '[2, 21]', %q{
+ def test(n, a, b)
+ (n < 5 ? a : b) + 1
+ end
+ [test(3, 1, 2), test(7, 10, 20)]
+ }
+ end
+
+ def test_while_loop
+ assert_compiles '10', %q{
+ def test(n)
+ i = 0
+ while i < n
+ i = i + 1
+ end
+ i
+ end
+ test(10)
+ }
+ end
+
+ def test_while_loop_chain
+ assert_compiles '[135, 270]', %q{
+ def test(n)
+ i = 0
+ while i < n
+ i = i + 1
+ end
+ while i < n * 10
+ i = i * 3
+ end
+ i
+ end
+ [test(5), test(10)]
+ }
+ end
+
+ def test_while_loop_nested
+ assert_compiles '[0, 4, 12]', %q{
+ def test(n, m)
+ i = 0
+ while i < n
+ j = 0
+ while j < m
+ j += 2
+ end
+ i += j
+ end
+ i
+ end
+ [test(0, 0), test(1, 3), test(10, 5)]
+ }
+ end
+
+ def test_while_loop_if_else
+ assert_compiles '[9, -1]', %q{
+ def test(n)
+ i = 0
+ while i < n
+ if n >= 10
+ return -1
+ else
+ i = i + 1
+ end
+ end
+ i
+ end
+ [test(9), test(10)]
+ }
+ end
+
+ def test_if_while_loop
+ assert_compiles '[9, 12]', %q{
+ def test(n)
+ i = 0
+ if n < 10
+ while i < n
+ i += 1
+ end
+ else
+ while i < n
+ i += 3
+ end
+ end
+ i
+ end
+ [test(9), test(10)]
+ }
+ end
+
+ def test_live_reg_past_ccall
+ assert_compiles '2', %q{
+ def callee = 1
+ def test = callee + callee
+ test
+ }
+ end
+
+ def test_method_call
+ assert_compiles '12', %q{
+ def callee(a, b)
+ a - b
+ end
+
+ def test
+ callee(4, 2) + 10
+ end
+
+ test # profile test
+ test
+ }, call_threshold: 2
+ end
+
+ def test_recursive_fact
+ assert_compiles '[1, 6, 720]', %q{
+ def fact(n)
+ if n == 0
+ return 1
+ end
+ return n * fact(n-1)
+ end
+ [fact(0), fact(3), fact(6)]
+ }
+ end
+
+ def test_profiled_fact
+ assert_compiles '[1, 6, 720]', %q{
+ def fact(n)
+ if n == 0
+ return 1
+ end
+ return n * fact(n-1)
+ end
+ fact(1) # profile fact
+ [fact(0), fact(3), fact(6)]
+ }, call_threshold: 3, num_profiles: 2
+ end
+
+ def test_recursive_fib
+ assert_compiles '[0, 2, 3]', %q{
+ def fib(n)
+ if n < 2
+ return n
+ end
+ return fib(n-1) + fib(n-2)
+ end
+ [fib(0), fib(3), fib(4)]
+ }
+ end
+
+ def test_profiled_fib
+ assert_compiles '[0, 2, 3]', %q{
+ def fib(n)
+ if n < 2
+ return n
+ end
+ return fib(n-1) + fib(n-2)
+ end
+ fib(3) # profile fib
+ [fib(0), fib(3), fib(4)]
+ }, 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
+ insn_names = RubyVM::INSTRUCTION_NAMES
+ zjit, others = insn_names.map.with_index.partition { |name, _| name.start_with?('zjit_') }
+ zjit_indexes = zjit.map(&:last)
+ other_indexes = others.map(&:last)
+ zjit_indexes.product(other_indexes).each do |zjit_index, other_index|
+ assert zjit_index > other_index, "'#{insn_names[zjit_index]}' at #{zjit_index} "\
+ "must be defined after '#{insn_names[other_index]}' at #{other_index}"
+ 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, 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
+ 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}
+ }
+ IO.open(#{pipe_fd}).write(Marshal.dump(result))
+ RUBY
+
+ out, err, status, result = eval_with_jit(script, pipe_fd:, **opts)
+ assert_success(out, err, status)
+
+ 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
+
+ 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,
+ 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)
+ 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
+ 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)
+ # We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants.
+ s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join
+ end
+end