summaryrefslogtreecommitdiff
path: root/spec/ruby/core/objectspace
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/objectspace')
-rw-r--r--spec/ruby/core/objectspace/_id2ref_spec.rb65
-rw-r--r--spec/ruby/core/objectspace/count_objects_spec.rb5
-rw-r--r--spec/ruby/core/objectspace/define_finalizer_spec.rb215
-rw-r--r--spec/ruby/core/objectspace/each_object_spec.rb213
-rw-r--r--spec/ruby/core/objectspace/fixtures/classes.rb64
-rw-r--r--spec/ruby/core/objectspace/garbage_collect_spec.rb22
-rw-r--r--spec/ruby/core/objectspace/undefine_finalizer_spec.rb33
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/clear_spec.rb27
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/delete_spec.rb51
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb107
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb82
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/fixtures/classes.rb5
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb28
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb21
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/key_spec.rb44
-rw-r--r--spec/ruby/core/objectspace/weakmap/delete_spec.rb30
-rw-r--r--spec/ruby/core/objectspace/weakmap/each_key_spec.rb11
-rw-r--r--spec/ruby/core/objectspace/weakmap/each_pair_spec.rb11
-rw-r--r--spec/ruby/core/objectspace/weakmap/each_spec.rb11
-rw-r--r--spec/ruby/core/objectspace/weakmap/each_value_spec.rb11
-rw-r--r--spec/ruby/core/objectspace/weakmap/element_reference_spec.rb24
-rw-r--r--spec/ruby/core/objectspace/weakmap/element_set_spec.rb38
-rw-r--r--spec/ruby/core/objectspace/weakmap/include_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/inspect_spec.rb25
-rw-r--r--spec/ruby/core/objectspace/weakmap/key_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/keys_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/length_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/member_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/shared/each.rb10
-rw-r--r--spec/ruby/core/objectspace/weakmap/shared/include.rb30
-rw-r--r--spec/ruby/core/objectspace/weakmap/shared/members.rb14
-rw-r--r--spec/ruby/core/objectspace/weakmap/shared/size.rb14
-rw-r--r--spec/ruby/core/objectspace/weakmap/size_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/values_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap_spec.rb12
35 files changed, 1265 insertions, 0 deletions
diff --git a/spec/ruby/core/objectspace/_id2ref_spec.rb b/spec/ruby/core/objectspace/_id2ref_spec.rb
new file mode 100644
index 0000000000..1ae3230bdf
--- /dev/null
+++ b/spec/ruby/core/objectspace/_id2ref_spec.rb
@@ -0,0 +1,65 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "4.0" do
+ describe "ObjectSpace._id2ref" do
+ it "is deprecated" do
+ id = nil.object_id
+ -> {
+ ObjectSpace._id2ref(id)
+ }.should complain(/warning: ObjectSpace\._id2ref is deprecated/)
+ end
+ end
+end
+
+ruby_version_is ""..."4.0" do
+ describe "ObjectSpace._id2ref" do
+ it "converts an object id to a reference to the object" do
+ s = "I am a string"
+ r = ObjectSpace._id2ref(s.object_id)
+ r.should == s
+ end
+
+ it "retrieves true by object_id" do
+ ObjectSpace._id2ref(true.object_id).should == true
+ end
+
+ it "retrieves false by object_id" do
+ ObjectSpace._id2ref(false.object_id).should == false
+ end
+
+ it "retrieves nil by object_id" do
+ ObjectSpace._id2ref(nil.object_id).should == nil
+ end
+
+ it "retrieves a small Integer by object_id" do
+ ObjectSpace._id2ref(1.object_id).should == 1
+ ObjectSpace._id2ref((-42).object_id).should == -42
+ end
+
+ it "retrieves a large Integer by object_id" do
+ obj = 1 << 88
+ ObjectSpace._id2ref(obj.object_id).should.equal?(obj)
+ end
+
+ it "retrieves a Symbol by object_id" do
+ ObjectSpace._id2ref(:sym.object_id).should.equal?(:sym)
+ end
+
+ it "retrieves a String by object_id" do
+ obj = "str"
+ ObjectSpace._id2ref(obj.object_id).should.equal?(obj)
+ end
+
+ it "retrieves a frozen literal String by object_id" do
+ ObjectSpace._id2ref("frozen string literal _id2ref".freeze.object_id).should.equal?("frozen string literal _id2ref".freeze)
+ end
+
+ it "retrieves an Encoding by object_id" do
+ ObjectSpace._id2ref(Encoding::UTF_8.object_id).should.equal?(Encoding::UTF_8)
+ end
+
+ it 'raises RangeError when an object could not be found' do
+ proc { ObjectSpace._id2ref(1 << 60) }.should raise_error(RangeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/count_objects_spec.rb b/spec/ruby/core/objectspace/count_objects_spec.rb
new file mode 100644
index 0000000000..e9831a3a42
--- /dev/null
+++ b/spec/ruby/core/objectspace/count_objects_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace.count_objects" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/objectspace/define_finalizer_spec.rb b/spec/ruby/core/objectspace/define_finalizer_spec.rb
new file mode 100644
index 0000000000..0f4b54c345
--- /dev/null
+++ b/spec/ruby/core/objectspace/define_finalizer_spec.rb
@@ -0,0 +1,215 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# Why do we not test that finalizers are run by the GC? The documentation
+# says that finalizers are never guaranteed to be run, so we can't
+# spec that they are. On some implementations of Ruby the finalizers may
+# run asynchronously, meaning that we can't predict when they'll run,
+# even if they were guaranteed to do so. Even on MRI finalizers can be
+# very unpredictable, due to conservative stack scanning and references
+# left in unused memory.
+
+describe "ObjectSpace.define_finalizer" do
+ it "raises an ArgumentError if the action does not respond to call" do
+ -> {
+ ObjectSpace.define_finalizer(Object.new, mock("ObjectSpace.define_finalizer no #call"))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "accepts an object and a proc" do
+ handler = -> id { id }
+ ObjectSpace.define_finalizer(Object.new, handler).should == [0, handler]
+ end
+
+ it "accepts an object and a bound method" do
+ handler = mock("callable")
+ def handler.finalize(id) end
+ finalize = handler.method(:finalize)
+ ObjectSpace.define_finalizer(Object.new, finalize).should == [0, finalize]
+ end
+
+ it "accepts an object and a callable" do
+ handler = mock("callable")
+ def handler.call(id) end
+ ObjectSpace.define_finalizer(Object.new, handler).should == [0, handler]
+ end
+
+ it "accepts an object and a block" do
+ handler = -> id { id }
+ ObjectSpace.define_finalizer(Object.new, &handler).should == [0, handler]
+ end
+
+ it "raises ArgumentError trying to define a finalizer on a non-reference" do
+ -> {
+ ObjectSpace.define_finalizer(:blah) { 1 }
+ }.should raise_error(ArgumentError)
+ end
+
+ # see [ruby-core:24095]
+ it "calls finalizer on process termination" do
+ code = <<-RUBY
+ def scoped
+ Proc.new { puts "finalizer run" }
+ end
+ handler = scoped
+ obj = +"Test"
+ ObjectSpace.define_finalizer(obj, handler)
+ exit 0
+ RUBY
+
+ ruby_exe(code, :args => "2>&1").should include("finalizer run\n")
+ end
+
+ it "warns if the finalizer has the object as the receiver" do
+ code = <<-RUBY
+ class CapturesSelf
+ def initialize
+ ObjectSpace.define_finalizer(self, proc {
+ puts "finalizer run"
+ })
+ end
+ end
+ CapturesSelf.new
+ exit 0
+ RUBY
+
+ ruby_exe(code, :args => "2>&1").should include("warning: finalizer references object to be finalized\n")
+ end
+
+ it "warns if the finalizer is a method bound to the receiver" do
+ code = <<-RUBY
+ class CapturesSelf
+ def initialize
+ ObjectSpace.define_finalizer(self, method(:finalize))
+ end
+ def finalize(id)
+ puts "finalizer run"
+ end
+ end
+ CapturesSelf.new
+ exit 0
+ RUBY
+
+ ruby_exe(code, :args => "2>&1").should include("warning: finalizer references object to be finalized\n")
+ end
+
+ it "warns if the finalizer was a block in the receiver" do
+ code = <<-RUBY
+ class CapturesSelf
+ def initialize
+ ObjectSpace.define_finalizer(self) do
+ puts "finalizer run"
+ end
+ end
+ end
+ CapturesSelf.new
+ exit 0
+ RUBY
+
+ ruby_exe(code, :args => "2>&1").should include("warning: finalizer references object to be finalized\n")
+ end
+
+ it "calls a finalizer at exit even if it is self-referencing" do
+ code = <<-RUBY
+ obj = +"Test"
+ handler = Proc.new { puts "finalizer run" }
+ ObjectSpace.define_finalizer(obj, handler)
+ exit 0
+ RUBY
+
+ ruby_exe(code).should include("finalizer run\n")
+ end
+
+ it "calls a finalizer at exit even if it is indirectly self-referencing" do
+ code = <<-RUBY
+ class CapturesSelf
+ def initialize
+ ObjectSpace.define_finalizer(self, finalizer(self))
+ end
+ def finalizer(zelf)
+ proc do
+ puts "finalizer run"
+ end
+ end
+ end
+ CapturesSelf.new
+ exit 0
+ RUBY
+
+ ruby_exe(code, :args => "2>&1").should include("finalizer run\n")
+ end
+
+ it "calls a finalizer defined in a finalizer running at exit" do
+ code = <<-RUBY
+ obj = +"Test"
+ handler = Proc.new do
+ obj2 = +"Test"
+ handler2 = Proc.new { puts "finalizer 2 run" }
+ ObjectSpace.define_finalizer(obj2, handler2)
+ exit 0
+ end
+ ObjectSpace.define_finalizer(obj, handler)
+ exit 0
+ RUBY
+
+ ruby_exe(code, :args => "2>&1").should include("finalizer 2 run\n")
+ end
+
+ it "allows multiple finalizers with different 'callables' to be defined" do
+ code = <<-'RUBY'
+ obj = Object.new
+
+ ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized1\n" })
+ ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized2\n" })
+
+ exit 0
+ RUBY
+
+ ruby_exe(code).lines.sort.should == ["finalized1\n", "finalized2\n"]
+ end
+
+ it "defines same finalizer only once" do
+ code = <<~RUBY
+ obj = Object.new
+ p = proc { |id| print "ok" }
+ ObjectSpace.define_finalizer(obj, p.dup)
+ ObjectSpace.define_finalizer(obj, p.dup)
+ RUBY
+
+ ruby_exe(code).should == "ok"
+ end
+
+ it "returns the defined finalizer" do
+ obj = Object.new
+ p = proc { |id| }
+ p2 = p.dup
+
+ ret = ObjectSpace.define_finalizer(obj, p)
+ ret.should == [0, p]
+ ret[1].should.equal?(p)
+
+ ret = ObjectSpace.define_finalizer(obj, p2)
+ ret.should == [0, p]
+ ret[1].should.equal?(p)
+ end
+
+ describe "when $VERBOSE is not nil" do
+ it "warns if an exception is raised in finalizer" do
+ code = <<-RUBY
+ ObjectSpace.define_finalizer(Object.new) { raise "finalizing" }
+ RUBY
+
+ ruby_exe(code, args: "2>&1").should include("warning: Exception in finalizer", "finalizing")
+ end
+ end
+
+ describe "when $VERBOSE is nil" do
+ it "does not warn even if an exception is raised in finalizer" do
+ code = <<-RUBY
+ ObjectSpace.define_finalizer(Object.new) { raise "finalizing" }
+ RUBY
+
+ ruby_exe(code, args: "2>&1", options: "-W0").should == ""
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/each_object_spec.rb b/spec/ruby/core/objectspace/each_object_spec.rb
new file mode 100644
index 0000000000..09a582afaf
--- /dev/null
+++ b/spec/ruby/core/objectspace/each_object_spec.rb
@@ -0,0 +1,213 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "ObjectSpace.each_object" do
+ it "calls the block once for each living, non-immediate object in the Ruby process" do
+ klass = Class.new
+ new_obj = klass.new
+
+ yields = 0
+ count = ObjectSpace.each_object(klass) do |obj|
+ obj.should == new_obj
+ yields += 1
+ end
+ count.should == 1
+ yields.should == 1
+
+ # this is needed to prevent the new_obj from being GC'd too early
+ new_obj.should_not == nil
+ end
+
+ it "calls the block once for each class, module in the Ruby process" do
+ klass = Class.new
+ mod = Module.new
+
+ [klass, mod].each do |k|
+ yields = 0
+ got_it = false
+ count = ObjectSpace.each_object(k.class) do |obj|
+ got_it = true if obj == k
+ yields += 1
+ end
+ got_it.should == true
+ count.should == yields
+ end
+ end
+
+ it "returns an enumerator if not given a block" do
+ klass = Class.new
+ new_obj = klass.new
+
+ counter = ObjectSpace.each_object(klass)
+ counter.should be_an_instance_of(Enumerator)
+ counter.each{}.should == 1
+ # this is needed to prevent the new_obj from being GC'd too early
+ new_obj.should_not == nil
+ end
+
+ it "finds an object stored in a global variable" do
+ $object_space_global_variable = ObjectSpaceFixtures::ObjectToBeFound.new(:global)
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:global)
+ end
+
+ it "finds an object stored in a top-level constant" do
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:top_level_constant)
+ end
+
+ it "finds an object stored in a second-level constant" do
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:second_level_constant)
+ end
+
+ it "finds an object stored in a local variable" do
+ local = ObjectSpaceFixtures::ObjectToBeFound.new(:local)
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:local)
+ end
+
+ it "finds an object stored in a local variable captured in a block explicitly" do
+ proc = Proc.new {
+ local_in_block = ObjectSpaceFixtures::ObjectToBeFound.new(:local_in_block_explicit)
+ Proc.new { local_in_block }
+ }.call
+
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:local_in_block_explicit)
+ end
+
+ it "finds an object stored in a local variable captured in a block implicitly" do
+ proc = Proc.new {
+ local_in_block = ObjectSpaceFixtures::ObjectToBeFound.new(:local_in_block_implicit)
+ Proc.new { }
+ }.call
+
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:local_in_block_implicit)
+ end
+
+ it "finds an object stored in a local variable captured in by a method defined with a block" do
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:captured_by_define_method)
+ end
+
+ it "finds an object stored in a local variable captured in a Proc#binding" do
+ binding = Proc.new {
+ local_in_proc_binding = ObjectSpaceFixtures::ObjectToBeFound.new(:local_in_proc_binding)
+ Proc.new { }.binding
+ }.call
+
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:local_in_proc_binding)
+ end
+
+ it "finds an object stored in a local variable captured in a Kernel#binding" do
+ b = Proc.new {
+ local_in_kernel_binding = ObjectSpaceFixtures::ObjectToBeFound.new(:local_in_kernel_binding)
+ binding
+ }.call
+
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:local_in_kernel_binding)
+ end
+
+ it "finds an object stored in a local variable set in a binding manually" do
+ b = binding
+ b.eval("local = ObjectSpaceFixtures::ObjectToBeFound.new(:local_in_manual_binding)")
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:local_in_manual_binding)
+ end
+
+ it "finds an object stored in an array" do
+ array = [ObjectSpaceFixtures::ObjectToBeFound.new(:array)]
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:array)
+ end
+
+ it "finds an object stored in a hash key" do
+ hash = {ObjectSpaceFixtures::ObjectToBeFound.new(:hash_key) => :value}
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:hash_key)
+ end
+
+ it "finds an object stored in a hash value" do
+ hash = {a: ObjectSpaceFixtures::ObjectToBeFound.new(:hash_value)}
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:hash_value)
+ end
+
+ it "finds an object stored in an instance variable" do
+ local = ObjectSpaceFixtures::ObjectWithInstanceVariable.new
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:instance_variable)
+ end
+
+ it "finds an object stored in a thread local" do
+ thread = Thread.new {}
+ thread.thread_variable_set(:object_space_thread_local, ObjectSpaceFixtures::ObjectToBeFound.new(:thread_local))
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:thread_local)
+ thread.join
+ end
+
+ it "finds an object stored in a fiber local" do
+ Thread.current[:object_space_fiber_local] = ObjectSpaceFixtures::ObjectToBeFound.new(:fiber_local)
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:fiber_local)
+ end
+
+ it "finds an object captured in an at_exit handler" do
+ Proc.new {
+ local = ObjectSpaceFixtures::ObjectToBeFound.new(:at_exit)
+
+ at_exit do
+ local
+ end
+ }.call
+
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:at_exit)
+ end
+
+ it "finds an object captured in finalizer" do
+ alive = Object.new
+
+ Proc.new {
+ local = ObjectSpaceFixtures::ObjectToBeFound.new(:finalizer)
+
+ ObjectSpace.define_finalizer(alive, Proc.new {
+ local
+ })
+ }.call
+
+ ObjectSpaceFixtures.to_be_found_symbols.should include(:finalizer)
+
+ alive.should_not be_nil
+ end
+
+ describe "on singleton classes" do
+ before :each do
+ @klass = Class.new
+ instance = @klass.new
+ @sclass = instance.singleton_class
+ @meta = @klass.singleton_class
+ end
+
+ it "does not walk hidden metaclasses" do
+ klass = Class.new.singleton_class
+ ancestors = ObjectSpace.each_object(Class).select { |c| klass.is_a? c }
+ hidden = ancestors.find { |h| h.inspect.include? klass.inspect }
+ hidden.should == nil
+ end
+
+ it "walks singleton classes" do
+ @sclass.should be_kind_of(@meta)
+ ObjectSpace.each_object(@meta).to_a.should include(@sclass)
+ end
+ end
+
+ it "walks a class and its normal descendants when passed the class's singleton class" do
+ a = Class.new
+ b = Class.new(a)
+ c = Class.new(a)
+ d = Class.new(b)
+
+ c_instance = c.new
+ c_sclass = c_instance.singleton_class
+
+ expected = [ a, b, c, d ]
+
+ expected << c_sclass
+ c_sclass.should be_kind_of(a.singleton_class)
+
+ b.extend Enumerable # included modules should not be walked
+
+ classes = ObjectSpace.each_object(a.singleton_class).to_a
+
+ classes.sort_by(&:object_id).should == expected.sort_by(&:object_id)
+ end
+end
diff --git a/spec/ruby/core/objectspace/fixtures/classes.rb b/spec/ruby/core/objectspace/fixtures/classes.rb
new file mode 100644
index 0000000000..612156c180
--- /dev/null
+++ b/spec/ruby/core/objectspace/fixtures/classes.rb
@@ -0,0 +1,64 @@
+module ObjectSpaceFixtures
+ def self.garbage
+ blah
+ end
+
+ def self.blah
+ o = "hello"
+ @garbage_objid = o.object_id
+ return o
+ end
+
+ @last_objid = nil
+
+ def self.last_objid
+ @last_objid
+ end
+
+ def self.garbage_objid
+ @garbage_objid
+ end
+
+ def self.make_finalizer
+ proc { |obj_id| @last_objid = obj_id }
+ end
+
+ def self.define_finalizer
+ handler = -> obj { ScratchPad.record :finalized }
+ ObjectSpace.define_finalizer "#{rand 5}", handler
+ end
+
+ def self.scoped(wr)
+ return Proc.new { wr.write "finalized"; wr.close }
+ end
+
+ class ObjectToBeFound
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+ end
+
+ class ObjectWithInstanceVariable
+ def initialize
+ @instance_variable = ObjectToBeFound.new(:instance_variable)
+ end
+ end
+
+ def self.to_be_found_symbols
+ ObjectSpace.each_object(ObjectToBeFound).map do |o|
+ o.name
+ end
+ end
+
+ o = ObjectToBeFound.new(:captured_by_define_method)
+ define_method :capturing_method do
+ o
+ end
+
+ SECOND_LEVEL_CONSTANT = ObjectToBeFound.new(:second_level_constant)
+
+end
+
+OBJECT_SPACE_TOP_LEVEL_CONSTANT = ObjectSpaceFixtures::ObjectToBeFound.new(:top_level_constant)
diff --git a/spec/ruby/core/objectspace/garbage_collect_spec.rb b/spec/ruby/core/objectspace/garbage_collect_spec.rb
new file mode 100644
index 0000000000..521eaa8785
--- /dev/null
+++ b/spec/ruby/core/objectspace/garbage_collect_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace.garbage_collect" do
+
+ it "can be invoked without any exceptions" do
+ -> { ObjectSpace.garbage_collect }.should_not raise_error
+ end
+
+ it "accepts keyword arguments" do
+ ObjectSpace.garbage_collect(full_mark: true, immediate_sweep: true).should == nil
+ end
+
+ it "ignores the supplied block" do
+ -> { ObjectSpace.garbage_collect {} }.should_not raise_error
+ end
+
+ it "always returns nil" do
+ ObjectSpace.garbage_collect.should == nil
+ ObjectSpace.garbage_collect.should == nil
+ end
+
+end
diff --git a/spec/ruby/core/objectspace/undefine_finalizer_spec.rb b/spec/ruby/core/objectspace/undefine_finalizer_spec.rb
new file mode 100644
index 0000000000..f57d5a7845
--- /dev/null
+++ b/spec/ruby/core/objectspace/undefine_finalizer_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace.undefine_finalizer" do
+ it "removes finalizers for an object" do
+ code = <<~RUBY
+ obj = Object.new
+ ObjectSpace.define_finalizer(obj, proc { |id| puts "hello" })
+ ObjectSpace.undefine_finalizer(obj)
+ RUBY
+
+ ruby_exe(code).should.empty?
+ end
+
+ it "should not remove finalizers for a frozen object" do
+ code = <<~RUBY
+ obj = Object.new
+ ObjectSpace.define_finalizer(obj, proc { |id| print "ok" })
+ obj.freeze
+ begin
+ ObjectSpace.undefine_finalizer(obj)
+ rescue
+ end
+ RUBY
+
+ ruby_exe(code).should == "ok"
+ end
+
+ it "should raise when removing finalizers for a frozen object" do
+ obj = Object.new
+ obj.freeze
+ -> { ObjectSpace.undefine_finalizer(obj) }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakkeymap/clear_spec.rb b/spec/ruby/core/objectspace/weakkeymap/clear_spec.rb
new file mode 100644
index 0000000000..8050e2c307
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakkeymap/clear_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is '3.3' do
+ describe "ObjectSpace::WeakKeyMap#clear" do
+ it "removes all the entries" do
+ m = ObjectSpace::WeakKeyMap.new
+
+ key = Object.new
+ value = Object.new
+ m[key] = value
+
+ key2 = Object.new
+ value2 = Object.new
+ m[key2] = value2
+
+ m.clear
+
+ m.key?(key).should == false
+ m.key?(key2).should == false
+ end
+
+ it "returns self" do
+ m = ObjectSpace::WeakKeyMap.new
+ m.clear.should.equal?(m)
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakkeymap/delete_spec.rb b/spec/ruby/core/objectspace/weakkeymap/delete_spec.rb
new file mode 100644
index 0000000000..3cd61355d6
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakkeymap/delete_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is '3.3' do
+ describe "ObjectSpace::WeakKeyMap#delete" do
+ it "removes the entry and returns the deleted value" do
+ m = ObjectSpace::WeakKeyMap.new
+ key = Object.new
+ value = Object.new
+ m[key] = value
+
+ m.delete(key).should == value
+ m.key?(key).should == false
+ end
+
+ it "uses equality semantic" do
+ m = ObjectSpace::WeakKeyMap.new
+ key = "foo".upcase
+ value = Object.new
+ m[key] = value
+
+ m.delete("foo".upcase).should == value
+ m.key?(key).should == false
+ end
+
+ it "calls supplied block if the key is not found" do
+ key = Object.new
+ m = ObjectSpace::WeakKeyMap.new
+ return_value = m.delete(key) do |yielded_key|
+ yielded_key.should == key
+ 5
+ end
+ return_value.should == 5
+ end
+
+ it "returns nil if the key is not found when no block is given" do
+ m = ObjectSpace::WeakKeyMap.new
+ m.delete(Object.new).should == nil
+ end
+
+ it "returns nil when a key cannot be garbage collected" do
+ map = ObjectSpace::WeakKeyMap.new
+
+ map.delete(1).should == nil
+ map.delete(1.0).should == nil
+ map.delete(:a).should == nil
+ map.delete(true).should == nil
+ map.delete(false).should == nil
+ map.delete(nil).should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb b/spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb
new file mode 100644
index 0000000000..51368e8d3b
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb
@@ -0,0 +1,107 @@
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.3" do
+ describe "ObjectSpace::WeakKeyMap#[]" do
+ it "is faithful to the map's content" do
+ map = ObjectSpace::WeakKeyMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+ map[key1] = ref1
+ map[key1].should == ref1
+ map[key1] = ref1
+ map[key1].should == ref1
+ map[key2] = ref2
+ map[key1].should == ref1
+ map[key2].should == ref2
+ end
+
+ it "compares keys with #eql? semantics" do
+ map = ObjectSpace::WeakKeyMap.new
+ key = [1.0]
+ map[key] = "x"
+ map[[1]].should == nil
+ map[[1.0]].should == "x"
+ key.should == [1.0] # keep the key alive until here to keep the map entry
+
+ map = ObjectSpace::WeakKeyMap.new
+ key = [1]
+ map[key] = "x"
+ map[[1.0]].should == nil
+ map[[1]].should == "x"
+ key.should == [1] # keep the key alive until here to keep the map entry
+
+ map = ObjectSpace::WeakKeyMap.new
+ key1, key2 = %w[a a].map(&:upcase)
+ ref = "x"
+ map[key1] = ref
+ map[key2].should == ref
+ end
+
+ it "compares key via #hash first" do
+ x = mock('0')
+ x.should_receive(:hash).and_return(0)
+
+ map = ObjectSpace::WeakKeyMap.new
+ key = 'foo'
+ map[key] = :bar
+ map[x].should == nil
+ end
+
+ it "does not compare keys with different #hash values via #eql?" do
+ x = mock('x')
+ x.should_not_receive(:eql?)
+ x.stub!(:hash).and_return(0)
+
+ y = mock('y')
+ y.should_not_receive(:eql?)
+ y.stub!(:hash).and_return(1)
+
+ map = ObjectSpace::WeakKeyMap.new
+ map[y] = 1
+ map[x].should == nil
+ end
+
+ it "compares keys with the same #hash value via #eql?" do
+ x = mock('x')
+ x.should_receive(:eql?).and_return(true)
+ x.stub!(:hash).and_return(42)
+
+ y = mock('y')
+ y.should_not_receive(:eql?)
+ y.stub!(:hash).and_return(42)
+
+ map = ObjectSpace::WeakKeyMap.new
+ map[y] = 1
+ map[x].should == 1
+ end
+
+ it "finds a value via an identical key even when its #eql? isn't reflexive" do
+ x = mock('x')
+ x.should_receive(:hash).at_least(1).and_return(42)
+ x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI.
+
+ map = ObjectSpace::WeakKeyMap.new
+ map[x] = :x
+ map[x].should == :x
+ end
+
+ it "supports keys with private #hash method" do
+ key = WeakKeyMapSpecs::KeyWithPrivateHash.new
+ map = ObjectSpace::WeakKeyMap.new
+ map[key] = 42
+ map[key].should == 42
+ end
+
+ it "returns nil and does not raise error when a key cannot be garbage collected" do
+ map = ObjectSpace::WeakKeyMap.new
+
+ map[1].should == nil
+ map[1.0].should == nil
+ map[:a].should == nil
+ map[true].should == nil
+ map[false].should == nil
+ map[nil].should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb b/spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb
new file mode 100644
index 0000000000..8db8d780c7
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb
@@ -0,0 +1,82 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is "3.3" do
+ describe "ObjectSpace::WeakKeyMap#[]=" do
+ def should_accept(map, key, value)
+ (map[key] = value).should == value
+ map.should.key?(key)
+ map[key].should == value
+ end
+
+ it "is correct" do
+ map = ObjectSpace::WeakKeyMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+ should_accept(map, key1, ref1)
+ should_accept(map, key1, ref1)
+ should_accept(map, key2, ref2)
+ map[key1].should == ref1
+ end
+
+ it "requires the keys to implement #hash" do
+ map = ObjectSpace::WeakKeyMap.new
+ -> { map[BasicObject.new] = 1 }.should raise_error(NoMethodError, /undefined method [`']hash' for an instance of BasicObject/)
+ end
+
+ it "accepts frozen keys or values" do
+ map = ObjectSpace::WeakKeyMap.new
+ x = Object.new
+ should_accept(map, x, true)
+ should_accept(map, x, false)
+ should_accept(map, x, 42)
+ should_accept(map, x, :foo)
+
+ y = Object.new.freeze
+ should_accept(map, x, y)
+ should_accept(map, y, x)
+ end
+
+ it "does not duplicate and freeze String keys (like Hash#[]= does)" do
+ map = ObjectSpace::WeakKeyMap.new
+ key = +"a"
+ map[key] = 1
+
+ map.getkey("a").should.equal? key
+ map.getkey("a").should_not.frozen?
+
+ key.should == "a" # keep the key alive until here to keep the map entry
+ end
+
+ context "a key cannot be garbage collected" do
+ it "raises ArgumentError when Integer is used as a key" do
+ map = ObjectSpace::WeakKeyMap.new
+ -> { map[1] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/)
+ end
+
+ it "raises ArgumentError when Float is used as a key" do
+ map = ObjectSpace::WeakKeyMap.new
+ -> { map[1.0] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/)
+ end
+
+ it "raises ArgumentError when Symbol is used as a key" do
+ map = ObjectSpace::WeakKeyMap.new
+ -> { map[:a] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/)
+ end
+
+ it "raises ArgumentError when true is used as a key" do
+ map = ObjectSpace::WeakKeyMap.new
+ -> { map[true] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/)
+ end
+
+ it "raises ArgumentError when false is used as a key" do
+ map = ObjectSpace::WeakKeyMap.new
+ -> { map[false] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/)
+ end
+
+ it "raises ArgumentError when nil is used as a key" do
+ map = ObjectSpace::WeakKeyMap.new
+ -> { map[nil] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakkeymap/fixtures/classes.rb b/spec/ruby/core/objectspace/weakkeymap/fixtures/classes.rb
new file mode 100644
index 0000000000..0fd04551b5
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakkeymap/fixtures/classes.rb
@@ -0,0 +1,5 @@
+module WeakKeyMapSpecs
+ class KeyWithPrivateHash
+ private :hash
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb b/spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb
new file mode 100644
index 0000000000..8a2dbf809d
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is "3.3" do
+ describe "ObjectSpace::WeakKeyMap#getkey" do
+ it "returns the existing equal key" do
+ map = ObjectSpace::WeakKeyMap.new
+ key1, key2 = %w[a a].map(&:upcase)
+
+ map[key1] = true
+ map.getkey(key2).should equal(key1)
+ map.getkey("X").should == nil
+
+ key1.should == "A" # keep the key alive until here to keep the map entry
+ key2.should == "A" # keep the key alive until here to keep the map entry
+ end
+
+ it "returns nil when a key cannot be garbage collected" do
+ map = ObjectSpace::WeakKeyMap.new
+
+ map.getkey(1).should == nil
+ map.getkey(1.0).should == nil
+ map.getkey(:a).should == nil
+ map.getkey(true).should == nil
+ map.getkey(false).should == nil
+ map.getkey(nil).should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb b/spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb
new file mode 100644
index 0000000000..319f050970
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is "3.3" do
+ describe "ObjectSpace::WeakKeyMap#inspect" do
+ it "only displays size in output" do
+ map = ObjectSpace::WeakKeyMap.new
+ key1, key2, key3 = "foo", "bar", "bar"
+ map.inspect.should =~ /\A\#<ObjectSpace::WeakKeyMap:0x\h+ size=0>\z/
+ map[key1] = 1
+ map.inspect.should =~ /\A\#<ObjectSpace::WeakKeyMap:0x\h+ size=1>\z/
+ map[key2] = 2
+ map.inspect.should =~ /\A\#<ObjectSpace::WeakKeyMap:0x\h+ size=2>\z/
+ map[key3] = 3
+ map.inspect.should =~ /\A\#<ObjectSpace::WeakKeyMap:0x\h+ size=2>\z/
+
+ key1.should == "foo" # keep the key alive until here to keep the map entry
+ key2.should == "bar" # keep the key alive until here to keep the map entry
+ key3.should == "bar" # keep the key alive until here to keep the map entry
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakkeymap/key_spec.rb b/spec/ruby/core/objectspace/weakkeymap/key_spec.rb
new file mode 100644
index 0000000000..a9a2e12432
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakkeymap/key_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is "3.3" do
+ describe "ObjectSpace::WeakKeyMap#key?" do
+ it "recognizes keys in use" do
+ map = ObjectSpace::WeakKeyMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+
+ map[key1] = ref1
+ map.key?(key1).should == true
+ map[key1] = ref1
+ map.key?(key1).should == true
+ map[key2] = ref2
+ map.key?(key2).should == true
+ end
+
+ it "matches using equality semantics" do
+ map = ObjectSpace::WeakKeyMap.new
+ key1, key2 = %w[a a].map(&:upcase)
+ ref = "x"
+ map[key1] = ref
+ map.key?(key2).should == true
+ end
+
+ it "reports true if the pair exists and the value is nil" do
+ map = ObjectSpace::WeakKeyMap.new
+ key = Object.new
+ map[key] = nil
+ map.key?(key).should == true
+ end
+
+ it "returns false when a key cannot be garbage collected" do
+ map = ObjectSpace::WeakKeyMap.new
+
+ map.key?(1).should == false
+ map.key?(1.0).should == false
+ map.key?(:a).should == false
+ map.key?(true).should == false
+ map.key?(false).should == false
+ map.key?(nil).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/delete_spec.rb b/spec/ruby/core/objectspace/weakmap/delete_spec.rb
new file mode 100644
index 0000000000..302de264fb
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/delete_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is '3.3' do
+ describe "ObjectSpace::WeakMap#delete" do
+ it "removes the entry and returns the deleted value" do
+ m = ObjectSpace::WeakMap.new
+ key = Object.new
+ value = Object.new
+ m[key] = value
+
+ m.delete(key).should == value
+ m.key?(key).should == false
+ end
+
+ it "calls supplied block if the key is not found" do
+ key = Object.new
+ m = ObjectSpace::WeakMap.new
+ return_value = m.delete(key) do |yielded_key|
+ yielded_key.should == key
+ 5
+ end
+ return_value.should == 5
+ end
+
+ it "returns nil if the key is not found when no block is given" do
+ m = ObjectSpace::WeakMap.new
+ m.delete(Object.new).should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/each_key_spec.rb b/spec/ruby/core/objectspace/weakmap/each_key_spec.rb
new file mode 100644
index 0000000000..df971deeb9
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/each_key_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/members'
+require_relative 'shared/each'
+
+describe "ObjectSpace::WeakMap#each_key" do
+ it_behaves_like :weakmap_members, -> map { a = []; map.each_key{ |k| a << k }; a }, %w[A B]
+end
+
+describe "ObjectSpace::WeakMap#each_key" do
+ it_behaves_like :weakmap_each, :each_key
+end
diff --git a/spec/ruby/core/objectspace/weakmap/each_pair_spec.rb b/spec/ruby/core/objectspace/weakmap/each_pair_spec.rb
new file mode 100644
index 0000000000..ea29edbd2f
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/each_pair_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/members'
+require_relative 'shared/each'
+
+describe "ObjectSpace::WeakMap#each_pair" do
+ it_behaves_like :weakmap_members, -> map { a = []; map.each_pair{ |k,v| a << "#{k}#{v}" }; a }, %w[Ax By]
+end
+
+describe "ObjectSpace::WeakMap#each_key" do
+ it_behaves_like :weakmap_each, :each_pair
+end
diff --git a/spec/ruby/core/objectspace/weakmap/each_spec.rb b/spec/ruby/core/objectspace/weakmap/each_spec.rb
new file mode 100644
index 0000000000..46fcb66a6f
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/each_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/members'
+require_relative 'shared/each'
+
+describe "ObjectSpace::WeakMap#each" do
+ it_behaves_like :weakmap_members, -> map { a = []; map.each{ |k,v| a << "#{k}#{v}" }; a }, %w[Ax By]
+end
+
+describe "ObjectSpace::WeakMap#each_key" do
+ it_behaves_like :weakmap_each, :each
+end
diff --git a/spec/ruby/core/objectspace/weakmap/each_value_spec.rb b/spec/ruby/core/objectspace/weakmap/each_value_spec.rb
new file mode 100644
index 0000000000..65a1a7f6fe
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/each_value_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/members'
+require_relative 'shared/each'
+
+describe "ObjectSpace::WeakMap#each_value" do
+ it_behaves_like :weakmap_members, -> map { a = []; map.each_value{ |k| a << k }; a }, %w[x y]
+end
+
+describe "ObjectSpace::WeakMap#each_key" do
+ it_behaves_like :weakmap_each, :each_value
+end
diff --git a/spec/ruby/core/objectspace/weakmap/element_reference_spec.rb b/spec/ruby/core/objectspace/weakmap/element_reference_spec.rb
new file mode 100644
index 0000000000..cb3174cbfa
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/element_reference_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+describe "ObjectSpace::WeakMap#[]" do
+ it "is faithful to the map's content" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+ map[key1] = ref1
+ map[key1].should == ref1
+ map[key1] = ref1
+ map[key1].should == ref1
+ map[key2] = ref2
+ map[key1].should == ref1
+ map[key2].should == ref2
+ end
+
+ it "matches using identity semantics" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a a].map(&:upcase)
+ ref = "x"
+ map[key1] = ref
+ map[key2].should == nil
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/element_set_spec.rb b/spec/ruby/core/objectspace/weakmap/element_set_spec.rb
new file mode 100644
index 0000000000..8588877158
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/element_set_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../../spec_helper'
+
+describe "ObjectSpace::WeakMap#[]=" do
+ def should_accept(map, key, value)
+ (map[key] = value).should == value
+ map.should.key?(key)
+ map[key].should == value
+ end
+
+ it "is correct" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+ should_accept(map, key1, ref1)
+ should_accept(map, key1, ref1)
+ should_accept(map, key2, ref2)
+ map[key1].should == ref1
+ end
+
+ it "accepts primitive or frozen keys or values" do
+ map = ObjectSpace::WeakMap.new
+ x = Object.new
+ should_accept(map, true, x)
+ should_accept(map, false, x)
+ should_accept(map, nil, x)
+ should_accept(map, 42, x)
+ should_accept(map, :foo, x)
+
+ should_accept(map, x, true)
+ should_accept(map, x, false)
+ should_accept(map, x, 42)
+ should_accept(map, x, :foo)
+
+ y = Object.new.freeze
+ should_accept(map, x, y)
+ should_accept(map, y, x)
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/include_spec.rb b/spec/ruby/core/objectspace/weakmap/include_spec.rb
new file mode 100644
index 0000000000..54ca6b3030
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/include_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/include'
+
+describe "ObjectSpace::WeakMap#include?" do
+ it_behaves_like :weakmap_include?, :include?
+end
diff --git a/spec/ruby/core/objectspace/weakmap/inspect_spec.rb b/spec/ruby/core/objectspace/weakmap/inspect_spec.rb
new file mode 100644
index 0000000000..f064f6e3ea
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/inspect_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+
+describe "ObjectSpace::WeakMap#inspect" do
+ it "displays object pointers in output" do
+ map = ObjectSpace::WeakMap.new
+ # important to test with BasicObject (without Kernel) here to test edge cases
+ key1, key2 = [BasicObject.new, Object.new]
+ ref1, ref2 = [BasicObject.new, Object.new]
+ map.inspect.should =~ /\A\#<ObjectSpace::WeakMap:0x\h+>\z/
+ map[key1] = ref1
+ map.inspect.should =~ /\A\#<ObjectSpace::WeakMap:0x\h+: \#<BasicObject:0x\h+> => \#<BasicObject:0x\h+>>\z/
+ map[key1] = ref1
+ map.inspect.should =~ /\A\#<ObjectSpace::WeakMap:0x\h+: \#<BasicObject:0x\h+> => \#<BasicObject:0x\h+>>\z/
+ map[key2] = ref2
+
+ regexp1 = /\A\#<ObjectSpace::WeakMap:0x\h+: \#<BasicObject:0x\h+> => \#<BasicObject:0x\h+>, \#<Object:0x\h+> => \#<Object:0x\h+>>\z/
+ regexp2 = /\A\#<ObjectSpace::WeakMap:0x\h+: \#<Object:0x\h+> => \#<Object:0x\h+>, \#<BasicObject:0x\h+> => \#<BasicObject:0x\h+>>\z/
+ str = map.inspect
+ if str =~ regexp1
+ str.should =~ regexp1
+ else
+ str.should =~ regexp2
+ end
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/key_spec.rb b/spec/ruby/core/objectspace/weakmap/key_spec.rb
new file mode 100644
index 0000000000..999685ff95
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/key_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/include'
+
+describe "ObjectSpace::WeakMap#key?" do
+ it_behaves_like :weakmap_include?, :key?
+end
diff --git a/spec/ruby/core/objectspace/weakmap/keys_spec.rb b/spec/ruby/core/objectspace/weakmap/keys_spec.rb
new file mode 100644
index 0000000000..7b1494bdd7
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/keys_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/members'
+
+describe "ObjectSpace::WeakMap#keys" do
+ it_behaves_like :weakmap_members, -> map { map.keys }, %w[A B]
+end
diff --git a/spec/ruby/core/objectspace/weakmap/length_spec.rb b/spec/ruby/core/objectspace/weakmap/length_spec.rb
new file mode 100644
index 0000000000..3a935648b1
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/length_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/size'
+
+describe "ObjectSpace::WeakMap#length" do
+ it_behaves_like :weakmap_size, :length
+end
diff --git a/spec/ruby/core/objectspace/weakmap/member_spec.rb b/spec/ruby/core/objectspace/weakmap/member_spec.rb
new file mode 100644
index 0000000000..cefb190ce7
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/member_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/include'
+
+describe "ObjectSpace::WeakMap#member?" do
+ it_behaves_like :weakmap_include?, :member?
+end
diff --git a/spec/ruby/core/objectspace/weakmap/shared/each.rb b/spec/ruby/core/objectspace/weakmap/shared/each.rb
new file mode 100644
index 0000000000..3d43a19347
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/shared/each.rb
@@ -0,0 +1,10 @@
+describe :weakmap_each, shared: true do
+ it "must take a block, except when empty" do
+ map = ObjectSpace::WeakMap.new
+ key = "a".upcase
+ ref = "x"
+ map.send(@method).should == map
+ map[key] = ref
+ -> { map.send(@method) }.should raise_error(LocalJumpError)
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/shared/include.rb b/spec/ruby/core/objectspace/weakmap/shared/include.rb
new file mode 100644
index 0000000000..1770eeac8b
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/shared/include.rb
@@ -0,0 +1,30 @@
+describe :weakmap_include?, shared: true do
+ it "recognizes keys in use" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+
+ map[key1] = ref1
+ map.send(@method, key1).should == true
+ map[key1] = ref1
+ map.send(@method, key1).should == true
+ map[key2] = ref2
+ map.send(@method, key2).should == true
+ end
+
+ it "matches using identity semantics" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a a].map(&:upcase)
+ ref = "x"
+ map[key1] = ref
+ map.send(@method, key2).should == false
+ end
+
+ it "reports true if the pair exists and the value is nil" do
+ map = ObjectSpace::WeakMap.new
+ key = Object.new
+ map[key] = nil
+ map.size.should == 1
+ map.send(@method, key).should == true
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/shared/members.rb b/spec/ruby/core/objectspace/weakmap/shared/members.rb
new file mode 100644
index 0000000000..57226c8d7a
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/shared/members.rb
@@ -0,0 +1,14 @@
+describe :weakmap_members, shared: true do
+ it "is correct" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+ @method.call(map).should == []
+ map[key1] = ref1
+ @method.call(map).should == @object[0..0]
+ map[key1] = ref1
+ @method.call(map).should == @object[0..0]
+ map[key2] = ref2
+ @method.call(map).sort.should == @object
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/shared/size.rb b/spec/ruby/core/objectspace/weakmap/shared/size.rb
new file mode 100644
index 0000000000..1064f99d1b
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/shared/size.rb
@@ -0,0 +1,14 @@
+describe :weakmap_size, shared: true do
+ it "is correct" do
+ map = ObjectSpace::WeakMap.new
+ key1, key2 = %w[a b].map(&:upcase)
+ ref1, ref2 = %w[x y]
+ map.send(@method).should == 0
+ map[key1] = ref1
+ map.send(@method).should == 1
+ map[key1] = ref1
+ map.send(@method).should == 1
+ map[key2] = ref2
+ map.send(@method).should == 2
+ end
+end
diff --git a/spec/ruby/core/objectspace/weakmap/size_spec.rb b/spec/ruby/core/objectspace/weakmap/size_spec.rb
new file mode 100644
index 0000000000..1446abaa24
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/size_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/size'
+
+describe "ObjectSpace::WeakMap#size" do
+ it_behaves_like :weakmap_size, :size
+end
diff --git a/spec/ruby/core/objectspace/weakmap/values_spec.rb b/spec/ruby/core/objectspace/weakmap/values_spec.rb
new file mode 100644
index 0000000000..6f6f90d0ba
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap/values_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/members'
+
+describe "ObjectSpace::WeakMap#values" do
+ it_behaves_like :weakmap_members, -> map { map.values }, %w[x y]
+end
diff --git a/spec/ruby/core/objectspace/weakmap_spec.rb b/spec/ruby/core/objectspace/weakmap_spec.rb
new file mode 100644
index 0000000000..2f3f93c291
--- /dev/null
+++ b/spec/ruby/core/objectspace/weakmap_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "ObjectSpace::WeakMap" do
+
+ # Note that we can't really spec the most important aspect of this class: that entries get removed when the values
+ # become unreachable. This is because Ruby does not offer a way to reliable invoke GC (GC.start is not enough, neither
+ # on MRI or on alternative implementations).
+
+ it "includes Enumerable" do
+ ObjectSpace::WeakMap.include?(Enumerable).should == true
+ end
+end