你如何测试是否会调用Ruby析构函数?

时间:2016-08-30 01:57:23

标签: ruby rspec mocking destructor

我创建了一个类,我希望将其挂在文件描述符上,并在实例进行GC编辑时将其关闭。

我创建了一个看起来像这样的类:

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.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)>'

4 个答案:

答案 0 :(得分:4)

finalize中调用

initialize,返回proc,并且永远不再调用它,因此您不能指望在结束时调用它。这是实例最终确定时调用的 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

答案 1 :(得分:1)

  

即使“file closed”与file.inspect一起打印,测试也会失败并显示以下输出

我把你的代码扔进一个文件并运行它。看来,直到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)>"

关于它的原因,我现在无法分辨。 Dave is right你在已经发生的事情上断言,所以你的测试永远不会过去。您可以通过将测试更改为:

来观察此情况
 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

答案 2 :(得分:1)

恕我直言,你不应该依赖终结器在GC运行时完全运行。他们最终会跑。但也许只有在流程结束时。据我所知,这也取决于Ruby实现和GC实现。 1.8具有与1.9+不同的行为,Rubinius和JRuby也可能不同。

确保资源的释放可以通过一个块来实现,该块也会在不再需要的时候尽快释放资源。

多个API在Ruby中具有相同的样式:

(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

我这样做:

LASSSST = IP.Cells(Rows.Count, "B").End(xlUp).Rows.Count

答案 3 :(得分:0)

我在下面重写了我的工作解决方案,我没有运行此代码。

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期望的结果 - 我不知道为什么,但我想由Rspec创建的包装器之前已经被包含或侵犯了调用类的析构函数。

希望这有帮助!