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
193
194
|
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
ruby_version_is "3.0" do
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
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
|