您如何测试 Ruby 析构函数是否会被调用?
How do you test whether a Ruby destructor will be called?
我创建了一个 class,我想将其挂在文件描述符上并在实例被 GC 编辑时将其关闭。
我创建了一个看起来像这样的 class:
class DataWriter
def initialize(file)
# open file
@file = File.open(file, 'wb')
# create destructor
ObjectSpace.define_finalizer(self, self.class.finalize(@file))
end
# write
def write(line)
@file.puts(line)
@file.flush
end
# close file descriptor, note, important that it is a class method
def self.finalize(file)
proc { file.close; p "file closed"; p file.inspect}
end
end
然后我尝试像这样测试析构函数方法:
RSpec.describe DataWriter do
context 'it should call its destructor' do
it 'calls the destructor' do
data_writer = DataWriter.new('/tmp/example.txt')
expect(DataWriter).to receive(:finalize)
data_writer = nil
GC.start
end
end
end
当 运行 此测试时,即使 "file closed" 与 file.inspect 一起打印,测试失败并输出以下内容:
1) DataWriter it should call its destructor calls the destructor
Failure/Error: expect(DataWriter).to receive(:finalize)
(DataWriter (class)).finalize(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/utils/data_writer_spec.rb:23:in `block (3 levels) in <top (required)>'
even though the "file closed" is printed along with the file.inspect, the test fails with the following output
我将您的代码放入一个文件中 运行。鉴于我收到的输出,在 rspec 退出之前,最终代码似乎没有被清理:
Failures:
F
1) DataWriter it should call its destructor calls the destructor
Failure/Error: expect(DataWriter).to receive(:finalize)
(DataWriter (class)).finalize(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
# /scratch/data_writer.rb:27:in `block (3 levels) in <top (required)>'
Finished in 0.01066 seconds (files took 0.16847 seconds to load)
1 example, 1 failure
Failed examples:
rspec /scratch/data_writer.rb:25 # DataWriter it should call its destructor calls the destructor
"file closed"
"#<File:/tmp/example.txt (closed)>"
至于为什么,我现在还不能说。 你在断言已经发生的事情,所以你的测试永远不会要过去了。您可以通过将测试更改为:
来观察这一点
it 'calls the destructor' do
expect(DataWriter).to receive(:finalize).and_call_original
data_writer = DataWriter.new('/tmp/example.txt')
data_writer = nil
GC.start
end
finalize
在 initialize
、returns 过程中被调用,并且再也不会被调用,所以你不能期望它在完成时被调用。这是在实例完成时调用的 proc。要检查这一点,让 proc 调用一个方法而不是自己完成工作。这通过了:
class DataWriter
# initialize and write same as above
def self.finalize(file)
proc { actually_finalize file }
end
def self.actually_finalize(file)
file.close
end
end
RSpec.describe DataWriter do
context 'it should call its destructor' do
it 'calls the destructor' do
data_writer = DataWriter.new('/tmp/example.txt')
expect(DataWriter).to receive(:actually_finalize)
data_writer = nil
GC.start
end
end
end
恕我直言,您不应该在 GC 运行 时完全依赖终结器 运行。他们最终会 运行。但也许只有当流程结束时。据我所知,这也取决于 Ruby 实现和 GC 实现。
1.8 的行为与 1.9+ 不同,Rubinius 和 JRuby 也可能不同。
确保资源的释放可以通过块来实现,这也将确保资源在不再需要时立即释放。
Ruby多个API风格相同:
File.open('thing.txt', 'wb') do |file| # file is passed to block
# do something with file
end # file will be closed when block ends
而不是这样做(正如你在要点中展示的那样)
(1..100_000).each do |i|
File.open(filename, 'ab') do |file|
file.puts "line: #{i}"
end
end
我会这样做:
File.open(filename, 'wb') do |file|
(1..100_000).each do |i|
file.puts "line: #{i}"
end
end
我在下面重写了我的工作解决方案,但我没有运行这段代码。
RSpec.describe DataWriter do
context 'it should call its destructor' do
it 'calls the destructor' do
# creating pipe for IPC to get result from child process
# after it garbaged
# http://ruby-doc.org/core-2.0.0/IO.html#method-c-pipe
rd, wr = IO.pipe
# forking
# https://ruby-doc.org/core-2.1.2/Process.html#method-c-fork
if fork
wr.close
called = rd.read
Process.wait
expect(called).to eq('/tmp/example.txt')
rd.close
else
rd.close
# overriding DataWriter.actually_finalize(file)
DataWriter.singleton_class.class_eval do
define_method(:actually_finalize) do |arg|
wr.write arg
wr.close
end
end
data_writer = DataWriter.new('/tmp/example.txt')
data_writer = nil
GC.start
end
end
end
end
主要是我发现 GC.start 调用在退出进程时执行实际工作。我已经尝试过块和线程,但在我的情况下 (ruby 2.2.4p230 @ Ubuntu x86_64) 它仅在进程完成时才有效。
我建议,可能存在从子进程获取结果的更好方法,但我使用了进程间通信 (IPC)。
而且我还没有在像 expect(DataWriter).to receive(:actually_finalize).with('/tmp/example.txt')
这样的形式的析构函数调用上构建 rspec 期望得到结果 - 我不知道为什么,但我想 [=26 创建的包装器=] 在调用 class.
的析构函数之前已被垃圾处理或侵犯
希望对您有所帮助!
我创建了一个 class,我想将其挂在文件描述符上并在实例被 GC 编辑时将其关闭。
我创建了一个看起来像这样的 class:
class DataWriter
def initialize(file)
# open file
@file = File.open(file, 'wb')
# create destructor
ObjectSpace.define_finalizer(self, self.class.finalize(@file))
end
# write
def write(line)
@file.puts(line)
@file.flush
end
# close file descriptor, note, important that it is a class method
def self.finalize(file)
proc { file.close; p "file closed"; p file.inspect}
end
end
然后我尝试像这样测试析构函数方法:
RSpec.describe DataWriter do
context 'it should call its destructor' do
it 'calls the destructor' do
data_writer = DataWriter.new('/tmp/example.txt')
expect(DataWriter).to receive(:finalize)
data_writer = nil
GC.start
end
end
end
当 运行 此测试时,即使 "file closed" 与 file.inspect 一起打印,测试失败并输出以下内容:
1) DataWriter it should call its destructor calls the destructor
Failure/Error: expect(DataWriter).to receive(:finalize)
(DataWriter (class)).finalize(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/utils/data_writer_spec.rb:23:in `block (3 levels) in <top (required)>'
even though the "file closed" is printed along with the file.inspect, the test fails with the following output
我将您的代码放入一个文件中 运行。鉴于我收到的输出,在 rspec 退出之前,最终代码似乎没有被清理:
Failures:
F
1) DataWriter it should call its destructor calls the destructor
Failure/Error: expect(DataWriter).to receive(:finalize)
(DataWriter (class)).finalize(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
# /scratch/data_writer.rb:27:in `block (3 levels) in <top (required)>'
Finished in 0.01066 seconds (files took 0.16847 seconds to load)
1 example, 1 failure
Failed examples:
rspec /scratch/data_writer.rb:25 # DataWriter it should call its destructor calls the destructor
"file closed"
"#<File:/tmp/example.txt (closed)>"
至于为什么,我现在还不能说。
it 'calls the destructor' do
expect(DataWriter).to receive(:finalize).and_call_original
data_writer = DataWriter.new('/tmp/example.txt')
data_writer = nil
GC.start
end
finalize
在 initialize
、returns 过程中被调用,并且再也不会被调用,所以你不能期望它在完成时被调用。这是在实例完成时调用的 proc。要检查这一点,让 proc 调用一个方法而不是自己完成工作。这通过了:
class DataWriter
# initialize and write same as above
def self.finalize(file)
proc { actually_finalize file }
end
def self.actually_finalize(file)
file.close
end
end
RSpec.describe DataWriter do
context 'it should call its destructor' do
it 'calls the destructor' do
data_writer = DataWriter.new('/tmp/example.txt')
expect(DataWriter).to receive(:actually_finalize)
data_writer = nil
GC.start
end
end
end
恕我直言,您不应该在 GC 运行 时完全依赖终结器 运行。他们最终会 运行。但也许只有当流程结束时。据我所知,这也取决于 Ruby 实现和 GC 实现。 1.8 的行为与 1.9+ 不同,Rubinius 和 JRuby 也可能不同。
确保资源的释放可以通过块来实现,这也将确保资源在不再需要时立即释放。
Ruby多个API风格相同:
File.open('thing.txt', 'wb') do |file| # file is passed to block
# do something with file
end # file will be closed when block ends
而不是这样做(正如你在要点中展示的那样)
(1..100_000).each do |i|
File.open(filename, 'ab') do |file|
file.puts "line: #{i}"
end
end
我会这样做:
File.open(filename, 'wb') do |file|
(1..100_000).each do |i|
file.puts "line: #{i}"
end
end
我在下面重写了我的工作解决方案,但我没有运行这段代码。
RSpec.describe DataWriter do
context 'it should call its destructor' do
it 'calls the destructor' do
# creating pipe for IPC to get result from child process
# after it garbaged
# http://ruby-doc.org/core-2.0.0/IO.html#method-c-pipe
rd, wr = IO.pipe
# forking
# https://ruby-doc.org/core-2.1.2/Process.html#method-c-fork
if fork
wr.close
called = rd.read
Process.wait
expect(called).to eq('/tmp/example.txt')
rd.close
else
rd.close
# overriding DataWriter.actually_finalize(file)
DataWriter.singleton_class.class_eval do
define_method(:actually_finalize) do |arg|
wr.write arg
wr.close
end
end
data_writer = DataWriter.new('/tmp/example.txt')
data_writer = nil
GC.start
end
end
end
end
主要是我发现 GC.start 调用在退出进程时执行实际工作。我已经尝试过块和线程,但在我的情况下 (ruby 2.2.4p230 @ Ubuntu x86_64) 它仅在进程完成时才有效。
我建议,可能存在从子进程获取结果的更好方法,但我使用了进程间通信 (IPC)。
而且我还没有在像 expect(DataWriter).to receive(:actually_finalize).with('/tmp/example.txt')
这样的形式的析构函数调用上构建 rspec 期望得到结果 - 我不知道为什么,但我想 [=26 创建的包装器=] 在调用 class.
希望对您有所帮助!