Added fakefs/safe which is a bit less intrusive.
This commit is contained in:
parent
bc5dbd3a01
commit
3f4de75254
2
Rakefile
2
Rakefile
@ -1,3 +1,3 @@
|
|||||||
task :default do
|
task :default do
|
||||||
exec "ruby test/fakefs_test.rb"
|
Dir['test/*_test.rb'].each { |file| require file }
|
||||||
end
|
end
|
||||||
|
443
lib/fakefs.rb
443
lib/fakefs.rb
@ -1,447 +1,6 @@
|
|||||||
require 'fileutils'
|
require 'fileutils'
|
||||||
require 'pathname'
|
require 'pathname'
|
||||||
|
require 'fakefs/safe'
|
||||||
RealFile = File
|
|
||||||
RealFileUtils = FileUtils
|
|
||||||
RealDir = Dir
|
|
||||||
RealFileUtils::Dir = RealDir
|
|
||||||
RealFileUtils::File = RealFile
|
|
||||||
|
|
||||||
module FakeFS
|
|
||||||
module FileUtils
|
|
||||||
extend self
|
|
||||||
|
|
||||||
def mkdir_p(path)
|
|
||||||
FileSystem.add(path, MockDir.new)
|
|
||||||
end
|
|
||||||
|
|
||||||
def rm(path)
|
|
||||||
FileSystem.delete(path)
|
|
||||||
end
|
|
||||||
alias_method :rm_rf, :rm
|
|
||||||
alias_method :rm_r, :rm
|
|
||||||
|
|
||||||
def ln_s(target, path)
|
|
||||||
raise Errno::EEXIST, path if FileSystem.find(path)
|
|
||||||
FileSystem.add(path, MockSymlink.new(target))
|
|
||||||
end
|
|
||||||
|
|
||||||
def cp(src, dest)
|
|
||||||
dst_file = FileSystem.find(dest)
|
|
||||||
src_file = FileSystem.find(src)
|
|
||||||
|
|
||||||
if !src_file
|
|
||||||
raise Errno::ENOENT, src
|
|
||||||
end
|
|
||||||
|
|
||||||
if File.directory? src_file
|
|
||||||
raise Errno::EISDIR, src
|
|
||||||
end
|
|
||||||
|
|
||||||
if dst_file and File.directory?(dst_file)
|
|
||||||
FileSystem.add(File.join(dest, src), src_file.entry.clone(dst_file))
|
|
||||||
else
|
|
||||||
FileSystem.delete(dest)
|
|
||||||
FileSystem.add(dest, src_file.entry.clone)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def cp_r(src, dest)
|
|
||||||
# This error sucks, but it conforms to the original Ruby
|
|
||||||
# method.
|
|
||||||
raise "unknown file type: #{src}" unless dir = FileSystem.find(src)
|
|
||||||
|
|
||||||
new_dir = FileSystem.find(dest)
|
|
||||||
|
|
||||||
if new_dir && !File.directory?(dest)
|
|
||||||
raise Errno::EEXIST, dest
|
|
||||||
end
|
|
||||||
|
|
||||||
if !new_dir && !FileSystem.find(dest+'/../')
|
|
||||||
raise Errno::ENOENT, dest
|
|
||||||
end
|
|
||||||
|
|
||||||
# This last bit is a total abuse and should be thought hard
|
|
||||||
# about and cleaned up.
|
|
||||||
if new_dir
|
|
||||||
if src[-2..-1] == '/.'
|
|
||||||
dir.values.each{|f| new_dir[f.name] = f.clone(new_dir) }
|
|
||||||
else
|
|
||||||
new_dir[dir.name] = dir.entry.clone(new_dir)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
FileSystem.add(dest, dir.entry.clone)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def mv(src, dest)
|
|
||||||
if target = FileSystem.find(src)
|
|
||||||
FileSystem.add(dest, target.entry.clone)
|
|
||||||
FileSystem.delete(src)
|
|
||||||
else
|
|
||||||
raise Errno::ENOENT, src
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def chown(user, group, list, options={})
|
|
||||||
list = Array(list)
|
|
||||||
list.each do |f|
|
|
||||||
unless File.exists?(f)
|
|
||||||
raise Errno::ENOENT, f
|
|
||||||
end
|
|
||||||
end
|
|
||||||
list
|
|
||||||
end
|
|
||||||
|
|
||||||
def chown_R(user, group, list, options={})
|
|
||||||
chown(user, group, list, options={})
|
|
||||||
end
|
|
||||||
|
|
||||||
def touch(list, options={})
|
|
||||||
Array(list).each do |f|
|
|
||||||
directory = File.dirname(f)
|
|
||||||
# FIXME this explicit check for '.' shouldn't need to happen
|
|
||||||
if File.exists?(directory) || directory == '.'
|
|
||||||
FileSystem.add(f, MockFile.new)
|
|
||||||
else
|
|
||||||
raise Errno::ENOENT, f
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def cd(dir)
|
|
||||||
FileSystem.chdir(dir)
|
|
||||||
end
|
|
||||||
alias_method :chdir, :cd
|
|
||||||
end
|
|
||||||
|
|
||||||
class File
|
|
||||||
PATH_SEPARATOR = '/'
|
|
||||||
|
|
||||||
def self.join(*parts)
|
|
||||||
parts * PATH_SEPARATOR
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.exist?(path)
|
|
||||||
!!FileSystem.find(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
class << self
|
|
||||||
alias_method :exists?, :exist?
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.directory?(path)
|
|
||||||
if path.respond_to? :entry
|
|
||||||
path.entry.is_a? MockDir
|
|
||||||
else
|
|
||||||
result = FileSystem.find(path)
|
|
||||||
result ? result.entry.is_a?(MockDir) : false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.symlink?(path)
|
|
||||||
if path.respond_to? :entry
|
|
||||||
path.is_a? MockSymlink
|
|
||||||
else
|
|
||||||
FileSystem.find(path).is_a? MockSymlink
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.file?(path)
|
|
||||||
if path.respond_to? :entry
|
|
||||||
path.entry.is_a? MockFile
|
|
||||||
else
|
|
||||||
result = FileSystem.find(path)
|
|
||||||
result ? result.entry.is_a?(MockFile) : false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.expand_path(*args)
|
|
||||||
RealFile.expand_path(*args)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.basename(*args)
|
|
||||||
RealFile.basename(*args)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.dirname(path)
|
|
||||||
RealFile.dirname(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.readlink(path)
|
|
||||||
symlink = FileSystem.find(path)
|
|
||||||
FileSystem.find(symlink.target).to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.open(path, mode='r')
|
|
||||||
if block_given?
|
|
||||||
yield new(path, mode)
|
|
||||||
else
|
|
||||||
new(path, mode)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.read(path)
|
|
||||||
file = new(path)
|
|
||||||
if file.exists?
|
|
||||||
file.read
|
|
||||||
else
|
|
||||||
raise Errno::ENOENT
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.readlines(path)
|
|
||||||
read(path).split("\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :path
|
|
||||||
def initialize(path, mode = nil)
|
|
||||||
@path = path
|
|
||||||
@mode = mode
|
|
||||||
@file = FileSystem.find(path)
|
|
||||||
@open = true
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
@open = false
|
|
||||||
end
|
|
||||||
|
|
||||||
def read
|
|
||||||
raise IOError.new('closed stream') unless @open
|
|
||||||
@file.content
|
|
||||||
end
|
|
||||||
|
|
||||||
def exists?
|
|
||||||
@file
|
|
||||||
end
|
|
||||||
|
|
||||||
def puts(content)
|
|
||||||
write(content + "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
def write(content)
|
|
||||||
raise IOError.new('closed stream') unless @open
|
|
||||||
|
|
||||||
if !File.exists?(@path)
|
|
||||||
@file = FileSystem.add(path, MockFile.new)
|
|
||||||
end
|
|
||||||
|
|
||||||
@file.content += content
|
|
||||||
end
|
|
||||||
alias_method :print, :write
|
|
||||||
alias_method :<<, :write
|
|
||||||
|
|
||||||
def flush; self; end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Dir
|
|
||||||
def self.glob(pattern)
|
|
||||||
if pattern[-1,1] == '*'
|
|
||||||
blk = proc { |entry| entry.to_s }
|
|
||||||
else
|
|
||||||
blk = proc { |entry| entry[1].parent.to_s }
|
|
||||||
end
|
|
||||||
(FileSystem.find(pattern) || []).map(&blk).uniq.sort
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.[](pattern)
|
|
||||||
glob(pattern)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.chdir(dir, &blk)
|
|
||||||
FileSystem.chdir(dir, &blk)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module FileSystem
|
|
||||||
extend self
|
|
||||||
|
|
||||||
def dir_levels
|
|
||||||
@dir_levels ||= []
|
|
||||||
end
|
|
||||||
|
|
||||||
def fs
|
|
||||||
@fs ||= MockDir.new('.')
|
|
||||||
end
|
|
||||||
|
|
||||||
def clear
|
|
||||||
@dir_levels = nil
|
|
||||||
@fs = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def files
|
|
||||||
fs.values
|
|
||||||
end
|
|
||||||
|
|
||||||
def find(path)
|
|
||||||
parts = path_parts(normalize_path(path))
|
|
||||||
|
|
||||||
target = parts[0...-1].inject(fs) do |dir, part|
|
|
||||||
dir[part] || {}
|
|
||||||
end
|
|
||||||
|
|
||||||
case parts.last
|
|
||||||
when '*'
|
|
||||||
target.values
|
|
||||||
else
|
|
||||||
target[parts.last]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def add(path, object=MockDir.new)
|
|
||||||
parts = path_parts(normalize_path(path))
|
|
||||||
|
|
||||||
d = parts[0...-1].inject(fs) do |dir, part|
|
|
||||||
dir[part] ||= MockDir.new(part, dir)
|
|
||||||
end
|
|
||||||
|
|
||||||
object.name = parts.last
|
|
||||||
object.parent = d
|
|
||||||
d[parts.last] ||= object
|
|
||||||
end
|
|
||||||
|
|
||||||
# copies directories and files from the real filesystem
|
|
||||||
# into our fake one
|
|
||||||
def clone(path)
|
|
||||||
path = File.expand_path(path)
|
|
||||||
pattern = File.join(path, '**', '*')
|
|
||||||
files = RealFile.file?(path) ? [path] : [path] + RealDir.glob(pattern, RealFile::FNM_DOTMATCH)
|
|
||||||
|
|
||||||
files.each do |f|
|
|
||||||
if RealFile.file?(f)
|
|
||||||
FileUtils.mkdir_p(File.dirname(f))
|
|
||||||
File.open(f, 'w') do |g|
|
|
||||||
g.print RealFile.open(f){|h| h.read }
|
|
||||||
end
|
|
||||||
elsif RealFile.directory?(f)
|
|
||||||
FileUtils.mkdir_p(f)
|
|
||||||
elsif RealFile.symlink?(f)
|
|
||||||
FileUtils.ln_s()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(path)
|
|
||||||
if dir = FileSystem.find(path)
|
|
||||||
dir.parent.delete(dir.name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def chdir(dir, &blk)
|
|
||||||
new_dir = find(dir)
|
|
||||||
dir_levels.push dir if blk
|
|
||||||
|
|
||||||
raise Errno::ENOENT, dir unless new_dir
|
|
||||||
|
|
||||||
dir_levels.push dir if !blk
|
|
||||||
blk.call if blk
|
|
||||||
ensure
|
|
||||||
dir_levels.pop if blk
|
|
||||||
end
|
|
||||||
|
|
||||||
def path_parts(path)
|
|
||||||
path.split(File::PATH_SEPARATOR).reject { |part| part.empty? }
|
|
||||||
end
|
|
||||||
|
|
||||||
def normalize_path(path)
|
|
||||||
if Pathname.new(path).absolute?
|
|
||||||
File.expand_path(path)
|
|
||||||
else
|
|
||||||
parts = dir_levels + [path]
|
|
||||||
File.expand_path(File.join(*parts))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_dir
|
|
||||||
find(normalize_path('.'))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class MockFile
|
|
||||||
attr_accessor :name, :parent, :content
|
|
||||||
|
|
||||||
def initialize(name = nil, parent = nil)
|
|
||||||
@name = name
|
|
||||||
@parent = parent
|
|
||||||
@content = ''
|
|
||||||
end
|
|
||||||
|
|
||||||
def clone(parent = nil)
|
|
||||||
clone = super()
|
|
||||||
clone.parent = parent if parent
|
|
||||||
clone
|
|
||||||
end
|
|
||||||
|
|
||||||
def entry
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"(MockFile name:#{name.inspect} parent:#{parent.to_s.inspect} size:#{content.size})"
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
File.join(parent.to_s, name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class MockDir < Hash
|
|
||||||
attr_accessor :name, :parent
|
|
||||||
|
|
||||||
def initialize(name = nil, parent = nil)
|
|
||||||
@name = name
|
|
||||||
@parent = parent
|
|
||||||
end
|
|
||||||
|
|
||||||
def entry
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"(MockDir name:#{name.inspect} parent:#{parent.to_s.inspect} size:#{size})"
|
|
||||||
end
|
|
||||||
|
|
||||||
def clone(parent = nil)
|
|
||||||
clone = Marshal.load(Marshal.dump(self))
|
|
||||||
clone.each do |key, value|
|
|
||||||
value.parent = clone
|
|
||||||
end
|
|
||||||
clone.parent = parent if parent
|
|
||||||
clone
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
if parent && parent.to_s != '.'
|
|
||||||
File.join(parent.to_s, name)
|
|
||||||
elsif parent && parent.to_s == '.'
|
|
||||||
"#{File::PATH_SEPARATOR}#{name}"
|
|
||||||
else
|
|
||||||
name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class MockSymlink
|
|
||||||
attr_accessor :name, :target
|
|
||||||
alias_method :to_s, :name
|
|
||||||
|
|
||||||
def initialize(target)
|
|
||||||
@target = target
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"symlink(#{target.split('/').last})"
|
|
||||||
end
|
|
||||||
|
|
||||||
def entry
|
|
||||||
FileSystem.find(target)
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_missing(*args, &block)
|
|
||||||
entry.send(*args, &block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Object.class_eval do
|
Object.class_eval do
|
||||||
remove_const(:Dir)
|
remove_const(:Dir)
|
||||||
|
470
lib/fakefs/safe.rb
Normal file
470
lib/fakefs/safe.rb
Normal file
@ -0,0 +1,470 @@
|
|||||||
|
RealFile = File
|
||||||
|
RealFileUtils = FileUtils
|
||||||
|
RealDir = Dir
|
||||||
|
RealFileUtils::Dir = RealDir
|
||||||
|
RealFileUtils::File = RealFile
|
||||||
|
|
||||||
|
module FakeFS
|
||||||
|
def self.activate!
|
||||||
|
Object.class_eval do
|
||||||
|
remove_const(:Dir)
|
||||||
|
remove_const(:File)
|
||||||
|
remove_const(:FileUtils)
|
||||||
|
const_set(:Dir, FakeFS::Dir)
|
||||||
|
const_set(:File, FakeFS::File)
|
||||||
|
const_set(:FileUtils, FakeFS::FileUtils)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.deactivate!
|
||||||
|
Object.class_eval do
|
||||||
|
remove_const(:Dir)
|
||||||
|
remove_const(:File)
|
||||||
|
remove_const(:FileUtils)
|
||||||
|
const_set(:Dir, RealDir)
|
||||||
|
const_set(:File, RealFile)
|
||||||
|
const_set(:FileUtils, RealFileUtils)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module FileUtils
|
||||||
|
extend self
|
||||||
|
|
||||||
|
def mkdir_p(path)
|
||||||
|
FileSystem.add(path, MockDir.new)
|
||||||
|
end
|
||||||
|
|
||||||
|
def rm(path)
|
||||||
|
FileSystem.delete(path)
|
||||||
|
end
|
||||||
|
alias_method :rm_rf, :rm
|
||||||
|
alias_method :rm_r, :rm
|
||||||
|
|
||||||
|
def ln_s(target, path)
|
||||||
|
raise Errno::EEXIST, path if FileSystem.find(path)
|
||||||
|
FileSystem.add(path, MockSymlink.new(target))
|
||||||
|
end
|
||||||
|
|
||||||
|
def cp(src, dest)
|
||||||
|
dst_file = FileSystem.find(dest)
|
||||||
|
src_file = FileSystem.find(src)
|
||||||
|
|
||||||
|
if !src_file
|
||||||
|
raise Errno::ENOENT, src
|
||||||
|
end
|
||||||
|
|
||||||
|
if File.directory? src_file
|
||||||
|
raise Errno::EISDIR, src
|
||||||
|
end
|
||||||
|
|
||||||
|
if dst_file and File.directory?(dst_file)
|
||||||
|
FileSystem.add(File.join(dest, src), src_file.entry.clone(dst_file))
|
||||||
|
else
|
||||||
|
FileSystem.delete(dest)
|
||||||
|
FileSystem.add(dest, src_file.entry.clone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cp_r(src, dest)
|
||||||
|
# This error sucks, but it conforms to the original Ruby
|
||||||
|
# method.
|
||||||
|
raise "unknown file type: #{src}" unless dir = FileSystem.find(src)
|
||||||
|
|
||||||
|
new_dir = FileSystem.find(dest)
|
||||||
|
|
||||||
|
if new_dir && !File.directory?(dest)
|
||||||
|
raise Errno::EEXIST, dest
|
||||||
|
end
|
||||||
|
|
||||||
|
if !new_dir && !FileSystem.find(dest+'/../')
|
||||||
|
raise Errno::ENOENT, dest
|
||||||
|
end
|
||||||
|
|
||||||
|
# This last bit is a total abuse and should be thought hard
|
||||||
|
# about and cleaned up.
|
||||||
|
if new_dir
|
||||||
|
if src[-2..-1] == '/.'
|
||||||
|
dir.values.each{|f| new_dir[f.name] = f.clone(new_dir) }
|
||||||
|
else
|
||||||
|
new_dir[dir.name] = dir.entry.clone(new_dir)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
FileSystem.add(dest, dir.entry.clone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mv(src, dest)
|
||||||
|
if target = FileSystem.find(src)
|
||||||
|
FileSystem.add(dest, target.entry.clone)
|
||||||
|
FileSystem.delete(src)
|
||||||
|
else
|
||||||
|
raise Errno::ENOENT, src
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def chown(user, group, list, options={})
|
||||||
|
list = Array(list)
|
||||||
|
list.each do |f|
|
||||||
|
unless File.exists?(f)
|
||||||
|
raise Errno::ENOENT, f
|
||||||
|
end
|
||||||
|
end
|
||||||
|
list
|
||||||
|
end
|
||||||
|
|
||||||
|
def chown_R(user, group, list, options={})
|
||||||
|
chown(user, group, list, options={})
|
||||||
|
end
|
||||||
|
|
||||||
|
def touch(list, options={})
|
||||||
|
Array(list).each do |f|
|
||||||
|
directory = File.dirname(f)
|
||||||
|
# FIXME this explicit check for '.' shouldn't need to happen
|
||||||
|
if File.exists?(directory) || directory == '.'
|
||||||
|
FileSystem.add(f, MockFile.new)
|
||||||
|
else
|
||||||
|
raise Errno::ENOENT, f
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cd(dir)
|
||||||
|
FileSystem.chdir(dir)
|
||||||
|
end
|
||||||
|
alias_method :chdir, :cd
|
||||||
|
end
|
||||||
|
|
||||||
|
class File
|
||||||
|
PATH_SEPARATOR = '/'
|
||||||
|
|
||||||
|
def self.join(*parts)
|
||||||
|
parts * PATH_SEPARATOR
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.exist?(path)
|
||||||
|
!!FileSystem.find(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
alias_method :exists?, :exist?
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.directory?(path)
|
||||||
|
if path.respond_to? :entry
|
||||||
|
path.entry.is_a? MockDir
|
||||||
|
else
|
||||||
|
result = FileSystem.find(path)
|
||||||
|
result ? result.entry.is_a?(MockDir) : false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.symlink?(path)
|
||||||
|
if path.respond_to? :entry
|
||||||
|
path.is_a? MockSymlink
|
||||||
|
else
|
||||||
|
FileSystem.find(path).is_a? MockSymlink
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.file?(path)
|
||||||
|
if path.respond_to? :entry
|
||||||
|
path.entry.is_a? MockFile
|
||||||
|
else
|
||||||
|
result = FileSystem.find(path)
|
||||||
|
result ? result.entry.is_a?(MockFile) : false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.expand_path(*args)
|
||||||
|
RealFile.expand_path(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.basename(*args)
|
||||||
|
RealFile.basename(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.dirname(path)
|
||||||
|
RealFile.dirname(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.readlink(path)
|
||||||
|
symlink = FileSystem.find(path)
|
||||||
|
FileSystem.find(symlink.target).to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.open(path, mode='r')
|
||||||
|
if block_given?
|
||||||
|
yield new(path, mode)
|
||||||
|
else
|
||||||
|
new(path, mode)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.read(path)
|
||||||
|
file = new(path)
|
||||||
|
if file.exists?
|
||||||
|
file.read
|
||||||
|
else
|
||||||
|
raise Errno::ENOENT
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.readlines(path)
|
||||||
|
read(path).split("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :path
|
||||||
|
def initialize(path, mode = nil)
|
||||||
|
@path = path
|
||||||
|
@mode = mode
|
||||||
|
@file = FileSystem.find(path)
|
||||||
|
@open = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
@open = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def read
|
||||||
|
raise IOError.new('closed stream') unless @open
|
||||||
|
@file.content
|
||||||
|
end
|
||||||
|
|
||||||
|
def exists?
|
||||||
|
@file
|
||||||
|
end
|
||||||
|
|
||||||
|
def puts(content)
|
||||||
|
write(content + "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def write(content)
|
||||||
|
raise IOError.new('closed stream') unless @open
|
||||||
|
|
||||||
|
if !File.exists?(@path)
|
||||||
|
@file = FileSystem.add(path, MockFile.new)
|
||||||
|
end
|
||||||
|
|
||||||
|
@file.content += content
|
||||||
|
end
|
||||||
|
alias_method :print, :write
|
||||||
|
alias_method :<<, :write
|
||||||
|
|
||||||
|
def flush; self; end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Dir
|
||||||
|
def self.glob(pattern)
|
||||||
|
if pattern[-1,1] == '*'
|
||||||
|
blk = proc { |entry| entry.to_s }
|
||||||
|
else
|
||||||
|
blk = proc { |entry| entry[1].parent.to_s }
|
||||||
|
end
|
||||||
|
(FileSystem.find(pattern) || []).map(&blk).uniq.sort
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.[](pattern)
|
||||||
|
glob(pattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.chdir(dir, &blk)
|
||||||
|
FileSystem.chdir(dir, &blk)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module FileSystem
|
||||||
|
extend self
|
||||||
|
|
||||||
|
def dir_levels
|
||||||
|
@dir_levels ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def fs
|
||||||
|
@fs ||= MockDir.new('.')
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear
|
||||||
|
@dir_levels = nil
|
||||||
|
@fs = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def files
|
||||||
|
fs.values
|
||||||
|
end
|
||||||
|
|
||||||
|
def find(path)
|
||||||
|
parts = path_parts(normalize_path(path))
|
||||||
|
|
||||||
|
target = parts[0...-1].inject(fs) do |dir, part|
|
||||||
|
dir[part] || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
case parts.last
|
||||||
|
when '*'
|
||||||
|
target.values
|
||||||
|
else
|
||||||
|
target[parts.last]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add(path, object=MockDir.new)
|
||||||
|
parts = path_parts(normalize_path(path))
|
||||||
|
|
||||||
|
d = parts[0...-1].inject(fs) do |dir, part|
|
||||||
|
dir[part] ||= MockDir.new(part, dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
object.name = parts.last
|
||||||
|
object.parent = d
|
||||||
|
d[parts.last] ||= object
|
||||||
|
end
|
||||||
|
|
||||||
|
# copies directories and files from the real filesystem
|
||||||
|
# into our fake one
|
||||||
|
def clone(path)
|
||||||
|
path = File.expand_path(path)
|
||||||
|
pattern = File.join(path, '**', '*')
|
||||||
|
files = RealFile.file?(path) ? [path] : [path] + RealDir.glob(pattern, RealFile::FNM_DOTMATCH)
|
||||||
|
|
||||||
|
files.each do |f|
|
||||||
|
if RealFile.file?(f)
|
||||||
|
FileUtils.mkdir_p(File.dirname(f))
|
||||||
|
File.open(f, 'w') do |g|
|
||||||
|
g.print RealFile.open(f){|h| h.read }
|
||||||
|
end
|
||||||
|
elsif RealFile.directory?(f)
|
||||||
|
FileUtils.mkdir_p(f)
|
||||||
|
elsif RealFile.symlink?(f)
|
||||||
|
FileUtils.ln_s()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(path)
|
||||||
|
if dir = FileSystem.find(path)
|
||||||
|
dir.parent.delete(dir.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def chdir(dir, &blk)
|
||||||
|
new_dir = find(dir)
|
||||||
|
dir_levels.push dir if blk
|
||||||
|
|
||||||
|
raise Errno::ENOENT, dir unless new_dir
|
||||||
|
|
||||||
|
dir_levels.push dir if !blk
|
||||||
|
blk.call if blk
|
||||||
|
ensure
|
||||||
|
dir_levels.pop if blk
|
||||||
|
end
|
||||||
|
|
||||||
|
def path_parts(path)
|
||||||
|
path.split(File::PATH_SEPARATOR).reject { |part| part.empty? }
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_path(path)
|
||||||
|
if Pathname.new(path).absolute?
|
||||||
|
File.expand_path(path)
|
||||||
|
else
|
||||||
|
parts = dir_levels + [path]
|
||||||
|
File.expand_path(File.join(*parts))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_dir
|
||||||
|
find(normalize_path('.'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class MockFile
|
||||||
|
attr_accessor :name, :parent, :content
|
||||||
|
|
||||||
|
def initialize(name = nil, parent = nil)
|
||||||
|
@name = name
|
||||||
|
@parent = parent
|
||||||
|
@content = ''
|
||||||
|
end
|
||||||
|
|
||||||
|
def clone(parent = nil)
|
||||||
|
clone = super()
|
||||||
|
clone.parent = parent if parent
|
||||||
|
clone
|
||||||
|
end
|
||||||
|
|
||||||
|
def entry
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
"(MockFile name:#{name.inspect} parent:#{parent.to_s.inspect} size:#{content.size})"
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
File.join(parent.to_s, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class MockDir < Hash
|
||||||
|
attr_accessor :name, :parent
|
||||||
|
|
||||||
|
def initialize(name = nil, parent = nil)
|
||||||
|
@name = name
|
||||||
|
@parent = parent
|
||||||
|
end
|
||||||
|
|
||||||
|
def entry
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
"(MockDir name:#{name.inspect} parent:#{parent.to_s.inspect} size:#{size})"
|
||||||
|
end
|
||||||
|
|
||||||
|
def clone(parent = nil)
|
||||||
|
clone = Marshal.load(Marshal.dump(self))
|
||||||
|
clone.each do |key, value|
|
||||||
|
value.parent = clone
|
||||||
|
end
|
||||||
|
clone.parent = parent if parent
|
||||||
|
clone
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
if parent && parent.to_s != '.'
|
||||||
|
File.join(parent.to_s, name)
|
||||||
|
elsif parent && parent.to_s == '.'
|
||||||
|
"#{File::PATH_SEPARATOR}#{name}"
|
||||||
|
else
|
||||||
|
name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class MockSymlink
|
||||||
|
attr_accessor :name, :target
|
||||||
|
alias_method :to_s, :name
|
||||||
|
|
||||||
|
def initialize(target)
|
||||||
|
@target = target
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
"symlink(#{target.split('/').last})"
|
||||||
|
end
|
||||||
|
|
||||||
|
def entry
|
||||||
|
FileSystem.find(target)
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(*args, &block)
|
||||||
|
entry.send(*args, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def FakeFS
|
||||||
|
return FakeFS unless block_given?
|
||||||
|
::FakeFS.activate!
|
||||||
|
yield
|
||||||
|
::FakeFS.deactivate!
|
||||||
|
end
|
20
test/safe_test.rb
Normal file
20
test/safe_test.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
||||||
|
require 'fakefs/safe'
|
||||||
|
require 'test/unit'
|
||||||
|
|
||||||
|
class FakeFSSafeTest < Test::Unit::TestCase
|
||||||
|
def setup
|
||||||
|
FakeFS.deactivate!
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_FakeFS_method_does_not_intrude_on_global_namespace
|
||||||
|
path = '/path/to/file.txt'
|
||||||
|
|
||||||
|
FakeFS do
|
||||||
|
File.open(path, 'w') { |f| f.write "Yatta!" }
|
||||||
|
assert File.exists?(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert ! File.exists?(path)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user