From 20eb3f238eb853b2f6fadf1bfabd24ce5b8bff5d Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 30 Sep 2009 02:45:48 -0400 Subject: [PATCH 01/18] Add file modes. Replace magic values with constants. --- lib/fakefs/file.rb | 11 ++++++++++- lib/fakefs/file_system.rb | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index 7a1f9b3..19909c6 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -2,6 +2,15 @@ module FakeFS class File PATH_SEPARATOR = '/' + MODES = [ + READ_ONLY = "r", + READ_WRITE = "r+", + WRITE_ONLY = "w", + READ_WRITE_TRUNCATE = "w+", + APPEND_WRITE_ONLY = "a", + APPEND_READ_WRITE = "a+" + ] + def self.extname(path) RealFile.extname(path) end @@ -69,7 +78,7 @@ module FakeFS FileSystem.find(symlink.target).to_s end - def self.open(path, mode='r', perm = 0644) + def self.open(path, mode=READ_ONLY, perm = 0644) if block_given? yield new(path, mode, perm) else diff --git a/lib/fakefs/file_system.rb b/lib/fakefs/file_system.rb index f24a50b..93426f3 100644 --- a/lib/fakefs/file_system.rb +++ b/lib/fakefs/file_system.rb @@ -54,7 +54,7 @@ module FakeFS files.each do |f| if RealFile.file?(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 } end elsif RealFile.directory?(f) From 6068034c29b89d7c4c9dec18474c58a2b879a74b Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 30 Sep 2009 02:52:25 -0400 Subject: [PATCH 02/18] Don't allow writing in read-only mode --- lib/fakefs/file.rb | 9 ++++++++- test/fakefs_test.rb | 10 ++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index 19909c6..a14c6b9 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -127,7 +127,8 @@ module FakeFS end 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) @@ -139,5 +140,11 @@ module FakeFS alias_method :<<, :write def flush; self; end + + private + + def read_only? + @mode == READ_ONLY + end end end diff --git a/test/fakefs_test.rb b/test/fakefs_test.rb index 0ba1f46..5b9d69d 100644 --- a/test/fakefs_test.rb +++ b/test/fakefs_test.rb @@ -96,6 +96,16 @@ class FakeFSTest < Test::Unit::TestCase assert File.exists?(path) 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_can_read_files_once_written path = '/path/to/file.txt' File.open(path, 'w') do |f| From a8245db14599eccdc106d6bf17d40a53deed2783 Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 30 Sep 2009 02:57:10 -0400 Subject: [PATCH 03/18] Raise an error when giving File.open an illegal access mode. Set default mode for File.open to READ_ONLY --- lib/fakefs/file.rb | 10 +++++++++- test/fakefs_test.rb | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index a14c6b9..db14ef3 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -100,7 +100,9 @@ module FakeFS end attr_reader :path - def initialize(path, mode = nil, perm = nil) + def initialize(path, mode = READ_ONLY, perm = nil) + check_mode(mode) + @path = path @mode = mode @file = FileSystem.find(path) @@ -146,5 +148,11 @@ module FakeFS def read_only? @mode == READ_ONLY end + + def check_mode(mode) + if !MODES.include?(mode) + raise ArgumentError, "illegal access mode #{mode}" + end + end end end diff --git a/test/fakefs_test.rb b/test/fakefs_test.rb index 5b9d69d..22b08cc 100644 --- a/test/fakefs_test.rb +++ b/test/fakefs_test.rb @@ -106,6 +106,14 @@ class FakeFSTest < Test::Unit::TestCase 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_can_read_files_once_written path = '/path/to/file.txt' File.open(path, 'w') do |f| From 2881614123b527d5d63d93263c1652c95132d25e Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 30 Sep 2009 03:03:21 -0400 Subject: [PATCH 04/18] Raise an Errno::ENOENT error when opening a file which does not exist --- lib/fakefs/file.rb | 8 ++++++++ test/fakefs_test.rb | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index db14ef3..c608000 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -107,6 +107,8 @@ module FakeFS @mode = mode @file = FileSystem.find(path) @open = true + + check_file_existence! if read_only? end def close @@ -145,6 +147,12 @@ module FakeFS private + def check_file_existence! + unless @file + raise Errno::ENOENT, "No such file or directory - #{@file}" + end + end + def read_only? @mode == READ_ONLY end diff --git a/test/fakefs_test.rb b/test/fakefs_test.rb index 22b08cc..c562cc0 100644 --- a/test/fakefs_test.rb +++ b/test/fakefs_test.rb @@ -114,6 +114,12 @@ class FakeFSTest < Test::Unit::TestCase 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_can_read_files_once_written path = '/path/to/file.txt' File.open(path, 'w') do |f| From 17e2e470273658cd0f22e652b17bee30fda8c200 Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 30 Sep 2009 03:13:58 -0400 Subject: [PATCH 05/18] Raise an error when opening a file which does not exist in read-write ('r+') mode --- lib/fakefs/file.rb | 10 +++++++++- test/fakefs_test.rb | 6 ++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index c608000..0d81d0b 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -108,7 +108,7 @@ module FakeFS @file = FileSystem.find(path) @open = true - check_file_existence! if read_only? + check_file_existence! if !file_creation_mode? end def close @@ -157,6 +157,14 @@ module FakeFS @mode == READ_ONLY end + def file_creation_modes + MODES - [READ_ONLY, READ_WRITE] + end + + def file_creation_mode? + file_creation_modes.include?(@mode) + end + def check_mode(mode) if !MODES.include?(mode) raise ArgumentError, "illegal access mode #{mode}" diff --git a/test/fakefs_test.rb b/test/fakefs_test.rb index c562cc0..fd444ef 100644 --- a/test/fakefs_test.rb +++ b/test/fakefs_test.rb @@ -120,6 +120,12 @@ class FakeFSTest < Test::Unit::TestCase 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_can_read_files_once_written path = '/path/to/file.txt' File.open(path, 'w') do |f| From e3446636e50bd96019f787329b297e7a33da96a9 Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 30 Sep 2009 03:20:59 -0400 Subject: [PATCH 06/18] Create files in w, w+, a, and a+ modes when calling File.new and the file is missing. --- lib/fakefs/file.rb | 13 ++++++++----- test/fakefs_test.rb | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index 0d81d0b..ef0a844 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -108,7 +108,7 @@ module FakeFS @file = FileSystem.find(path) @open = true - check_file_existence! if !file_creation_mode? + file_creation_mode? ? create_missing_file : check_file_existence! end def close @@ -134,10 +134,7 @@ module FakeFS 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 - + create_missing_file @file.content += content end alias_method :print, :write @@ -170,5 +167,11 @@ module FakeFS raise ArgumentError, "illegal access mode #{mode}" end end + + def create_missing_file + if !File.exists?(@path) + @file = FileSystem.add(path, FakeFile.new) + end + end end end diff --git a/test/fakefs_test.rb b/test/fakefs_test.rb index fd444ef..e270833 100644 --- a/test/fakefs_test.rb +++ b/test/fakefs_test.rb @@ -126,6 +126,26 @@ class FakeFSTest < Test::Unit::TestCase 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_can_read_files_once_written path = '/path/to/file.txt' File.open(path, 'w') do |f| From 0b7d45b98511c356c9d75975608886d22b09c086 Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 30 Sep 2009 03:22:28 -0400 Subject: [PATCH 07/18] Don't attempt to create a missing file in File.write (the mode guarantees that the file will already be present) --- lib/fakefs/file.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index ef0a844..3f5b048 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -134,7 +134,6 @@ module FakeFS raise IOError, 'closed stream' unless @open raise IOError, 'not open for writing' if read_only? - create_missing_file @file.content += content end alias_method :print, :write From 3a5098092cccbaeaae36c35eee66f07405483305 Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 30 Sep 2009 03:28:33 -0400 Subject: [PATCH 08/18] Raise an IOError when a file is read in a write-only mode. --- lib/fakefs/file.rb | 7 ++++++- test/fakefs_test.rb | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index 3f5b048..fdc8199 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -116,7 +116,8 @@ module FakeFS end 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 end @@ -172,5 +173,9 @@ module FakeFS @file = FileSystem.add(path, FakeFile.new) end end + + def write_only? + @mode == WRITE_ONLY || @mode == APPEND_WRITE_ONLY + end end end diff --git a/test/fakefs_test.rb b/test/fakefs_test.rb index e270833..1d2914c 100644 --- a/test/fakefs_test.rb +++ b/test/fakefs_test.rb @@ -146,6 +146,26 @@ class FakeFSTest < Test::Unit::TestCase 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_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 path = '/path/to/file.txt' File.open(path, 'w') do |f| From 6d44186cf7913431856587605218f1ce985ddd40 Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 30 Sep 2009 03:36:56 -0400 Subject: [PATCH 09/18] Truncate an existing file in "w", "w+" modes. --- lib/fakefs/file.rb | 9 +++++++++ test/fakefs_test.rb | 16 ++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index fdc8199..72704c9 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -109,6 +109,7 @@ module FakeFS @open = true file_creation_mode? ? create_missing_file : check_file_existence! + truncate_file if truncation_mode? end def close @@ -177,5 +178,13 @@ module FakeFS def write_only? @mode == WRITE_ONLY || @mode == APPEND_WRITE_ONLY end + + def truncate_file + @file.content = "" + end + + def truncation_mode? + @mode == READ_WRITE_TRUNCATE || @mode == WRITE_ONLY + end end end diff --git a/test/fakefs_test.rb b/test/fakefs_test.rb index 1d2914c..9eaaf81 100644 --- a/test/fakefs_test.rb +++ b/test/fakefs_test.rb @@ -156,6 +156,22 @@ class FakeFSTest < Test::Unit::TestCase 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") From 731122a1ac8eed107cdb64b194f103c29d7629ea Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 30 Sep 2009 03:44:42 -0400 Subject: [PATCH 10/18] Refactor mode code --- lib/fakefs/file.rb | 55 ++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index 72704c9..ce0c316 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -11,6 +11,22 @@ module FakeFS 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) RealFile.extname(path) end @@ -100,14 +116,14 @@ module FakeFS end attr_reader :path - def initialize(path, mode = READ_ONLY, perm = nil) - check_mode(mode) + def initialize(path, mode = READ_ONLY, perm = nil) @path = path @mode = mode @file = FileSystem.find(path) @open = true + check_valid_mode file_creation_mode? ? create_missing_file : check_file_existence! truncate_file if truncation_mode? end @@ -119,6 +135,7 @@ module FakeFS def read raise IOError, 'closed stream' unless @open raise IOError, 'not opened for reading' if write_only? + @file.content end @@ -151,22 +168,30 @@ module FakeFS end end - def read_only? - @mode == READ_ONLY + def check_valid_mode + if !mode_in?(MODES) + raise ArgumentError, "illegal access mode #{@mode}" + end end - def file_creation_modes - MODES - [READ_ONLY, READ_WRITE] + def read_only? + mode_in? READ_ONLY_MODES end def file_creation_mode? - file_creation_modes.include?(@mode) + mode_in? FILE_CREATION_MODES end - def check_mode(mode) - if !MODES.include?(mode) - raise ArgumentError, "illegal access mode #{mode}" - 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 @@ -175,16 +200,8 @@ module FakeFS end end - def write_only? - @mode == WRITE_ONLY || @mode == APPEND_WRITE_ONLY - end - def truncate_file @file.content = "" end - - def truncation_mode? - @mode == READ_WRITE_TRUNCATE || @mode == WRITE_ONLY - end end end From 6f08568d8f009a806245dc1e1da88a60c827316a Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Tue, 29 Sep 2009 22:48:50 -0400 Subject: [PATCH 11/18] Add File.link as a copy operation --- lib/fakefs/file.rb | 8 ++++++++ test/fakefs_test.rb | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index ce0c316..fe2e046 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -114,6 +114,14 @@ module FakeFS def self.readlines(path) read(path).split("\n") end + + def self.link(source, dest) + raise(Errno::ENOENT, "No such file or directory - #{source} or #{dest}") if !File.exists?(source) + raise(Errno::EEXIST, "File exists - #{source} or #{dest}") if File.exists?(dest) + + FileUtils.cp(source, dest) + 0 + end attr_reader :path diff --git a/test/fakefs_test.rb b/test/fakefs_test.rb index 9eaaf81..1f69e2a 100644 --- a/test/fakefs_test.rb +++ b/test/fakefs_test.rb @@ -913,6 +913,41 @@ class FakeFSTest < Test::Unit::TestCase def test_tmpdir assert Dir.tmpdir == "/tmp" 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 here(fname) RealFile.expand_path(RealFile.dirname(__FILE__)+'/'+fname) From 72aca398ac572fedb086f6188ea2df260ea78736 Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Tue, 29 Sep 2009 22:54:17 -0400 Subject: [PATCH 12/18] Run all test files in test directory --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index bb6008e..a87500b 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,5 @@ task :default do - Dir['test/*_test.rb'].each { |file| require file } + Dir['test/**/*_test.rb'].each { |file| require file } end begin From a9877659d1c9d137fa4a13b6a089b716a97de4fd Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Tue, 29 Sep 2009 22:55:23 -0400 Subject: [PATCH 13/18] Call the test task what it is --- Rakefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index a87500b..537dd22 100644 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,10 @@ -task :default do +desc "Run tests" +task :test do Dir['test/**/*_test.rb'].each { |file| require file } end +task :default => :test + begin require 'jeweler' From cb9de5938fcdcf61a3e2113899c6bcd2372ceba4 Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Tue, 29 Sep 2009 22:57:14 -0400 Subject: [PATCH 14/18] method_missing should always be private --- lib/fakefs/fake/symlink.rb | 10 ++++++---- test/fake/symlink_test.rb | 11 +++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 test/fake/symlink_test.rb diff --git a/lib/fakefs/fake/symlink.rb b/lib/fakefs/fake/symlink.rb index aef056e..f4a81e9 100644 --- a/lib/fakefs/fake/symlink.rb +++ b/lib/fakefs/fake/symlink.rb @@ -15,12 +15,14 @@ module FakeFS FileSystem.find(target) end - def method_missing(*args, &block) - entry.send(*args, &block) - end - def respond_to?(method) entry.respond_to?(method) end + + private + + def method_missing(*args, &block) + entry.send(*args, &block) + end end end diff --git a/test/fake/symlink_test.rb b/test/fake/symlink_test.rb new file mode 100644 index 0000000..2c59c69 --- /dev/null +++ b/test/fake/symlink_test.rb @@ -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 \ No newline at end of file From 34a88c4e633127fe2c5f6542ef3e7aade8c8cc7a Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 30 Sep 2009 01:23:26 -0400 Subject: [PATCH 15/18] Start File::Stat. Add Stat#symlink?, stat#directory? --- lib/fakefs/file.rb | 22 +++++++++++++++++ test/fakefs_test.rb | 5 ++++ test/file/stat_test.rb | 54 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 test/file/stat_test.rb diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index fe2e046..8345d75 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -123,6 +123,28 @@ module FakeFS 0 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 + end + attr_reader :path def initialize(path, mode = READ_ONLY, perm = nil) diff --git a/test/fakefs_test.rb b/test/fakefs_test.rb index 1f69e2a..0d893af 100644 --- a/test/fakefs_test.rb +++ b/test/fakefs_test.rb @@ -949,6 +949,11 @@ class FakeFSTest < Test::Unit::TestCase assert_equal "some content", File.read("/bar") end + def test_file_stat_returns_file_stat_object + FileUtils.touch("/foo") + assert_equal File::Stat, File.stat("/foo").class + end + def here(fname) RealFile.expand_path(RealFile.dirname(__FILE__)+'/'+fname) end diff --git a/test/file/stat_test.rb b/test/file/stat_test.rb new file mode 100644 index 0000000..fd12353 --- /dev/null +++ b/test/file/stat_test.rb @@ -0,0 +1,54 @@ +$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 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 +end From 7e927d8ff00844dfcd465d3aa2be6aa529e6a5f4 Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 30 Sep 2009 01:26:52 -0400 Subject: [PATCH 16/18] Add File.symlink as an alias for FileUtils.ln_s --- lib/fakefs/file.rb | 4 ++++ test/fakefs_test.rb | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index 8345d75..b85b704 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -123,6 +123,10 @@ module FakeFS 0 end + def self.symlink(source, dest) + FileUtils.ln_s(source, dest) + end + def self.stat(file) File::Stat.new(file) end diff --git a/test/fakefs_test.rb b/test/fakefs_test.rb index 0d893af..690bcf1 100644 --- a/test/fakefs_test.rb +++ b/test/fakefs_test.rb @@ -618,6 +618,14 @@ class FakeFSTest < Test::Unit::TestCase assert_equal 'works', File.open('new/nother') { |f| f.read } 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 FileUtils.touch('touched_file') assert File.exists?('touched_file') From bdcdc26d490ff354670b0adfb63831f419e3a88e Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Thu, 1 Oct 2009 01:39:07 -0400 Subject: [PATCH 17/18] Add hard links. Closes #11 --- lib/fakefs/fake/file.rb | 40 +++++++++++++++++-- lib/fakefs/file.rb | 22 +++++++++-- test/fake/file_test.rb | 88 +++++++++++++++++++++++++++++++++++++++++ test/fakefs_test.rb | 8 ++++ test/file/stat_test.rb | 16 ++++++++ 5 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 test/fake/file_test.rb diff --git a/lib/fakefs/fake/file.rb b/lib/fakefs/fake/file.rb index c8ce6df..57c98f6 100644 --- a/lib/fakefs/fake/file.rb +++ b/lib/fakefs/fake/file.rb @@ -1,16 +1,50 @@ module FakeFS 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 + end def initialize(name = nil, parent = nil) - @name = name + @name = name @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 def clone(parent = nil) clone = super() clone.parent = parent if parent + clone.inode = inode.clone clone end diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index b85b704..a7e3869 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -116,10 +116,22 @@ module FakeFS end def self.link(source, dest) - raise(Errno::ENOENT, "No such file or directory - #{source} or #{dest}") if !File.exists?(source) - raise(Errno::EEXIST, "File exists - #{source} or #{dest}") if File.exists?(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 - FileUtils.cp(source, dest) + 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 @@ -147,6 +159,10 @@ module FakeFS def directory? File.directory?(@file) end + + def nlink + FileSystem.find(@file).links.size + end end attr_reader :path diff --git a/test/fake/file_test.rb b/test/fake/file_test.rb new file mode 100644 index 0000000..dcd87af --- /dev/null +++ b/test/fake/file_test.rb @@ -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 diff --git a/test/fakefs_test.rb b/test/fakefs_test.rb index 690bcf1..70cbf42 100644 --- a/test/fakefs_test.rb +++ b/test/fakefs_test.rb @@ -957,6 +957,14 @@ class FakeFSTest < Test::Unit::TestCase 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 diff --git a/test/file/stat_test.rb b/test/file/stat_test.rb index fd12353..4121774 100644 --- a/test/file/stat_test.rb +++ b/test/file/stat_test.rb @@ -21,6 +21,10 @@ class FileStatTest < Test::Unit::TestCase 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") @@ -51,4 +55,16 @@ class FileStatTest < Test::Unit::TestCase 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 From c00d4506623d16a928fb37ae66084d2c632970a1 Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Thu, 1 Oct 2009 02:42:29 -0400 Subject: [PATCH 18/18] Add File::delete and alias File::unlink. Get those, plus FileUtils.rm to work properly with hard links. Closes #8 --- lib/fakefs/fake/dir.rb | 8 ++++ lib/fakefs/fake/file.rb | 9 ++++ lib/fakefs/fake/symlink.rb | 6 ++- lib/fakefs/file.rb | 18 ++++++++ lib/fakefs/file_system.rb | 4 +- test/fakefs_test.rb | 90 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 132 insertions(+), 3 deletions(-) diff --git a/lib/fakefs/fake/dir.rb b/lib/fakefs/fake/dir.rb index 5ca8ca1..c75a57b 100644 --- a/lib/fakefs/fake/dir.rb +++ b/lib/fakefs/fake/dir.rb @@ -33,5 +33,13 @@ module FakeFS name end end + + def delete(node = self) + if node == self + parent.delete(self) + else + super(node.name) + end + end end end diff --git a/lib/fakefs/fake/file.rb b/lib/fakefs/fake/file.rb index 57c98f6..2c3074b 100644 --- a/lib/fakefs/fake/file.rb +++ b/lib/fakefs/fake/file.rb @@ -15,6 +15,10 @@ module FakeFS links << file unless links.include?(file) file.inode = self end + + def unlink(file) + links.delete(file) + end end def initialize(name = nil, parent = nil) @@ -59,5 +63,10 @@ module FakeFS def to_s File.join(parent.to_s, name) end + + def delete + inode.unlink(self) + parent.delete(self) + end end end diff --git a/lib/fakefs/fake/symlink.rb b/lib/fakefs/fake/symlink.rb index f4a81e9..81d6c3c 100644 --- a/lib/fakefs/fake/symlink.rb +++ b/lib/fakefs/fake/symlink.rb @@ -15,10 +15,14 @@ module FakeFS FileSystem.find(target) end + def delete + parent.delete(self) + end + def respond_to?(method) entry.respond_to?(method) end - + private def method_missing(*args, &block) diff --git a/lib/fakefs/file.rb b/lib/fakefs/file.rb index a7e3869..64c5afe 100644 --- a/lib/fakefs/file.rb +++ b/lib/fakefs/file.rb @@ -135,6 +135,24 @@ module FakeFS 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 diff --git a/lib/fakefs/file_system.rb b/lib/fakefs/file_system.rb index 93426f3..09af802 100644 --- a/lib/fakefs/file_system.rb +++ b/lib/fakefs/file_system.rb @@ -66,8 +66,8 @@ module FakeFS end def delete(path) - if dir = FileSystem.find(path) - dir.parent.delete(dir.name) + if node = FileSystem.find(path) + node.delete end end diff --git a/test/fakefs_test.rb b/test/fakefs_test.rb index 70cbf42..0d06956 100644 --- a/test/fakefs_test.rb +++ b/test/fakefs_test.rb @@ -970,6 +970,96 @@ class FakeFSTest < Test::Unit::TestCase 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) RealFile.expand_path(RealFile.dirname(__FILE__)+'/'+fname) end