From 95e8c48dd3348503a8c7db5d0498894a1b676395 Mon Sep 17 00:00:00 2001 From: eregon Date: Sun, 7 May 2017 12:04:49 +0000 Subject: Add in-tree mspec and ruby/spec * For easier modifications of ruby/spec by MRI developers. * .gitignore: track changes under spec. * spec/mspec, spec/rubyspec: add in-tree mspec and ruby/spec. These files can therefore be updated like any other file in MRI. Instructions are provided in spec/README. [Feature #13156] [ruby-core:79246] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@58595 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- spec/rubyspec/core/objectspace/_id2ref_spec.rb | 25 +++ .../core/objectspace/add_finalizer_spec.rb | 5 + .../core/objectspace/call_finalizer_spec.rb | 5 + .../core/objectspace/count_objects_spec.rb | 5 + .../core/objectspace/define_finalizer_spec.rb | 101 +++++++++ spec/rubyspec/core/objectspace/each_object_spec.rb | 225 +++++++++++++++++++++ spec/rubyspec/core/objectspace/finalizers_spec.rb | 5 + spec/rubyspec/core/objectspace/fixtures/classes.rb | 64 ++++++ .../core/objectspace/garbage_collect_spec.rb | 22 ++ .../core/objectspace/remove_finalizer_spec.rb | 5 + .../core/objectspace/undefine_finalizer_spec.rb | 5 + 11 files changed, 467 insertions(+) create mode 100644 spec/rubyspec/core/objectspace/_id2ref_spec.rb create mode 100644 spec/rubyspec/core/objectspace/add_finalizer_spec.rb create mode 100644 spec/rubyspec/core/objectspace/call_finalizer_spec.rb create mode 100644 spec/rubyspec/core/objectspace/count_objects_spec.rb create mode 100644 spec/rubyspec/core/objectspace/define_finalizer_spec.rb create mode 100644 spec/rubyspec/core/objectspace/each_object_spec.rb create mode 100644 spec/rubyspec/core/objectspace/finalizers_spec.rb create mode 100644 spec/rubyspec/core/objectspace/fixtures/classes.rb create mode 100644 spec/rubyspec/core/objectspace/garbage_collect_spec.rb create mode 100644 spec/rubyspec/core/objectspace/remove_finalizer_spec.rb create mode 100644 spec/rubyspec/core/objectspace/undefine_finalizer_spec.rb (limited to 'spec/rubyspec/core/objectspace') diff --git a/spec/rubyspec/core/objectspace/_id2ref_spec.rb b/spec/rubyspec/core/objectspace/_id2ref_spec.rb new file mode 100644 index 0000000000..6e0b6e03fb --- /dev/null +++ b/spec/rubyspec/core/objectspace/_id2ref_spec.rb @@ -0,0 +1,25 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +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 a Fixnum by object_id" do + f = 1 + r = ObjectSpace._id2ref(f.object_id) + r.should == f + end + + it "retrieves a Symbol by object_id" do + s = :sym + r = ObjectSpace._id2ref(s.object_id) + r.should == s + end + + it 'raises RangeError when an object could not be found' do + proc { ObjectSpace._id2ref(1 << 60) }.should raise_error(RangeError) + end +end diff --git a/spec/rubyspec/core/objectspace/add_finalizer_spec.rb b/spec/rubyspec/core/objectspace/add_finalizer_spec.rb new file mode 100644 index 0000000000..2d529f2d6c --- /dev/null +++ b/spec/rubyspec/core/objectspace/add_finalizer_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "ObjectSpace.add_finalizer" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/rubyspec/core/objectspace/call_finalizer_spec.rb b/spec/rubyspec/core/objectspace/call_finalizer_spec.rb new file mode 100644 index 0000000000..bb7cee68a4 --- /dev/null +++ b/spec/rubyspec/core/objectspace/call_finalizer_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "ObjectSpace.call_finalizer" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/rubyspec/core/objectspace/count_objects_spec.rb b/spec/rubyspec/core/objectspace/count_objects_spec.rb new file mode 100644 index 0000000000..3e77d562a8 --- /dev/null +++ b/spec/rubyspec/core/objectspace/count_objects_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "ObjectSpace.count_objects" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/rubyspec/core/objectspace/define_finalizer_spec.rb b/spec/rubyspec/core/objectspace/define_finalizer_spec.rb new file mode 100644 index 0000000000..969e8b16b0 --- /dev/null +++ b/spec/rubyspec/core/objectspace/define_finalizer_spec.rb @@ -0,0 +1,101 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../fixtures/classes', __FILE__) + +# NOTE: A call to define_finalizer does not guarantee that the +# passed proc or callable will be called at any particular time. +# It is highly questionable whether these aspects of ObjectSpace +# should be spec'd at all. +describe "ObjectSpace.define_finalizer" do + it "raises an ArgumentError if the action does not respond to call" do + lambda { + ObjectSpace.define_finalizer("", mock("ObjectSpace.define_finalizer no #call")) + }.should raise_error(ArgumentError) + end + + it "accepts an object and a proc" do + handler = lambda { |obj| obj } + ObjectSpace.define_finalizer("garbage", handler).should == [0, handler] + end + + it "accepts an object and a callable" do + handler = mock("callable") + def handler.call(obj) end + ObjectSpace.define_finalizer("garbage", handler).should == [0, handler] + end + + it "raises ArgumentError trying to define a finalizer on a non-reference" do + lambda { + ObjectSpace.define_finalizer(:blah) { 1 } + }.should raise_error(ArgumentError) + end + + # see [ruby-core:24095] + with_feature :fork do + it "calls finalizer on process termination" do + rd, wr = IO.pipe + pid = Process.fork do + rd.close + handler = ObjectSpaceFixtures.scoped(wr) + obj = "Test" + ObjectSpace.define_finalizer(obj, handler) + exit 0 + end + + wr.close + begin + rd.read.should == "finalized" + ensure + rd.close + Process.wait pid + end + end + + it "calls finalizer at exit even if it is self-referencing" do + rd, wr = IO.pipe + pid = Process.fork do + rd.close + obj = "Test" + handler = Proc.new { wr.write "finalized"; wr.close } + ObjectSpace.define_finalizer(obj, handler) + exit 0 + end + + wr.close + begin + rd.read.should == "finalized" + ensure + rd.close + Process.wait pid + end + end + + # These specs are defined under the fork specs because there is no + # deterministic way to force finalizers to be run, except process exit, so + # we rely on that. + it "allows multiple finalizers with different 'callables' to be defined" do + rd1, wr1 = IO.pipe + rd2, wr2 = IO.pipe + + pid = Kernel::fork do + rd1.close + rd2.close + obj = mock("ObjectSpace.define_finalizer multiple") + + ObjectSpace.define_finalizer(obj, Proc.new { wr1.write "finalized1"; wr1.close }) + ObjectSpace.define_finalizer(obj, Proc.new { wr2.write "finalized2"; wr2.close }) + + exit 0 + end + + wr1.close + wr2.close + + rd1.read.should == "finalized1" + rd2.read.should == "finalized2" + + rd1.close + rd2.close + Process.wait pid + end + end +end diff --git a/spec/rubyspec/core/objectspace/each_object_spec.rb b/spec/rubyspec/core/objectspace/each_object_spec.rb new file mode 100644 index 0000000000..604b45cb34 --- /dev/null +++ b/spec/rubyspec/core/objectspace/each_object_spec.rb @@ -0,0 +1,225 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../fixtures/classes', __FILE__) + +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 + + ruby_version_is ""..."2.3" do + it "does not walk singleton classes" do + @sclass.should be_kind_of(@meta) + ObjectSpace.each_object(@meta).to_a.should_not include(@sclass) + end + end + + ruby_version_is "2.3" do + it "walks singleton classes" do + @sclass.should be_kind_of(@meta) + ObjectSpace.each_object(@meta).to_a.should include(@sclass) + end + 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 ] + + # singleton classes should be walked only on >= 2.3 + ruby_version_is "2.3" do + expected << c_sclass + c_sclass.should be_kind_of(a.singleton_class) + end + + 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/rubyspec/core/objectspace/finalizers_spec.rb b/spec/rubyspec/core/objectspace/finalizers_spec.rb new file mode 100644 index 0000000000..8d5f6cc029 --- /dev/null +++ b/spec/rubyspec/core/objectspace/finalizers_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "ObjectSpace.finalizers" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/rubyspec/core/objectspace/fixtures/classes.rb b/spec/rubyspec/core/objectspace/fixtures/classes.rb new file mode 100644 index 0000000000..9a4b0aa865 --- /dev/null +++ b/spec/rubyspec/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 = lambda { |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/rubyspec/core/objectspace/garbage_collect_spec.rb b/spec/rubyspec/core/objectspace/garbage_collect_spec.rb new file mode 100644 index 0000000000..aa7e233b25 --- /dev/null +++ b/spec/rubyspec/core/objectspace/garbage_collect_spec.rb @@ -0,0 +1,22 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "ObjectSpace.garbage_collect" do + + it "can be invoked without any exceptions" do + lambda { ObjectSpace.garbage_collect }.should_not raise_error + end + + it "doesn't accept any arguments" do + lambda { ObjectSpace.garbage_collect(1) }.should raise_error(ArgumentError) + end + + it "ignores the supplied block" do + lambda { 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/rubyspec/core/objectspace/remove_finalizer_spec.rb b/spec/rubyspec/core/objectspace/remove_finalizer_spec.rb new file mode 100644 index 0000000000..3053c31511 --- /dev/null +++ b/spec/rubyspec/core/objectspace/remove_finalizer_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "ObjectSpace.remove_finalizer" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/rubyspec/core/objectspace/undefine_finalizer_spec.rb b/spec/rubyspec/core/objectspace/undefine_finalizer_spec.rb new file mode 100644 index 0000000000..98f521db98 --- /dev/null +++ b/spec/rubyspec/core/objectspace/undefine_finalizer_spec.rb @@ -0,0 +1,5 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "ObjectSpace.undefine_finalizer" do + it "needs to be reviewed for spec completeness" +end -- cgit v1.2.3