fakefs / fakefs

A fake filesystem. Use it in your tests.
MIT License
1.04k stars 188 forks source link

How do I simulate an ELOOP? #459

Closed djberg96 closed 3 years ago

djberg96 commented 3 years ago

I've got a library where I specifically handle Errno::ELOOP situations, and have also have specs for them. Essentially, I try to stat the file, and if that generates an Errno::ELOOP, then I retry with lstat instead.

This is perfectly valid code, but if you uncomment the fakefs lines, it results in a SystemStackError:

#require 'fakefs'
require 'fileutils'

begin
  #FakeFS.activate!
  File.symlink('eloop0', 'eloop1')
  File.symlink('eloop1', 'eloop0')
  p File.lstat('eloop1')
ensure
  #FakeFS.deactivate!
  FileUtils.rm_rf('eloop1')
  FileUtils.rm_rf('eloop0')
end

Backtrace:

>ruby fakefs_eloop.rb
Traceback (most recent call last):
    9863: from fakefs_eloop.rb:8:in `<main>'
    9862: from /Users/dberger/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/fakefs-1.3.1/lib/fakefs/file.rb:286:in `lstat'
    9861: from /Users/dberger/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/fakefs-1.3.1/lib/fakefs/file.rb:286:in `new'
    9860: from /Users/dberger/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/fakefs-1.3.1/lib/fakefs/file.rb:348:in `initialize'
    9859: from /Users/dberger/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/fakefs-1.3.1/lib/fakefs/file.rb:45:in `exist?'
    9858: from /Users/dberger/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/fakefs-1.3.1/lib/fakefs/file.rb:152:in `symlink?'
    9857: from /Users/dberger/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/fakefs-1.3.1/lib/fakefs/file_system.rb:27:in `find'
    9856: from /Users/dberger/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/fakefs-1.3.1/lib/fakefs/file_system.rb:27:in `flat_map'
     ... 9851 levels...
       4: from /Users/dberger/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/fakefs-1.3.1/lib/fakefs/file_system.rb:108:in `normalize_path'
       3: from /Users/dberger/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/fakefs-1.3.1/lib/fakefs/file_system.rb:108:in `reduce'
       2: from /Users/dberger/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/fakefs-1.3.1/lib/fakefs/file_system.rb:108:in `each'
       1: from /Users/dberger/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/fakefs-1.3.1/lib/fakefs/file_system.rb:109:in `block in normalize_path'
/Users/dberger/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/fakefs-1.3.1/lib/fakefs/base.rb:12:in `Pathname': stack level too deep (SystemStackError)

Is there a proper way to do this with fakefs?

grosser commented 3 years ago

Sounds like a bug in fakefs, PR welcome if you can figure out how to do it properly 👍

grosser commented 3 years ago

In general Dir.mktmpdir can be useful to cover these edge-cases.

djberg96 commented 3 years ago

This one's been hard to track down, I probably need to use a GUI debugger. Anyway, I can tell you that within the FakeFS::FileSystem#normalize_path method, the base and part inside the reduce block are always "/" and ".", respectively.

https://github.com/fakefs/fakefs/blob/master/lib/fakefs/file_system.rb#L108-L110

djberg96 commented 3 years ago

In general Dir.mktmpdir can be useful to cover these edge-cases.

Indeed, that's what I was doing already.

In the end I couldn't solve this without breaking other things. I had to put a break in Pathname#plus, plus a couple other minor tweaks, but I couldn't do it in a general way. Maybe someone smarter than me can fix this someday. For now I just temporarily deactivate fakefs in the specs.