Merge remote branch 'smt/hard_links_and_deletion'

This commit is contained in:
Chris Wanstrath 2009-10-06 20:55:11 -07:00
commit e4ea7e1406
10 changed files with 620 additions and 18 deletions

View File

@ -1,7 +1,10 @@
task :default do desc "Run tests"
Dir['test/*_test.rb'].each { |file| require file } task :test do
Dir['test/**/*_test.rb'].each { |file| require file }
end end
task :default => :test
begin begin
require 'jeweler' require 'jeweler'

View File

@ -33,5 +33,13 @@ module FakeFS
name name
end end
end end
def delete(node = self)
if node == self
parent.delete(self)
else
super(node.name)
end
end
end end
end end

View File

@ -1,16 +1,54 @@
module FakeFS module FakeFS
class FakeFile class FakeFile
attr_accessor :name, :parent, :content attr_accessor :name, :parent
class Inode
def initialize(file_owner)
@content = ""
@links = [file_owner]
end
attr_accessor :content
attr_accessor :links
def link(file)
links << file unless links.include?(file)
file.inode = self
end
def unlink(file)
links.delete(file)
end
end
def initialize(name = nil, parent = nil) def initialize(name = nil, parent = nil)
@name = name @name = name
@parent = parent @parent = parent
@content = '' @inode = Inode.new(self)
end
attr_accessor :inode
def content
@inode.content
end
def content=(str)
@inode.content = str
end
def links
@inode.links
end
def link(other_file)
@inode.link(other_file)
end end
def clone(parent = nil) def clone(parent = nil)
clone = super() clone = super()
clone.parent = parent if parent clone.parent = parent if parent
clone.inode = inode.clone
clone clone
end end
@ -25,5 +63,10 @@ module FakeFS
def to_s def to_s
File.join(parent.to_s, name) File.join(parent.to_s, name)
end end
def delete
inode.unlink(self)
parent.delete(self)
end
end end
end end

View File

@ -15,12 +15,18 @@ module FakeFS
FileSystem.find(target) FileSystem.find(target)
end end
def method_missing(*args, &block) def delete
entry.send(*args, &block) parent.delete(self)
end end
def respond_to?(method) def respond_to?(method)
entry.respond_to?(method) entry.respond_to?(method)
end end
private
def method_missing(*args, &block)
entry.send(*args, &block)
end
end end
end end

View File

@ -2,6 +2,31 @@ module FakeFS
class File class File
PATH_SEPARATOR = '/' PATH_SEPARATOR = '/'
MODES = [
READ_ONLY = "r",
READ_WRITE = "r+",
WRITE_ONLY = "w",
READ_WRITE_TRUNCATE = "w+",
APPEND_WRITE_ONLY = "a",
APPEND_READ_WRITE = "a+"
]
FILE_CREATION_MODES = MODES - [READ_ONLY, READ_WRITE]
READ_ONLY_MODES = [
READ_ONLY
]
WRITE_ONLY_MODES = [
WRITE_ONLY,
APPEND_WRITE_ONLY
]
TRUNCATION_MODES = [
WRITE_ONLY,
READ_WRITE_TRUNCATE
]
def self.extname(path) def self.extname(path)
RealFile.extname(path) RealFile.extname(path)
end end
@ -69,7 +94,7 @@ module FakeFS
FileSystem.find(symlink.target).to_s FileSystem.find(symlink.target).to_s
end end
def self.open(path, mode='r', perm = 0644) def self.open(path, mode=READ_ONLY, perm = 0644)
if block_given? if block_given?
yield new(path, mode, perm) yield new(path, mode, perm)
else else
@ -89,13 +114,86 @@ module FakeFS
def self.readlines(path) def self.readlines(path)
read(path).split("\n") read(path).split("\n")
end end
def self.link(source, dest)
if directory?(source)
raise Errno::EPERM, "Operation not permitted - #{source} or #{dest}"
end
if !exists?(source)
raise Errno::ENOENT, "No such file or directory - #{source} or #{dest}"
end
if exists?(dest)
raise Errno::EEXIST, "File exists - #{source} or #{dest}"
end
source = FileSystem.find(source)
dest = FileSystem.add(dest, source.entry.clone)
source.link(dest)
0
end
def self.delete(file_name, *additional_file_names)
if !exists?(file_name)
raise Errno::ENOENT, "No such file or directory - #{file_name}"
end
FileUtils.rm(file_name)
additional_file_names.each do |file_name|
FileUtils.rm(file_name)
end
additional_file_names.size + 1
end
class << self
alias_method :unlink, :delete
end
def self.symlink(source, dest)
FileUtils.ln_s(source, dest)
end
def self.stat(file)
File::Stat.new(file)
end
class Stat
def initialize(file)
if !File.exists?(file)
raise(Errno::ENOENT, "No such file or directory - #{file}")
end
@file = file
end
def symlink?
File.symlink?(@file)
end
def directory?
File.directory?(@file)
end
def nlink
FileSystem.find(@file).links.size
end
end
attr_reader :path attr_reader :path
def initialize(path, mode = nil, perm = nil)
def initialize(path, mode = READ_ONLY, perm = nil)
@path = path @path = path
@mode = mode @mode = mode
@file = FileSystem.find(path) @file = FileSystem.find(path)
@open = true @open = true
check_valid_mode
file_creation_mode? ? create_missing_file : check_file_existence!
truncate_file if truncation_mode?
end end
def close def close
@ -103,7 +201,9 @@ module FakeFS
end end
def read def read
raise IOError.new('closed stream') unless @open raise IOError, 'closed stream' unless @open
raise IOError, 'not opened for reading' if write_only?
@file.content @file.content
end end
@ -118,11 +218,8 @@ module FakeFS
end end
def write(content) def write(content)
raise IOError.new('closed stream') unless @open raise IOError, 'closed stream' unless @open
raise IOError, 'not open for writing' if read_only?
if !File.exists?(@path)
@file = FileSystem.add(path, FakeFile.new)
end
@file.content += content @file.content += content
end end
@ -130,5 +227,49 @@ module FakeFS
alias_method :<<, :write alias_method :<<, :write
def flush; self; end def flush; self; end
private
def check_file_existence!
unless @file
raise Errno::ENOENT, "No such file or directory - #{@file}"
end
end
def check_valid_mode
if !mode_in?(MODES)
raise ArgumentError, "illegal access mode #{@mode}"
end
end
def read_only?
mode_in? READ_ONLY_MODES
end
def file_creation_mode?
mode_in? FILE_CREATION_MODES
end
def write_only?
mode_in? WRITE_ONLY_MODES
end
def truncation_mode?
mode_in? TRUNCATION_MODES
end
def mode_in?(list)
list.include?(@mode)
end
def create_missing_file
if !File.exists?(@path)
@file = FileSystem.add(path, FakeFile.new)
end
end
def truncate_file
@file.content = ""
end
end end
end end

View File

@ -54,7 +54,7 @@ module FakeFS
files.each do |f| files.each do |f|
if RealFile.file?(f) if RealFile.file?(f)
FileUtils.mkdir_p(File.dirname(f)) FileUtils.mkdir_p(File.dirname(f))
File.open(f, 'w') do |g| File.open(f, File::WRITE_ONLY) do |g|
g.print RealFile.open(f){|h| h.read } g.print RealFile.open(f){|h| h.read }
end end
elsif RealFile.directory?(f) elsif RealFile.directory?(f)
@ -66,8 +66,8 @@ module FakeFS
end end
def delete(path) def delete(path)
if dir = FileSystem.find(path) if node = FileSystem.find(path)
dir.parent.delete(dir.name) node.delete
end end
end end

88
test/fake/file_test.rb Normal file
View File

@ -0,0 +1,88 @@
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
require 'fakefs/safe'
require 'test/unit'
class FakeFileTest < Test::Unit::TestCase
include FakeFS
def setup
FileSystem.clear
@file = FakeFile.new
end
def test_fake_file_has_empty_content_by_default
assert_equal "", @file.content
end
def test_fake_file_can_read_and_write_to_content
@file.content = "foobar"
assert_equal "foobar", @file.content
end
def test_fake_file_has_1_link_by_default
assert_equal [@file], @file.links
end
def test_fake_file_can_create_link
other_file = FakeFile.new
@file.link(other_file)
assert_equal [@file, other_file], @file.links
end
def test_fake_file_wont_add_link_to_same_file_twice
other_file = FakeFile.new
@file.link other_file
@file.link other_file
assert_equal [@file, other_file], @file.links
end
def test_links_are_mutual
other_file = FakeFile.new
@file.link(other_file)
assert_equal [@file, other_file], other_file.links
end
def test_can_link_multiple_files
file_two = FakeFile.new
file_three = FakeFile.new
@file.link file_two
@file.link file_three
assert_equal [@file, file_two, file_three], @file.links
assert_equal [@file, file_two, file_three], file_two.links
assert_equal [@file, file_two, file_three], file_three.links
end
def test_links_share_same_content
other_file = FakeFile.new
@file.link other_file
@file.content = "foobar"
assert_equal "foobar", other_file.content
end
def test_clone_creates_new_inode
clone = @file.clone
assert !clone.inode.equal?(@file.inode)
end
def test_cloning_does_not_use_same_content_object
clone = @file.clone
clone.content = "foo"
@file.content = "bar"
assert_equal "foo", clone.content
assert_equal "bar", @file.content
end
end

11
test/fake/symlink_test.rb Normal file
View File

@ -0,0 +1,11 @@
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
require 'fakefs/safe'
require 'test/unit'
class FakeSymlinkTest < Test::Unit::TestCase
include FakeFS
def test_symlink_has_method_missing_as_private
assert FakeSymlink.private_instance_methods.include?("method_missing")
end
end

View File

@ -96,6 +96,92 @@ class FakeFSTest < Test::Unit::TestCase
assert File.exists?(path) assert File.exists?(path)
end end
def test_file_opens_in_read_only_mode
File.open("foo", "w") { |f| f << "foo" }
f = File.open("foo")
assert_raises(IOError) do
f << "bar"
end
end
def test_file_opens_in_invalid_mode
FileUtils.touch("foo")
assert_raises(ArgumentError) do
File.open("foo", "an_illegal_mode")
end
end
def test_raises_error_when_cannot_find_file_in_read_mode
assert_raises(Errno::ENOENT) do
File.open("does_not_exist", "r")
end
end
def test_raises_error_when_cannot_find_file_in_read_write_mode
assert_raises(Errno::ENOENT) do
File.open("does_not_exist", "r+")
end
end
def test_creates_files_in_write_only_mode
File.open("foo", "w")
assert File.exists?("foo")
end
def test_creates_files_in_read_write_truncate_mode
File.open("foo", "w+")
assert File.exists?("foo")
end
def test_creates_files_in_append_write_only
File.open("foo", "a")
assert File.exists?("foo")
end
def test_creates_files_in_append_read_write
File.open("foo", "a+")
assert File.exists?("foo")
end
def test_file_in_write_only_raises_error_when_reading
FileUtils.touch("foo")
f = File.open("foo", "w")
assert_raises(IOError) do
f.read
end
end
def test_file_in_write_mode_truncates_existing_file
File.open("foo", "w") { |f| f << "contents" }
f = File.open("foo", "w")
assert_equal "", File.read("foo")
end
def test_file_in_read_write_truncation_mode_truncates_file
File.open("foo", "w") { |f| f << "foo" }
f = File.open("foo", "w+")
assert_equal "", File.read("foo")
end
def test_file_in_append_write_only_raises_error_when_reading
FileUtils.touch("foo")
f = File.open("foo", "a")
assert_raises(IOError) do
f.read
end
end
def test_can_read_files_once_written def test_can_read_files_once_written
path = '/path/to/file.txt' path = '/path/to/file.txt'
File.open(path, 'w') do |f| File.open(path, 'w') do |f|
@ -532,6 +618,14 @@ class FakeFSTest < Test::Unit::TestCase
assert_equal 'works', File.open('new/nother') { |f| f.read } assert_equal 'works', File.open('new/nother') { |f| f.read }
end end
def test_can_symlink_through_file
FileUtils.touch("/foo")
File.symlink("/foo", "/bar")
assert File.symlink?("/bar")
end
def test_files_can_be_touched def test_files_can_be_touched
FileUtils.touch('touched_file') FileUtils.touch('touched_file')
assert File.exists?('touched_file') assert File.exists?('touched_file')
@ -827,6 +921,144 @@ class FakeFSTest < Test::Unit::TestCase
def test_tmpdir def test_tmpdir
assert Dir.tmpdir == "/tmp" assert Dir.tmpdir == "/tmp"
end end
def test_hard_link_creates_file
FileUtils.touch("/foo")
File.link("/foo", "/bar")
assert File.exists?("/bar")
end
def test_hard_link_with_missing_file_raises_error
assert_raises(Errno::ENOENT) do
File.link("/foo", "/bar")
end
end
def test_hard_link_with_existing_destination_file
FileUtils.touch("/foo")
FileUtils.touch("/bar")
assert_raises(Errno::EEXIST) do
File.link("/foo", "/bar")
end
end
def test_hard_link_returns_0_when_successful
FileUtils.touch("/foo")
assert_equal 0, File.link("/foo", "/bar")
end
def test_hard_link_returns_duplicate_file
File.open("/foo", "w") { |x| x << "some content" }
File.link("/foo", "/bar")
assert_equal "some content", File.read("/bar")
end
def test_hard_link_with_directory_raises_error
Dir.mkdir "/foo"
assert_raises(Errno::EPERM) do
File.link("/foo", "/bar")
end
end
def test_file_stat_returns_file_stat_object
FileUtils.touch("/foo")
assert_equal File::Stat, File.stat("/foo").class
end
def test_can_delete_file_with_delete
FileUtils.touch("/foo")
File.delete("/foo")
assert !File.exists?("/foo")
end
def test_can_delete_multiple_files_with_delete
FileUtils.touch("/foo")
FileUtils.touch("/bar")
File.delete("/foo", "/bar")
assert !File.exists?("/foo")
assert !File.exists?("/bar")
end
def test_delete_raises_argument_error_with_no_filename_given
assert_raises ArgumentError do
File.delete
end
end
def test_delete_returns_number_one_when_given_one_arg
FileUtils.touch("/foo")
assert_equal 1, File.delete("/foo")
end
def test_delete_returns_number_two_when_given_two_args
FileUtils.touch("/foo")
FileUtils.touch("/bar")
assert_equal 2, File.delete("/foo", "/bar")
end
def test_delete_raises_error_when_first_file_does_not_exist
assert_raises Errno::ENOENT do
File.delete("/foo")
end
end
def test_delete_does_not_raise_error_when_second_file_does_not_exist
FileUtils.touch("/foo")
assert_nothing_raised do
File.delete("/foo", "/bar")
end
end
def test_unlink_is_alias_for_delete
assert_equal File.method(:unlink), File.method(:delete)
end
def test_unlink_removes_only_one_file_content
File.open("/foo", "w") { |f| f << "some_content" }
File.link("/foo", "/bar")
File.unlink("/bar")
File.read("/foo") == "some_content"
end
def test_link_reports_correct_stat_info_after_unlinking
File.open("/foo", "w") { |f| f << "some_content" }
File.link("/foo", "/bar")
File.unlink("/bar")
assert_equal 1, File.stat("/foo").nlink
end
def test_delete_works_with_symlink
FileUtils.touch("/foo")
File.symlink("/foo", "/bar")
File.unlink("/bar")
assert File.exists?("/foo")
assert !File.exists?("/bar")
end
def test_delete_works_with_symlink_source
FileUtils.touch("/foo")
File.symlink("/foo", "/bar")
File.unlink("/foo")
assert !File.exists?("/foo")
end
def here(fname) def here(fname)
RealFile.expand_path(RealFile.dirname(__FILE__)+'/'+fname) RealFile.expand_path(RealFile.dirname(__FILE__)+'/'+fname)

70
test/file/stat_test.rb Normal file
View File

@ -0,0 +1,70 @@
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
require 'fakefs/safe'
require 'test/unit'
class FileStatTest < Test::Unit::TestCase
include FakeFS
def setup
FileSystem.clear
end
def touch(*args)
FileUtils.touch(*args)
end
def ln_s(*args)
FileUtils.ln_s(*args)
end
def mkdir(*args)
Dir.mkdir(*args)
end
def ln(*args)
File.link(*args)
end
def test_file_stat_init_with_non_existant_file
assert_raises(Errno::ENOENT) do
File::Stat.new("/foo")
end
end
def test_symlink_should_be_true_when_symlink
touch("/foo")
ln_s("/foo", "/bar")
assert File::Stat.new("/bar").symlink?
end
def test_symlink_should_be_false_when_not_a_symlink
FileUtils.touch("/foo")
assert !File::Stat.new("/foo").symlink?
end
def test_should_return_false_for_directory_when_not_a_directory
FileUtils.touch("/foo")
assert !File::Stat.new("/foo").directory?
end
def test_should_return_true_for_directory_when_a_directory
mkdir "/foo"
assert File::Stat.new("/foo").directory?
end
def test_one_file_has_hard_link
touch "testfile"
assert_equal 1, File.stat("testfile").nlink
end
def test_two_hard_links_show_nlinks_as_two
touch "testfile"
ln "testfile", "testfile.bak"
assert_equal 2, File.stat("testfile").nlink
end
end