1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
|
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
ruby_version_is "3.1" do
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
end
|